Jofthomas HF Staff commited on
Commit
7b700e7
·
verified ·
1 Parent(s): b95828a

Create script.js

Browse files
Files changed (1) hide show
  1. static/script.js +149 -0
static/script.js ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ document.addEventListener('DOMContentLoaded', () => {
2
+ const leaderboardBody = document.getElementById('leaderboard-body');
3
+ const loadingIndicator = document.getElementById('loading-indicator');
4
+ const lastUpdatedElement = document.getElementById('last-updated');
5
+ const modal = document.getElementById('code-modal');
6
+ const modalUsername = document.getElementById('modal-username');
7
+ const modalCode = document.getElementById('modal-code');
8
+ const closeModalButton = document.querySelector('.close-button');
9
+
10
+ // Fetch from the FastAPI backend endpoint
11
+ const API_URL = '/api/leaderboard';
12
+ const REFRESH_INTERVAL_MS = 60 * 1000; // 60 seconds
13
+
14
+ async function fetchData() {
15
+ try {
16
+ // Fetch directly from our backend API
17
+ const response = await fetch(API_URL);
18
+ if (!response.ok) {
19
+ // Try to get error message from backend if available
20
+ let errorMsg = `HTTP error! status: ${response.status}`;
21
+ try {
22
+ const errorData = await response.json();
23
+ errorMsg = errorData.detail || errorMsg;
24
+ } catch (e) { /* Ignore if response is not JSON */ }
25
+ throw new Error(errorMsg);
26
+ }
27
+ const data = await response.json();
28
+ // The data is expected to be the already sorted list of objects
29
+ return data;
30
+ } catch (error) {
31
+ console.error("Failed to fetch leaderboard data:", error);
32
+ lastUpdatedElement.textContent = `Error: ${error.message}`; // Display error
33
+ return null; // Return null to indicate failure
34
+ }
35
+ }
36
+
37
+ // No sorting needed here - backend does it
38
+ // function sortData(data) { ... } // REMOVED
39
+
40
+ function formatTimestamp(isoString) {
41
+ // Keep this helper function
42
+ try {
43
+ const date = new Date(isoString);
44
+ return date.toISOString().slice(0, 19).replace('T', ' ');
45
+ } catch (e) {
46
+ return 'Invalid Date';
47
+ }
48
+ }
49
+
50
+ function renderLeaderboard(leaderboardData) {
51
+ // Clear previous content
52
+ leaderboardBody.innerHTML = '';
53
+
54
+ if (!leaderboardData || leaderboardData.length === 0) {
55
+ // Check if the loading indicator is still displayed, means fetch failed on load
56
+ if (loadingIndicator.style.display !== 'none') {
57
+ leaderboardBody.innerHTML = '<div class="leaderboard-row" style="justify-content: center; color: #aaa;">No data available or failed to load. Check logs.</div>';
58
+ } else {
59
+ // If fetch failed during refresh, keep old data and show error in footer? Or clear?
60
+ // Let's clear for now and rely on error message in footer.
61
+ leaderboardBody.innerHTML = '<div class="leaderboard-row" style="justify-content: center; color: #aaa;">Failed to refresh data.</div>';
62
+ }
63
+ return;
64
+ }
65
+
66
+ leaderboardData.forEach((entry, index) => {
67
+ const rank = index + 1;
68
+ const row = document.createElement('div');
69
+ row.className = 'leaderboard-row';
70
+ row.setAttribute('data-rank', rank);
71
+
72
+ row.innerHTML = `
73
+ <div class="rank">#${rank}</div>
74
+ <div class="username" title="${entry.username}">${entry.username}</div>
75
+ <div class="score">${entry.score} pts</div>
76
+ <div class="timestamp">${formatTimestamp(entry.timestamp)}</div>
77
+ <div class="code-action">
78
+ <button class="view-code-btn" data-username="${entry.username}">View</button>
79
+ </div>
80
+ `;
81
+
82
+ const button = row.querySelector('.view-code-btn');
83
+ button._codeData = entry.code; // Store code directly
84
+ button.addEventListener('click', handleViewCodeClick);
85
+
86
+ leaderboardBody.appendChild(row);
87
+ });
88
+ }
89
+
90
+ function handleViewCodeClick(event) {
91
+ const button = event.currentTarget;
92
+ const username = button.getAttribute('data-username');
93
+ const code = button._codeData;
94
+ showCodeModal(username, code);
95
+ }
96
+
97
+ function showCodeModal(username, code) {
98
+ modalUsername.textContent = `Code from ${username}`;
99
+ modalCode.textContent = code || '// No code submitted or available';
100
+
101
+ if (typeof hljs !== 'undefined') {
102
+ modalCode.className = 'language-python';
103
+ hljs.highlightElement(modalCode);
104
+ }
105
+ modal.style.display = 'block';
106
+ }
107
+
108
+ function hideCodeModal() {
109
+ modal.style.display = 'none';
110
+ modalCode.textContent = '';
111
+ modalUsername.textContent = '';
112
+ }
113
+
114
+ async function updateLeaderboard() {
115
+ if (!leaderboardBody.hasChildNodes()) {
116
+ loadingIndicator.style.display = 'flex';
117
+ } else {
118
+ lastUpdatedElement.textContent = 'Updating...';
119
+ }
120
+
121
+ const leaderboardData = await fetchData(); // Fetches already sorted data
122
+
123
+ loadingIndicator.style.display = 'none'; // Hide loading indicator regardless of success/fail
124
+
125
+ if (leaderboardData) {
126
+ renderLeaderboard(leaderboardData);
127
+ // Only update timestamp on success
128
+ lastUpdatedElement.textContent = `Last updated: ${new Date().toLocaleTimeString()}`;
129
+ } else {
130
+ // Error occurred, message already set in fetchData
131
+ // Optionally render empty state if needed
132
+ if (!leaderboardBody.hasChildNodes()) {
133
+ renderLeaderboard(null); // Show 'No data' message if initial load failed
134
+ }
135
+ }
136
+ }
137
+
138
+ // --- Event Listeners ---
139
+ closeModalButton.addEventListener('click', hideCodeModal);
140
+ window.addEventListener('click', (event) => {
141
+ if (event.target === modal) {
142
+ hideCodeModal();
143
+ }
144
+ });
145
+
146
+ // --- Initial Load and Auto-Refresh ---
147
+ updateLeaderboard(); // Initial load
148
+ setInterval(updateLeaderboard, REFRESH_INTERVAL_MS); // Refresh every 60 seconds
149
+ });