Files
spore-ledlab/public/scripts/ledlab-app.js
2025-10-11 21:56:22 +02:00

235 lines
7.1 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 Node Canvas Grid component (new multi-canvas view)
const gridSection = document.querySelector('.matrix-grid-section');
if (gridSection) {
this.nodeCanvasGrid = new NodeCanvasGrid(gridSection, this.viewModel, this.eventBus);
this.nodeCanvasGrid.mount();
}
// Initialize Preset Controls component (now works with floating controls)
const floatingControls = document.querySelector('#floating-controls');
if (floatingControls) {
this.presetControls = new PresetControls(floatingControls, this.viewModel, this.eventBus);
this.presetControls.mount();
}
// Keep old Matrix Display component for backwards compatibility with settings view
const matrixContainer = document.querySelector('.matrix-section');
if (matrixContainer) {
this.matrixDisplay = new MatrixDisplay(matrixContainer, this.viewModel, this.eventBus);
this.matrixDisplay.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('selectNode', (data) => {
this.sendWebSocketMessage({
type: 'selectNode',
...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 });
}
}
// 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;
}