retropulse/public/js/overlay.js
2025-09-03 21:55:40 +05:30

184 lines
7.9 KiB
JavaScript

class RetroPulseOverlay {
constructor() {
this.ws = null;
this.username = null;
this.reconnectInterval = null;
this.reconnectDelay = 3000; // 3 seconds
this.lastUpdateTime = null;
// DOM elements
this.errorDisplay = document.getElementById('error-display');
this.errorMessage = document.getElementById('error-message');
this.waitingForActivity = document.getElementById('waiting-for-activity');
// Game elements
this.currentGame = document.getElementById('current-game');
this.gameIcon = document.getElementById('game-icon');
this.gameTitle = document.getElementById('game-title');
this.consoleName = document.getElementById('console-name');
// Progress bar elements
this.gameProgressSection = document.getElementById('game-progress-section');
this.progressText = document.getElementById('progress-text');
this.progressPercentage = document.getElementById('progress-percentage');
this.progressBar = document.getElementById('progress-bar');
// Achievements elements
this.achievementsContainer = document.getElementById('achievements-container');
this.achievementsList = document.getElementById('achievements-list');
this.init();
}
init() {
// Extract username from URL path
const pathParts = window.location.pathname.split('/');
this.username = decodeURIComponent(pathParts[pathParts.length - 1]);
if (!this.username || this.username === 'overlay') {
this.showError('No username specified in URL');
return;
}
console.log(`Initializing RetroPulse overlay for user: ${this.username}`);
this.connect();
}
connect() {
try {
// Determine WebSocket URL (ws:// for http, wss:// for https)
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws?user=${encodeURIComponent(this.username)}`;
console.log(`Connecting to WebSocket: ${wsUrl}`);
this.ws = new WebSocket(wsUrl);
this.ws.onopen = () => {
console.log('WebSocket connected');
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
}
};
this.ws.onmessage = (event) => {
try {
const message = JSON.parse(event.data);
this.handleMessage(message);
} catch (error) {
console.error('Error parsing WebSocket message:', error);
}
};
this.ws.onclose = (event) => {
console.log('WebSocket disconnected:', event.code, event.reason);
if (event.code !== 1000) { // Don't reconnect on clean close
this.scheduleReconnect();
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
} catch (error) {
console.error('Error creating WebSocket connection:', error);
this.showError(`Connection failed: ${error.message}`);
}
}
scheduleReconnect() {
if (this.reconnectInterval) return; // Reconnect already scheduled
console.log(`Scheduling reconnect in ${this.reconnectDelay / 1000} seconds...`);
this.reconnectInterval = setInterval(() => {
console.log('Reconnecting...');
this.connect();
}, this.reconnectDelay);
}
handleMessage(message) {
console.log('Received message:', message);
if (message.error) {
this.showError(message.error);
return;
}
if (message.type === 'initial' || message.type === 'update') {
this.updateOverlay(message.data);
}
}
updateOverlay(data) {
if (!data) return;
console.log('Updating overlay with data:', data);
this.lastUpdateTime = new Date();
this.errorDisplay.classList.add('hidden');
const hasGameProgress = data.gameProgress !== null;
const hasAchievements = data.recentAchievements && data.recentAchievements.length > 0;
if (hasGameProgress || hasAchievements) {
// We have data, so hide the waiting message
this.waitingForActivity.classList.add('hidden');
// Update Game Info and Progress Bar
if (hasGameProgress) {
this.currentGame.classList.remove('hidden');
this.updateGameInfo(data.gameProgress);
} else {
this.currentGame.classList.add('hidden');
}
// Update achievements list
if (hasAchievements) {
this.achievementsContainer.classList.remove('hidden');
this.updateAchievements(data.recentAchievements);
} else {
this.achievementsContainer.classList.add('hidden');
}
} else {
// No data to display, show the waiting message
this.waitingForActivity.classList.remove('hidden');
this.currentGame.classList.add('hidden');
this.achievementsContainer.classList.add('hidden');
}
}
updateGameInfo(gameData) {
this.currentGame.classList.remove('hidden');
this.gameTitle.textContent = gameData.title;
this.consoleName.textContent = gameData.consoleName || '';
if (gameData.icon) {
this.gameIcon.src = gameData.icon;
this.gameIcon.style.display = 'block';
} else {
this.gameIcon.style.display = 'none';
}
// Update progress bar
if (gameData.hasOwnProperty('completion') && gameData.totalAchievements > 0) {
this.progressText.textContent = `Completion: ${gameData.numAwarded} / ${gameData.totalAchievements}`;
this.progressPercentage.textContent = gameData.completion;
this.progressBar.style.width = gameData.completion;
this.gameProgressSection.classList.remove('hidden');
} else {
this.gameProgressSection.classList.add('hidden');
}
}
updateAchievements(achievements) {
// To avoid flickering, only update if content has changed.
// A simple check on the first achievement ID can work.
const firstExisting = this.achievementsList.querySelector('.achievement-item');
if (firstExisting && firstExisting.dataset.id == achievements[0].id) {
console.log('Achievements are already up-to-date.');
return;
}
console.log('Updating achievements list.');
this.achievementsList.innerHTML = ''; // Clear existing
achievements.forEach(ach => {
const item = document.createElement('li');
item.className = 'achievement-item';
item.dataset.id = ach.id; // For update checking
item.innerHTML = `
<div class="achievement-badge">
<img src="${ach.badgeName || ach.gameIcon || ''}" alt="Achievement Badge" style="width: 100%; height: 100%; object-fit: cover;">
</div>
<div class="achievement-details">
<div class="achievement-title">${ach.title}</div>
<div class="achievement-description">${ach.description}</div>
<div class="achievement-meta">
<span class="achievement-points">${ach.points}pts</span>
</div>
</div>
`;
this.achievementsList.appendChild(item);
});
}
showError(message) {
this.errorMessage.textContent = message;
this.errorDisplay.classList.remove('hidden');
this.waitingForActivity.classList.add('hidden');
this.currentGame.classList.add('hidden');
this.achievementsContainer.classList.add('hidden');
}
}
// Initialize the overlay when DOM is loaded
document.addEventListener('DOMContentLoaded', () => {
new RetroPulseOverlay();
});