// WiFi DensePose Application - Main Entry Point import { TabManager } from './components/TabManager.js'; import { DashboardTab } from './components/DashboardTab.js'; import { HardwareTab } from './components/HardwareTab.js'; import { LiveDemoTab } from './components/LiveDemoTab.js'; import { SensingTab } from './components/SensingTab.js'; import { apiService } from './services/api.service.js'; import { wsService } from './services/websocket.service.js'; import { healthService } from './services/health.service.js'; import { sensingService } from './services/sensing.service.js'; import { backendDetector } from './utils/backend-detector.js'; import { KeyboardShortcuts } from './utils/keyboard-shortcuts.js'; import { PerfMonitor } from './utils/perf-monitor.js'; import { toastManager } from './utils/toast.js'; import { ThemeToggle } from './utils/theme-toggle.js'; import { CommandPalette } from './utils/command-palette.js'; import { ActivityLog } from './utils/activity-log.js'; import { DataExport } from './utils/data-export.js'; import { FullscreenManager } from './utils/fullscreen.js'; import { ConnectionStatus } from './utils/connection-status.js'; import { MobileNav } from './utils/mobile-nav.js'; import { Router } from './utils/router.js'; import { Onboarding } from './utils/onboarding.js'; import { IdleManager } from './utils/idle-manager.js'; import { NotificationCenter } from './utils/notification-center.js'; import { i18n } from './utils/i18n.js'; import { ScreenshotTool } from './utils/screenshot.js'; import { UptimeClock } from './utils/uptime-clock.js'; import { QuickSettings } from './utils/quick-settings.js'; class WiFiDensePoseApp { constructor() { this.components = {}; this.isInitialized = false; } // Initialize application async init() { try { console.log('Initializing WiFi DensePose UI...'); // Set up error handling this.setupErrorHandling(); // Initialize services await this.initializeServices(); // Initialize UI components this.initializeComponents(); // Initialize enhancements this.initializeEnhancements(); // Set up global event listeners this.setupEventListeners(); this.isInitialized = true; console.log('WiFi DensePose UI initialized successfully'); } catch (error) { console.error('Failed to initialize application:', error); this.showGlobalError('Failed to initialize application. Please refresh the page.'); } } // Initialize services async initializeServices() { // Add request interceptor for error handling apiService.addResponseInterceptor(async (response, url) => { if (!response.ok && response.status === 401) { console.warn('Authentication required for:', url); // Handle authentication if needed } return response; }); // Detect backend availability and initialize accordingly const useMock = await backendDetector.shouldUseMockServer(); if (useMock) { console.log('๐Ÿงช Initializing with mock server for testing'); // Import and start mock server only when needed const { mockServer } = await import('./utils/mock-server.js'); mockServer.start(); // Show notification to user this.showBackendStatus('Mock server active - testing mode', 'warning'); } else { console.log('๐Ÿ”Œ Connecting to backend...'); try { const health = await healthService.checkLiveness(); console.log('โœ… Backend responding:', health); this.showBackendStatus('Connected to Rust sensing server', 'success'); } catch (error) { console.warn('โš ๏ธ Backend not available:', error.message); this.showBackendStatus('Backend unavailable โ€” start sensing-server', 'warning'); } // Start the sensing WebSocket service early so the dashboard and // live-demo tabs can show the correct data-source status immediately. sensingService.start(); } } // Initialize UI components initializeComponents() { const container = document.querySelector('.container'); if (!container) { throw new Error('Main container not found'); } // Initialize tab manager this.components.tabManager = new TabManager(container); this.components.tabManager.init(); // Initialize tab components this.initializeTabComponents(); // Set up tab change handling this.components.tabManager.onTabChange((newTab, oldTab) => { this.handleTabChange(newTab, oldTab); }); } // Initialize individual tab components initializeTabComponents() { // Dashboard tab const dashboardContainer = document.getElementById('dashboard'); if (dashboardContainer) { this.components.dashboard = new DashboardTab(dashboardContainer); this.components.dashboard.init().catch(error => { console.error('Failed to initialize dashboard:', error); }); } // Hardware tab const hardwareContainer = document.getElementById('hardware'); if (hardwareContainer) { this.components.hardware = new HardwareTab(hardwareContainer); this.components.hardware.init(); } // Live demo tab const demoContainer = document.getElementById('demo'); if (demoContainer) { this.components.demo = new LiveDemoTab(demoContainer); this.components.demo.init(); } // Sensing tab const sensingContainer = document.getElementById('sensing'); if (sensingContainer) { this.components.sensing = new SensingTab(sensingContainer); } // Training tab - lazy load to avoid breaking other tabs if import fails this.initTrainingTab(); // Architecture tab - static content, no component needed // Performance tab - static content, no component needed // Applications tab - static content, no component needed } // Lazy-load Training tab panels (dynamic import so failures don't break other tabs) async initTrainingTab() { try { const [{ default: TrainingPanel }, { default: ModelPanel }] = await Promise.all([ import('./components/TrainingPanel.js'), import('./components/ModelPanel.js') ]); const trainingContainer = document.getElementById('training-panel-container'); if (trainingContainer) { this.components.trainingPanel = new TrainingPanel(trainingContainer); } const modelContainer = document.getElementById('model-panel-container'); if (modelContainer) { this.components.modelPanel = new ModelPanel(modelContainer); } } catch (error) { console.error('Failed to load Training tab components:', error); } } // Initialize enhancement modules initializeEnhancements() { // Toast notifications toastManager.init(); // Connection status widget in header this.connectionStatus = new ConnectionStatus(); this.connectionStatus.init(); // Theme toggle this.themeToggle = new ThemeToggle(); this.themeToggle.init(); // Performance monitor this.perfMonitor = new PerfMonitor(); this.perfMonitor.init(); // Activity log this.activityLog = new ActivityLog(); this.activityLog.init(); // Data export this.dataExport = new DataExport(); this.dataExport.init(); // Fullscreen manager this.fullscreenManager = new FullscreenManager(); this.fullscreenManager.init(); // Command palette (Ctrl+K) this.commandPalette = new CommandPalette(this); this.commandPalette.init(); // Mobile navigation (hamburger menu for small screens) this.mobileNav = new MobileNav(); this.mobileNav.init(); // Notification center (bell icon in header) this.notificationCenter = new NotificationCenter(); this.notificationCenter.init(); // Screenshot tool this.screenshotTool = new ScreenshotTool(); this.screenshotTool.init(); // Uptime clock this.uptimeClock = new UptimeClock(); this.uptimeClock.init(); // Quick settings panel this.quickSettings = new QuickSettings(this); this.quickSettings.init(); // Internationalization (EN/PL) i18n.init(); // Keyboard shortcuts (pass app reference for tab switching) this.keyboardShortcuts = new KeyboardShortcuts(this); this.keyboardShortcuts.register('l', 'Toggle activity log', () => { document.dispatchEvent(new CustomEvent('toggle-activity-log')); }); this.keyboardShortcuts.register('e', 'Export sensor data', () => { document.dispatchEvent(new CustomEvent('export-data')); }); this.keyboardShortcuts.register('f', 'Toggle fullscreen', () => { document.dispatchEvent(new CustomEvent('toggle-fullscreen')); }); this.keyboardShortcuts.register('s', 'Take screenshot', () => { document.dispatchEvent(new CustomEvent('take-screenshot')); }); this.keyboardShortcuts.init(); // Listen for show-shortcuts from command palette document.addEventListener('show-shortcuts', () => { this.keyboardShortcuts.showHelp(); }); // Register PWA service worker this.registerServiceWorker(); // URL hash router (bookmarkable tabs) this.router = new Router(this); this.router.init(); // Idle detection (pause updates when inactive) this.idleManager = new IdleManager(); this.idleManager.onIdle(() => { healthService.stopHealthMonitoring(); console.info('[App] Paused health monitoring (idle)'); }); this.idleManager.onActive(() => { healthService.startHealthMonitoring(); console.info('[App] Resumed health monitoring (active)'); }); this.idleManager.init(); // Onboarding tour (first-run walkthrough) this.onboarding = new Onboarding(this); this.onboarding.init(); } // Register service worker for offline capability registerServiceWorker() { if ('serviceWorker' in navigator) { navigator.serviceWorker.register('./sw.js').then(reg => { console.info('Service worker registered:', reg.scope); }).catch(err => { console.warn('Service worker registration failed:', err); }); } } // Handle tab changes handleTabChange(newTab, oldTab) { console.log(`Tab changed from ${oldTab} to ${newTab}`); // Stop demo if leaving demo tab if (oldTab === 'demo' && this.components.demo) { this.components.demo.stopDemo(); } // Update components based on active tab switch (newTab) { case 'dashboard': // Dashboard auto-updates when visible break; case 'hardware': // Hardware visualization is always active break; case 'demo': // Demo starts manually break; case 'sensing': // Lazy-init sensing tab on first visit if (this.components.sensing && !this.components.sensing.splatRenderer) { this.components.sensing.init().catch(error => { console.error('Failed to initialize sensing tab:', error); }); } break; case 'training': // Refresh panels when training tab becomes visible if (this.components.trainingPanel && typeof this.components.trainingPanel.refresh === 'function') { this.components.trainingPanel.refresh(); } if (this.components.modelPanel && typeof this.components.modelPanel.refresh === 'function') { this.components.modelPanel.refresh(); } break; } } // Set up global event listeners setupEventListeners() { // Handle window resize window.addEventListener('resize', () => { this.handleResize(); }); // Handle visibility change document.addEventListener('visibilitychange', () => { this.handleVisibilityChange(); }); // Handle before unload window.addEventListener('beforeunload', () => { this.cleanup(); }); } // Handle window resize handleResize() { // Update canvas sizes if needed const canvases = document.querySelectorAll('canvas'); canvases.forEach(canvas => { const rect = canvas.parentElement.getBoundingClientRect(); if (canvas.width !== rect.width || canvas.height !== rect.height) { canvas.width = rect.width; canvas.height = rect.height; } }); } // Handle visibility change handleVisibilityChange() { if (document.hidden) { // Pause updates when page is hidden console.log('Page hidden, pausing updates'); healthService.stopHealthMonitoring(); } else { // Resume updates when page is visible console.log('Page visible, resuming updates'); healthService.startHealthMonitoring(); } } // Set up error handling setupErrorHandling() { window.addEventListener('error', (event) => { if (event.error) { console.error('Global error:', event.error); this.showGlobalError('An unexpected error occurred'); } }); window.addEventListener('unhandledrejection', (event) => { if (event.reason) { console.error('Unhandled promise rejection:', event.reason); this.showGlobalError('An unexpected error occurred'); } }); } // Show backend status notification (uses enhanced toast system) showBackendStatus(message, type) { const toastType = type === 'success' ? 'success' : 'warning'; toastManager[toastType](message, { duration: type === 'success' ? 3000 : 8000 }); } // Show global error message (uses enhanced toast system) showGlobalError(message) { toastManager.error(message, { duration: 6000 }); } // Clean up resources cleanup() { console.log('Cleaning up application resources...'); // Dispose all components Object.values(this.components).forEach(component => { if (component && typeof component.dispose === 'function') { component.dispose(); } }); // Disconnect all WebSocket connections wsService.disconnectAll(); // Stop health monitoring healthService.dispose(); // Dispose enhancements if (this.keyboardShortcuts) this.keyboardShortcuts.dispose(); if (this.perfMonitor) this.perfMonitor.dispose(); if (this.themeToggle) this.themeToggle.dispose(); if (this.commandPalette) this.commandPalette.dispose(); if (this.activityLog) this.activityLog.dispose(); if (this.dataExport) this.dataExport.dispose(); if (this.fullscreenManager) this.fullscreenManager.dispose(); if (this.connectionStatus) this.connectionStatus.dispose(); if (this.mobileNav) this.mobileNav.dispose(); if (this.router) this.router.dispose(); if (this.onboarding) this.onboarding.dispose(); if (this.idleManager) this.idleManager.dispose(); if (this.notificationCenter) this.notificationCenter.dispose(); if (this.screenshotTool) this.screenshotTool.dispose(); if (this.uptimeClock) this.uptimeClock.dispose(); if (this.quickSettings) this.quickSettings.dispose(); i18n.dispose(); toastManager.dispose(); } // Public API getComponent(name) { return this.components[name]; } isReady() { return this.isInitialized; } } // Initialize app when DOM is ready document.addEventListener('DOMContentLoaded', () => { window.wifiDensePoseApp = new WiFiDensePoseApp(); window.wifiDensePoseApp.init(); }); // Export for testing export { WiFiDensePoseApp };