// Main Application File for SnowWorld Client Display // Application configuration const AppConfig = { SERVER_URL: 'http://localhost:3000', API_BASE_URL: 'http://localhost:3000/api', DEFAULT_ZONE: 'reception', REFRESH_INTERVAL: 60000, // 1 minute ERROR_RETRY_DELAY: 5000, // 5 seconds MAX_ERROR_RETRIES: 3, LOADING_TIMEOUT: 10000, // 10 seconds WEATHER_UPDATE_INTERVAL: 300000, // 5 minutes TIME_UPDATE_INTERVAL: 1000, // 1 second CONTENT_PRELOAD_TIME: 2000, // 2 seconds before content expires SNOW_ANIMATION_COUNT: 8 }; // Main Application Class class SnowWorldClientApp { constructor() { this.config = AppConfig; this.isInitialized = false; this.zone = this.getZoneFromURL() || this.config.DEFAULT_ZONE; this.errorCount = 0; this.loadingTimeout = null; this.init(); } async init() { try { console.log('๐ฟ Initializing SnowWorld Client Display...'); console.log(`๐ Zone: ${this.zone}`); // Show loading screen this.showLoadingScreen(); // Wait for dependencies await this.waitForDependencies(); // Initialize components this.setupGlobalErrorHandling(); this.setupKeyboardShortcuts(); this.setupEventListeners(); // Initialize managers await this.initializeManagers(); // Start application this.startApplication(); this.isInitialized = true; console.log('โ SnowWorld Client Display initialized successfully'); } catch (error) { console.error('โ Failed to initialize application:', error); this.handleInitializationError(error); } } async waitForDependencies() { const maxWaitTime = 15000; // 15 seconds const checkInterval = 200; // 200ms let elapsedTime = 0; return new Promise((resolve, reject) => { const checkDependencies = () => { const required = [ window.displayManager, window.connectionManager, window.weatherManager ]; if (required.every(dep => dep)) { resolve(); } else if (elapsedTime >= maxWaitTime) { const missing = required.filter(dep => !dep).map((_, i) => ['displayManager', 'connectionManager', 'weatherManager'][i] ); reject(new Error(`Dependencies timeout - missing: ${missing.join(', ')}`)); } else { elapsedTime += checkInterval; setTimeout(checkDependencies, checkInterval); } }; checkDependencies(); }); } setupGlobalErrorHandling() { // Handle JavaScript errors window.addEventListener('error', (event) => { console.error('Global error:', event.error); this.handleError(event.error); }); // Handle unhandled promise rejections window.addEventListener('unhandledrejection', (event) => { console.error('Unhandled promise rejection:', event.reason); this.handleError(event.reason); }); // Handle network errors window.addEventListener('offline', () => { console.warn('Network offline detected'); this.handleNetworkError('offline'); }); window.addEventListener('online', () => { console.log('Network online detected'); this.handleNetworkError('online'); }); } setupKeyboardShortcuts() { document.addEventListener('keydown', (e) => { // Prevent default for F keys to avoid browser interference if (e.key.startsWith('F')) { e.preventDefault(); } switch (e.key) { case 'F1': // Show help/info this.showSystemInfo(); break; case 'F5': // Refresh content e.preventDefault(); this.refreshContent(); break; case 'F11': // Toggle fullscreen (handled by browser) break; case 'Escape': // Exit fullscreen or show zone selector e.preventDefault(); this.showZoneSelector(); break; case 'r': if (e.ctrlKey) { e.preventDefault(); this.refreshContent(); } break; case 'z': if (e.ctrlKey) { e.preventDefault(); this.showZoneSelector(); } break; } }); } setupEventListeners() { // Handle visibility changes document.addEventListener('visibilitychange', () => { if (document.hidden) { console.log('๐ฑ Tab hidden - pausing updates'); this.pauseUpdates(); } else { console.log('๐ฑ Tab visible - resuming updates'); this.resumeUpdates(); } }); // Handle window focus/blur for better performance window.addEventListener('blur', () => { console.log('๐ช Window blurred - reducing update frequency'); this.reduceUpdateFrequency(); }); window.addEventListener('focus', () => { console.log('๐ช Window focused - restoring update frequency'); this.restoreUpdateFrequency(); }); // Handle beforeunload window.addEventListener('beforeunload', () => { this.cleanup(); }); // Handle resize for responsive adjustments window.addEventListener('resize', () => { this.handleResize(); }); } async initializeManagers() { console.log('๐ง Initializing managers...'); // Set up inter-manager communication if (window.connectionManager) { window.connectionManager.zone = this.zone; } if (window.displayManager) { window.displayManager.zone = this.zone; window.displayManager.updateZoneDisplay(); } console.log('โ Managers initialized'); } startApplication() { console.log('๐ Starting application...'); // Hide loading screen after a short delay this.loadingTimeout = setTimeout(() => { this.hideLoadingScreen(); }, 2000); // Request initial content this.requestInitialContent(); // Start periodic refresh this.startPeriodicRefresh(); console.log('๐ฏ Application started successfully'); } showLoadingScreen() { const loadingScreen = document.getElementById('loadingScreen'); if (loadingScreen) { loadingScreen.classList.add('active'); loadingScreen.style.display = 'flex'; // Simulate loading progress this.simulateLoadingProgress(); } } hideLoadingScreen() { if (this.loadingTimeout) { clearTimeout(this.loadingTimeout); } const loadingScreen = document.getElementById('loadingScreen'); if (loadingScreen) { loadingScreen.classList.add('hidden'); setTimeout(() => { loadingScreen.style.display = 'none'; }, 500); } } simulateLoadingProgress() { const progressBar = document.querySelector('.loading-progress'); if (!progressBar) return; let progress = 0; const interval = setInterval(() => { progress += Math.random() * 15; if (progress >= 90) { progress = 90; clearInterval(interval); } progressBar.style.width = `${progress}%`; }, 200); // Complete progress when ready setTimeout(() => { clearInterval(interval); progressBar.style.width = '100%'; }, 1500); } requestInitialContent() { console.log(`๐บ Requesting initial content for zone: ${this.zone}`); if (window.connectionManager) { window.connectionManager.requestContentForZone(this.zone); } else { // Fallback: show placeholder if (window.displayManager) { window.displayManager.showPlaceholder(); } } } startPeriodicRefresh() { // Refresh content every minute setInterval(() => { this.refreshContent(); }, this.config.REFRESH_INTERVAL); console.log(`๐ Periodic refresh started with interval: ${this.config.REFRESH_INTERVAL}ms`); } refreshContent() { console.log('๐ Refreshing content...'); if (window.connectionManager) { window.connectionManager.requestContentForZone(this.zone); } } showSystemInfo() { const status = { zone: this.zone, connection: window.connectionManager?.getStatus() || 'Not available', display: window.displayManager?.getStatus() || 'Not available', weather: window.weatherManager?.getCurrentWeather() || 'Not available', timestamp: new Date().toISOString() }; console.log('๐ System Info:', status); // Could implement a visual system info overlay alert(`SnowWorld Display System Info:\n\n` + `Zone: ${status.zone}\n` + `Connection: ${status.connection.connected ? 'Connected' : 'Disconnected'}\n` + `Display: ${status.display.isPlaying ? 'Playing' : 'Stopped'}\n` + `Weather: ${window.weatherManager?.getWeatherSummary() || 'N/A'}\n` + `Time: ${new Date().toLocaleString('nl-NL')}`); } showZoneSelector() { const modal = document.getElementById('zoneModal'); if (modal) { this.populateZoneSelector(); modal.classList.add('active'); } } populateZoneSelector() { const optionsContainer = document.getElementById('zoneOptions'); if (!optionsContainer) return; const zones = [ { id: 'reception', name: 'Receptie', description: 'Hoofdingang en receptie', icon: 'fa-door-open' }, { id: 'restaurant', name: 'Restaurant', description: 'Eetgelegenheid', icon: 'fa-utensils' }, { id: 'skislope', name: 'Skibaan', description: 'Hoofdskibaan', icon: 'fa-skiing' }, { id: 'lockers', name: 'Kluisjes', description: 'Kleedkamers en kluisjes', icon: 'fa-locker' }, { id: 'shop', name: 'Winkel', description: 'Ski-uitrusting winkel', icon: 'fa-shopping-bag' } ]; optionsContainer.innerHTML = zones.map(zone => `
Het systeem kon niet worden geladen.
${error.message}