Jofthomas's picture
Create script.js
7b700e7 verified
document.addEventListener('DOMContentLoaded', () => {
const leaderboardBody = document.getElementById('leaderboard-body');
const loadingIndicator = document.getElementById('loading-indicator');
const lastUpdatedElement = document.getElementById('last-updated');
const modal = document.getElementById('code-modal');
const modalUsername = document.getElementById('modal-username');
const modalCode = document.getElementById('modal-code');
const closeModalButton = document.querySelector('.close-button');
// Fetch from the FastAPI backend endpoint
const API_URL = '/api/leaderboard';
const REFRESH_INTERVAL_MS = 60 * 1000; // 60 seconds
async function fetchData() {
try {
// Fetch directly from our backend API
const response = await fetch(API_URL);
if (!response.ok) {
// Try to get error message from backend if available
let errorMsg = `HTTP error! status: ${response.status}`;
try {
const errorData = await response.json();
errorMsg = errorData.detail || errorMsg;
} catch (e) { /* Ignore if response is not JSON */ }
throw new Error(errorMsg);
}
const data = await response.json();
// The data is expected to be the already sorted list of objects
return data;
} catch (error) {
console.error("Failed to fetch leaderboard data:", error);
lastUpdatedElement.textContent = `Error: ${error.message}`; // Display error
return null; // Return null to indicate failure
}
}
// No sorting needed here - backend does it
// function sortData(data) { ... } // REMOVED
function formatTimestamp(isoString) {
// Keep this helper function
try {
const date = new Date(isoString);
return date.toISOString().slice(0, 19).replace('T', ' ');
} catch (e) {
return 'Invalid Date';
}
}
function renderLeaderboard(leaderboardData) {
// Clear previous content
leaderboardBody.innerHTML = '';
if (!leaderboardData || leaderboardData.length === 0) {
// Check if the loading indicator is still displayed, means fetch failed on load
if (loadingIndicator.style.display !== 'none') {
leaderboardBody.innerHTML = '<div class="leaderboard-row" style="justify-content: center; color: #aaa;">No data available or failed to load. Check logs.</div>';
} else {
// If fetch failed during refresh, keep old data and show error in footer? Or clear?
// Let's clear for now and rely on error message in footer.
leaderboardBody.innerHTML = '<div class="leaderboard-row" style="justify-content: center; color: #aaa;">Failed to refresh data.</div>';
}
return;
}
leaderboardData.forEach((entry, index) => {
const rank = index + 1;
const row = document.createElement('div');
row.className = 'leaderboard-row';
row.setAttribute('data-rank', rank);
row.innerHTML = `
<div class="rank">#${rank}</div>
<div class="username" title="${entry.username}">${entry.username}</div>
<div class="score">${entry.score} pts</div>
<div class="timestamp">${formatTimestamp(entry.timestamp)}</div>
<div class="code-action">
<button class="view-code-btn" data-username="${entry.username}">View</button>
</div>
`;
const button = row.querySelector('.view-code-btn');
button._codeData = entry.code; // Store code directly
button.addEventListener('click', handleViewCodeClick);
leaderboardBody.appendChild(row);
});
}
function handleViewCodeClick(event) {
const button = event.currentTarget;
const username = button.getAttribute('data-username');
const code = button._codeData;
showCodeModal(username, code);
}
function showCodeModal(username, code) {
modalUsername.textContent = `Code from ${username}`;
modalCode.textContent = code || '// No code submitted or available';
if (typeof hljs !== 'undefined') {
modalCode.className = 'language-python';
hljs.highlightElement(modalCode);
}
modal.style.display = 'block';
}
function hideCodeModal() {
modal.style.display = 'none';
modalCode.textContent = '';
modalUsername.textContent = '';
}
async function updateLeaderboard() {
if (!leaderboardBody.hasChildNodes()) {
loadingIndicator.style.display = 'flex';
} else {
lastUpdatedElement.textContent = 'Updating...';
}
const leaderboardData = await fetchData(); // Fetches already sorted data
loadingIndicator.style.display = 'none'; // Hide loading indicator regardless of success/fail
if (leaderboardData) {
renderLeaderboard(leaderboardData);
// Only update timestamp on success
lastUpdatedElement.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
} else {
// Error occurred, message already set in fetchData
// Optionally render empty state if needed
if (!leaderboardBody.hasChildNodes()) {
renderLeaderboard(null); // Show 'No data' message if initial load failed
}
}
}
// --- Event Listeners ---
closeModalButton.addEventListener('click', hideCodeModal);
window.addEventListener('click', (event) => {
if (event.target === modal) {
hideCodeModal();
}
});
// --- Initial Load and Auto-Refresh ---
updateLeaderboard(); // Initial load
setInterval(updateLeaderboard, REFRESH_INTERVAL_MS); // Refresh every 60 seconds
});