// LEDLab Main Application class LEDLabApp { constructor() { this.viewModel = new ViewModel(); this.eventBus = new EventBus(); this.ws = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectDelay = 1000; this.matrixDisplay = null; this.presetControls = null; this.nodeDiscovery = null; this.init(); } init() { // Set up event bus on view model this.viewModel.setEventBus(this.eventBus); // Initialize components this.initComponents(); // Set up WebSocket connection this.connectWebSocket(); // Set up global event listeners this.setupGlobalEventListeners(); console.log('LEDLab app initialized'); } initComponents() { // Initialize Matrix Display component const matrixContainer = document.querySelector('.matrix-section'); if (matrixContainer) { this.matrixDisplay = new MatrixDisplay(matrixContainer, this.viewModel, this.eventBus); this.matrixDisplay.mount(); } // Initialize Preset Controls component const controlsContainer = document.querySelector('.control-section'); if (controlsContainer) { this.presetControls = new PresetControls(controlsContainer, this.viewModel, this.eventBus); this.presetControls.mount(); } // Initialize Node Discovery component const nodeContainer = document.querySelector('#node-list').parentElement; if (nodeContainer) { this.nodeDiscovery = new NodeDiscovery(nodeContainer, this.viewModel, this.eventBus); this.nodeDiscovery.mount(); } } connectWebSocket() { const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${protocol}//${window.location.host}`; try { this.ws = new WebSocket(wsUrl); this.setupWebSocketEventHandlers(); } catch (error) { console.error('Failed to create WebSocket connection:', error); this.scheduleReconnect(); } } setupWebSocketEventHandlers() { if (!this.ws) return; this.ws.onopen = () => { console.log('WebSocket connected'); this.reconnectAttempts = 0; // Send any queued messages this.flushMessageQueue(); }; this.ws.onmessage = (event) => { try { const data = JSON.parse(event.data); this.handleWebSocketMessage(data); } 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) { // Not a normal closure this.scheduleReconnect(); } }; this.ws.onerror = (error) => { console.error('WebSocket error:', error); }; } handleWebSocketMessage(data) { // Publish event to the event bus for components to handle this.eventBus.publish(data.type, data); } scheduleReconnect() { if (this.reconnectAttempts >= this.maxReconnectAttempts) { console.error('Max reconnection attempts reached'); return; } this.reconnectAttempts++; const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1); // Exponential backoff console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`); setTimeout(() => { this.connectWebSocket(); }, delay); } sendWebSocketMessage(data) { if (this.ws && this.ws.readyState === WebSocket.OPEN) { this.ws.send(JSON.stringify(data)); } else { console.warn('WebSocket not connected, queuing message'); // Queue message for when connection is restored if (!this.messageQueue) { this.messageQueue = []; } this.messageQueue.push(data); } } flushMessageQueue() { if (this.messageQueue && this.messageQueue.length > 0) { console.log(`Flushing ${this.messageQueue.length} queued messages`); this.messageQueue.forEach(data => this.sendWebSocketMessage(data)); this.messageQueue = []; } } setupGlobalEventListeners() { // Listen for messages from components that need to be sent to server this.eventBus.subscribe('startPreset', (data) => { this.sendWebSocketMessage({ type: 'startPreset', ...data }); }); this.eventBus.subscribe('stopStreaming', (data) => { this.sendWebSocketMessage({ type: 'stopStreaming', ...data }); }); this.eventBus.subscribe('updatePresetParameter', (data) => { this.sendWebSocketMessage({ type: 'updatePresetParameter', ...data }); }); this.eventBus.subscribe('setMatrixSize', (data) => { this.sendWebSocketMessage({ type: 'setMatrixSize', ...data }); }); this.eventBus.subscribe('sendToNode', (data) => { this.sendWebSocketMessage({ type: 'sendToNode', ...data }); }); this.eventBus.subscribe('broadcastToAll', (data) => { this.sendWebSocketMessage({ type: 'broadcastToAll', ...data }); }); this.eventBus.subscribe('selectNode', (data) => { this.sendWebSocketMessage({ type: 'selectNode', ...data }); }); this.eventBus.subscribe('selectBroadcast', (data) => { this.sendWebSocketMessage({ type: 'selectBroadcast', ...data }); }); this.eventBus.subscribe('updateFrameRate', (data) => { this.sendWebSocketMessage({ type: 'updateFrameRate', ...data }); }); // Handle theme changes window.addEventListener('themeChanged', (event) => { console.log('Theme changed to:', event.detail.theme); // Update any theme-specific UI elements if needed }); } // Public API methods for external use startPreset(presetName, width, height) { this.viewModel.publish('startPreset', { presetName, width, height }); } stopStreaming() { this.viewModel.publish('stopStreaming', {}); } updatePresetParameter(parameter, value) { this.viewModel.publish('updatePresetParameter', { parameter, value }); } setMatrixSize(width, height) { this.viewModel.publish('setMatrixSize', { width, height }); } sendToNode(nodeIp, message) { this.viewModel.publish('sendToNode', { nodeIp, message }); } broadcastToAll(message) { this.viewModel.publish('broadcastToAll', { message }); } } // Initialize the app when DOM is loaded document.addEventListener('DOMContentLoaded', function() { window.ledlabApp = new LEDLabApp(); }); // Export for use in other modules if (typeof module !== 'undefined' && module.exports) { module.exports = LEDLabApp; }