Files
spore-ledlab/public/scripts/ledlab-app.js
2025-10-11 17:46:32 +02:00

246 lines
7.3 KiB
JavaScript

// 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
});
});
// 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;
}