feat: ledlab
This commit is contained in:
245
public/scripts/ledlab-app.js
Normal file
245
public/scripts/ledlab-app.js
Normal file
@@ -0,0 +1,245 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user