feat: improve UI

This commit is contained in:
2025-10-11 18:25:00 +02:00
parent 30814807aa
commit 294d86f24b
6 changed files with 842 additions and 155 deletions

View File

@@ -201,6 +201,13 @@ class LEDLabApp {
});
});
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);

View File

@@ -0,0 +1,139 @@
// Navigation Manager for SPORE LEDLab
class NavigationManager {
constructor() {
this.currentView = 'stream';
this.init();
}
init() {
this.setupNavigationListeners();
// Sync settings values on init
this.syncSettingsValues();
}
setupNavigationListeners() {
// Navigation tab listeners
const navTabs = document.querySelectorAll('.nav-tab');
navTabs.forEach(tab => {
tab.addEventListener('click', () => {
const view = tab.dataset.view;
this.switchView(view);
});
});
// Settings apply button
const settingsApplyBtn = document.getElementById('settings-apply-matrix-btn');
if (settingsApplyBtn) {
settingsApplyBtn.addEventListener('click', () => {
this.applySettingsFromView();
});
}
// Sync settings when stream view matrix config changes
const streamWidthInput = document.getElementById('matrix-width');
const streamHeightInput = document.getElementById('matrix-height');
if (streamWidthInput) {
streamWidthInput.addEventListener('change', () => this.syncSettingsValues());
}
if (streamHeightInput) {
streamHeightInput.addEventListener('change', () => this.syncSettingsValues());
}
}
switchView(viewName) {
// Hide all views
const allViews = document.querySelectorAll('.view-content');
allViews.forEach(view => view.classList.remove('active'));
// Deactivate all tabs
const allTabs = document.querySelectorAll('.nav-tab');
allTabs.forEach(tab => tab.classList.remove('active'));
// Show selected view
const targetView = document.getElementById(`${viewName}-view`);
if (targetView) {
targetView.classList.add('active');
}
// Activate selected tab
const targetTab = document.querySelector(`.nav-tab[data-view="${viewName}"]`);
if (targetTab) {
targetTab.classList.add('active');
}
this.currentView = viewName;
// Sync values when switching to settings
if (viewName === 'settings') {
this.syncSettingsValues();
}
console.log(`Switched to ${viewName} view`);
}
syncSettingsValues() {
// Sync matrix configuration values from stream to settings
const streamWidth = document.getElementById('matrix-width');
const streamHeight = document.getElementById('matrix-height');
const settingsWidth = document.getElementById('settings-matrix-width');
const settingsHeight = document.getElementById('settings-matrix-height');
if (streamWidth && settingsWidth) {
settingsWidth.value = streamWidth.value;
}
if (streamHeight && settingsHeight) {
settingsHeight.value = streamHeight.value;
}
}
applySettingsFromView() {
const settingsWidth = document.getElementById('settings-matrix-width');
const settingsHeight = document.getElementById('settings-matrix-height');
const streamWidth = document.getElementById('matrix-width');
const streamHeight = document.getElementById('matrix-height');
if (settingsWidth && streamWidth) {
streamWidth.value = settingsWidth.value;
}
if (settingsHeight && streamHeight) {
streamHeight.value = settingsHeight.value;
}
// Trigger the apply button in the stream view
const applyBtn = document.getElementById('apply-matrix-btn');
if (applyBtn) {
applyBtn.click();
}
// Show success feedback
const btn = document.getElementById('settings-apply-matrix-btn');
if (btn) {
const originalText = btn.textContent;
btn.textContent = '✓ Applied!';
btn.style.background = 'linear-gradient(135deg, var(--accent-success) 0%, #22c55e 100%)';
setTimeout(() => {
btn.textContent = originalText;
btn.style.background = '';
}, 2000);
}
}
getCurrentView() {
return this.currentView;
}
}
// Initialize navigation when DOM is loaded
document.addEventListener('DOMContentLoaded', function() {
window.navigationManager = new NavigationManager();
console.log('Navigation manager initialized');
});
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = NavigationManager;
}

View File

@@ -16,11 +16,29 @@ class PresetControls extends Component {
}
setupEventListeners() {
// Preset selection
// FPS slider
const fpsSlider = this.findElement('#fps-slider');
const fpsValue = this.findElement('#fps-value');
if (fpsSlider && fpsValue) {
this.addEventListener(fpsSlider, 'input', (e) => {
const fps = parseInt(e.target.value);
fpsValue.textContent = fps;
this.updateFrameRate(fps);
});
}
// Preset selection - immediate switching
const presetSelect = this.findElement('#preset-select');
if (presetSelect) {
this.addEventListener(presetSelect, 'change', (e) => {
this.selectPreset(e.target.value);
const presetName = e.target.value;
this.selectPreset(presetName);
// If currently streaming, automatically restart with new preset
const toggleBtn = this.findElement('#toggle-stream-btn');
if (toggleBtn && toggleBtn.dataset.streaming === 'true' && presetName) {
this.startStreaming();
}
});
}
@@ -32,18 +50,16 @@ class PresetControls extends Component {
});
}
// Start/Stop buttons
const startBtn = this.findElement('#start-btn');
if (startBtn) {
this.addEventListener(startBtn, 'click', () => {
this.startStreaming();
});
}
const stopBtn = this.findElement('#stop-btn');
if (stopBtn) {
this.addEventListener(stopBtn, 'click', () => {
this.stopStreaming();
// Toggle stream button
const toggleStreamBtn = this.findElement('#toggle-stream-btn');
if (toggleStreamBtn) {
this.addEventListener(toggleStreamBtn, 'click', () => {
const isStreaming = toggleStreamBtn.dataset.streaming === 'true';
if (isStreaming) {
this.stopStreaming();
} else {
this.startStreaming();
}
});
}
@@ -73,7 +89,21 @@ class PresetControls extends Component {
});
this.subscribeToEvent('presetParameterUpdated', (data) => {
this.updatePresetParameter(data.parameter, data.value);
// Update control display without triggering another update
const control = this.presetControls.get(data.parameter);
if (control) {
if (control.type === 'range') {
control.value = data.value;
const valueDisplay = control.parentElement.querySelector('.preset-value');
if (valueDisplay) {
valueDisplay.textContent = parseFloat(data.value).toFixed(2);
}
} else if (control.type === 'color') {
control.value = this.hexToColorValue(data.value);
} else {
control.value = data.value;
}
}
});
@@ -82,15 +112,28 @@ class PresetControls extends Component {
const isStreaming = data.data.streaming;
const currentPreset = data.data.currentPreset;
const presetParameters = data.data.presetParameters;
const fps = data.data.fps;
this.updateStreamingState(isStreaming, currentPreset ? { name: currentPreset } : null);
if (currentPreset) {
this.selectPreset(currentPreset.toLowerCase().replace('-preset', ''));
// Update FPS display
if (fps !== undefined) {
const fpsSlider = this.findElement('#fps-slider');
const fpsValue = this.findElement('#fps-value');
if (fpsSlider && fpsValue) {
fpsSlider.value = fps;
fpsValue.textContent = fps;
}
}
// Only select preset if it's different from current (avoid recreating controls)
const presetSelect = this.findElement('#preset-select');
if (currentPreset && presetSelect && presetSelect.value !== currentPreset) {
this.selectPreset(currentPreset);
}
if (presetParameters && this.currentPreset) {
// Update parameter controls with current values
// Update parameter controls with current values without triggering events
Object.entries(presetParameters).forEach(([param, value]) => {
const control = this.presetControls.get(param);
if (control) {
@@ -109,6 +152,15 @@ class PresetControls extends Component {
});
}
});
this.subscribeToEvent('frameRateUpdated', (data) => {
const fpsSlider = this.findElement('#fps-slider');
const fpsValue = this.findElement('#fps-value');
if (fpsSlider && fpsValue && data.fps) {
fpsSlider.value = data.fps;
fpsValue.textContent = data.fps;
}
});
}
async loadPresets() {
@@ -201,8 +253,15 @@ class PresetControls extends Component {
valueDisplay.textContent = defaultValue;
sliderInput.addEventListener('input', (e) => {
valueDisplay.textContent = parseFloat(e.target.value).toFixed(2);
this.updatePresetParameter(paramName, parseFloat(e.target.value));
const value = parseFloat(e.target.value);
valueDisplay.textContent = value.toFixed(2);
this.updatePresetParameter(paramName, value);
// Visual feedback for real-time update
valueDisplay.style.color = 'var(--accent-primary)';
setTimeout(() => {
valueDisplay.style.color = '';
}, 200);
});
const container = document.createElement('div');
@@ -223,6 +282,12 @@ class PresetControls extends Component {
colorInput.addEventListener('input', (e) => {
const hexValue = this.colorValueToHex(e.target.value);
this.updatePresetParameter(paramName, hexValue);
// Visual feedback for real-time update
colorInput.style.borderColor = 'var(--accent-primary)';
setTimeout(() => {
colorInput.style.borderColor = '';
}, 200);
});
return colorInput;
@@ -235,6 +300,12 @@ class PresetControls extends Component {
textInput.addEventListener('input', (e) => {
this.updatePresetParameter(paramName, e.target.value);
// Visual feedback for real-time update
textInput.style.borderColor = 'var(--accent-primary)';
setTimeout(() => {
textInput.style.borderColor = '';
}, 200);
});
return textInput;
@@ -242,11 +313,13 @@ class PresetControls extends Component {
}
updatePresetParameter(parameter, value) {
// Send parameter update to server
// Send parameter update to server immediately (real-time)
this.viewModel.publish('updatePresetParameter', {
parameter,
value
});
console.log(`Parameter updated: ${parameter} = ${value}`);
}
clearPresetControls() {
@@ -353,17 +426,30 @@ class PresetControls extends Component {
}
updateStreamingState(isStreaming, preset) {
const startBtn = this.findElement('#start-btn');
const stopBtn = this.findElement('#stop-btn');
const toggleBtn = this.findElement('#toggle-stream-btn');
const btnIcon = toggleBtn?.querySelector('.btn-icon');
const btnText = toggleBtn?.querySelector('.btn-text');
if (isStreaming) {
startBtn.disabled = true;
stopBtn.disabled = false;
} else {
startBtn.disabled = false;
stopBtn.disabled = true;
if (toggleBtn) {
toggleBtn.dataset.streaming = isStreaming ? 'true' : 'false';
if (isStreaming) {
if (btnIcon) btnIcon.textContent = '⏸';
if (btnText) btnText.textContent = 'Stop Streaming';
toggleBtn.classList.remove('btn-primary');
toggleBtn.classList.add('btn-stop');
} else {
if (btnIcon) btnIcon.textContent = '▶';
if (btnText) btnText.textContent = 'Start Streaming';
toggleBtn.classList.remove('btn-stop');
toggleBtn.classList.add('btn-primary');
}
}
}
updateFrameRate(fps) {
this.viewModel.publish('updateFrameRate', { fps });
}
}
// Export for use in other modules