| | 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'); |
| |
|
| | |
| | const API_URL = '/api/leaderboard'; |
| | const REFRESH_INTERVAL_MS = 60 * 1000; |
| |
|
| | async function fetchData() { |
| | try { |
| | |
| | const response = await fetch(API_URL); |
| | if (!response.ok) { |
| | |
| | let errorMsg = `HTTP error! status: ${response.status}`; |
| | try { |
| | const errorData = await response.json(); |
| | errorMsg = errorData.detail || errorMsg; |
| | } catch (e) { } |
| | throw new Error(errorMsg); |
| | } |
| | const data = await response.json(); |
| | |
| | return data; |
| | } catch (error) { |
| | console.error("Failed to fetch leaderboard data:", error); |
| | lastUpdatedElement.textContent = `Error: ${error.message}`; |
| | return null; |
| | } |
| | } |
| |
|
| | |
| | |
| |
|
| | function formatTimestamp(isoString) { |
| | |
| | try { |
| | const date = new Date(isoString); |
| | return date.toISOString().slice(0, 19).replace('T', ' '); |
| | } catch (e) { |
| | return 'Invalid Date'; |
| | } |
| | } |
| |
|
| | function renderLeaderboard(leaderboardData) { |
| | |
| | leaderboardBody.innerHTML = ''; |
| |
|
| | if (!leaderboardData || leaderboardData.length === 0) { |
| | |
| | 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 { |
| | |
| | |
| | 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; |
| | 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(); |
| |
|
| | loadingIndicator.style.display = 'none'; |
| |
|
| | if (leaderboardData) { |
| | renderLeaderboard(leaderboardData); |
| | |
| | lastUpdatedElement.textContent = `Last updated: ${new Date().toLocaleTimeString()}`; |
| | } else { |
| | |
| | |
| | if (!leaderboardBody.hasChildNodes()) { |
| | renderLeaderboard(null); |
| | } |
| | } |
| | } |
| |
|
| | |
| | closeModalButton.addEventListener('click', hideCodeModal); |
| | window.addEventListener('click', (event) => { |
| | if (event.target === modal) { |
| | hideCodeModal(); |
| | } |
| | }); |
| |
|
| | |
| | updateLeaderboard(); |
| | setInterval(updateLeaderboard, REFRESH_INTERVAL_MS); |
| | }); |