476 lines
15 KiB
JavaScript
476 lines
15 KiB
JavaScript
// 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 }; |