ICRA26WM / static /js /main.js
qiukingballball's picture
squash history
c8b725d
/**
* ICRA26 Workshop - Main JavaScript
* Shared utilities for all pages
*/
// Common utility functions
const Utils = {
/**
* Format bytes to human readable string
*/
formatBytes(bytes) {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
},
/**
* Format date to local string
*/
formatDate(dateString) {
return new Date(dateString).toLocaleString();
},
/**
* Escape HTML to prevent XSS
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
},
/**
* Show notification message
*/
showNotification(message, type = 'info', duration = 5000) {
const container = document.getElementById('notification-container');
if (!container) return;
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<span class="notification-message">${message}</span>
<button class="notification-close">&times;</button>
`;
container.appendChild(notification);
// Close button handler
notification.querySelector('.notification-close').addEventListener('click', () => {
notification.remove();
});
// Auto-remove after duration
if (duration > 0) {
setTimeout(() => notification.remove(), duration);
}
},
/**
* Debounce function
*/
debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
},
/**
* Check if element is in viewport
*/
isInViewport(element) {
const rect = element.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
};
// API helper
const API = {
async get(url) {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
},
async post(url, data) {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
if (!response.ok) {
const error = await response.json().catch(() => ({ detail: 'Request failed' }));
throw new Error(error.detail || 'Request failed');
}
return response.json();
},
async upload(url, file, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', url);
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable && onProgress) {
onProgress((e.loaded / e.total) * 100);
}
});
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve(JSON.parse(xhr.responseText));
} else {
try {
const error = JSON.parse(xhr.responseText);
reject(new Error(error.detail || 'Upload failed'));
} catch {
reject(new Error('Upload failed'));
}
}
});
xhr.addEventListener('error', () => reject(new Error('Network error')));
const formData = new FormData();
formData.append('file', file);
xhr.send(formData);
});
}
};
// Auto-refresh helper for leaderboard
class AutoRefresh {
constructor(fetchFn, interval = 30000) {
this.fetchFn = fetchFn;
this.interval = interval;
this.isActive = true;
}
start() {
this.isActive = true;
this.fetchFn();
this.timer = setInterval(() => {
if (this.isActive) {
this.fetchFn();
}
}, this.interval);
}
stop() {
this.isActive = false;
if (this.timer) {
clearInterval(this.timer);
}
}
refresh() {
if (this.isActive) {
this.fetchFn();
}
}
}
// Status polling helper
class StatusPoller {
constructor(submissionId, onUpdate, onComplete, interval = 3000) {
this.submissionId = submissionId;
this.onUpdate = onUpdate;
this.onComplete = onComplete;
this.interval = interval;
this.timer = null;
}
start() {
const poll = async () => {
try {
const response = await fetch(`/api/status/${this.submissionId}`);
const data = await response.json();
this.onUpdate(data);
if (data.status === 'completed' || data.status === 'error') {
this.stop();
if (this.onComplete) {
this.onComplete(data);
}
} else {
this.timer = setTimeout(poll, this.interval);
}
} catch (error) {
console.error('Polling error:', error);
this.timer = setTimeout(poll, this.interval);
}
};
poll();
}
stop() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
}
// Export for use in inline scripts
window.Utils = Utils;
window.API = API;
window.AutoRefresh = AutoRefresh;
window.StatusPoller = StatusPoller;