373 lines
12 KiB
JavaScript
373 lines
12 KiB
JavaScript
// Preset Controls Component
|
|
|
|
class PresetControls extends Component {
|
|
constructor(container, viewModel, eventBus) {
|
|
super(container, viewModel, eventBus);
|
|
this.presets = {};
|
|
this.currentPreset = null;
|
|
this.presetControls = new Map();
|
|
}
|
|
|
|
mount() {
|
|
super.mount();
|
|
this.setupEventListeners();
|
|
this.setupViewModelListeners();
|
|
this.loadPresets();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Preset selection
|
|
const presetSelect = this.findElement('#preset-select');
|
|
if (presetSelect) {
|
|
this.addEventListener(presetSelect, 'change', (e) => {
|
|
this.selectPreset(e.target.value);
|
|
});
|
|
}
|
|
|
|
// Apply matrix config button
|
|
const applyMatrixBtn = this.findElement('#apply-matrix-btn');
|
|
if (applyMatrixBtn) {
|
|
this.addEventListener(applyMatrixBtn, 'click', () => {
|
|
this.applyMatrixConfig();
|
|
});
|
|
}
|
|
|
|
// 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();
|
|
});
|
|
}
|
|
|
|
// Test and clear buttons
|
|
const sendTestBtn = this.findElement('#send-test-btn');
|
|
if (sendTestBtn) {
|
|
this.addEventListener(sendTestBtn, 'click', () => {
|
|
this.sendTestFrame();
|
|
});
|
|
}
|
|
|
|
const clearMatrixBtn = this.findElement('#clear-matrix-btn');
|
|
if (clearMatrixBtn) {
|
|
this.addEventListener(clearMatrixBtn, 'click', () => {
|
|
this.clearMatrix();
|
|
});
|
|
}
|
|
}
|
|
|
|
setupViewModelListeners() {
|
|
this.subscribeToEvent('streamingStarted', (data) => {
|
|
this.updateStreamingState(true, data.preset);
|
|
});
|
|
|
|
this.subscribeToEvent('streamingStopped', () => {
|
|
this.updateStreamingState(false);
|
|
});
|
|
|
|
this.subscribeToEvent('presetParameterUpdated', (data) => {
|
|
this.updatePresetParameter(data.parameter, data.value);
|
|
});
|
|
|
|
|
|
this.subscribeToEvent('status', (data) => {
|
|
// Update UI to reflect current server state
|
|
const isStreaming = data.data.streaming;
|
|
const currentPreset = data.data.currentPreset;
|
|
const presetParameters = data.data.presetParameters;
|
|
|
|
this.updateStreamingState(isStreaming, currentPreset ? { name: currentPreset } : null);
|
|
|
|
if (currentPreset) {
|
|
this.selectPreset(currentPreset.toLowerCase().replace('-preset', ''));
|
|
}
|
|
|
|
if (presetParameters && this.currentPreset) {
|
|
// Update parameter controls with current values
|
|
Object.entries(presetParameters).forEach(([param, value]) => {
|
|
const control = this.presetControls.get(param);
|
|
if (control) {
|
|
if (control.type === 'range') {
|
|
control.value = value;
|
|
const valueDisplay = control.parentElement.querySelector('.preset-value');
|
|
if (valueDisplay) {
|
|
valueDisplay.textContent = parseFloat(value).toFixed(2);
|
|
}
|
|
} else if (control.type === 'color') {
|
|
control.value = this.hexToColorValue(value);
|
|
} else {
|
|
control.value = value;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
async loadPresets() {
|
|
try {
|
|
const response = await fetch('/api/presets');
|
|
const data = await response.json();
|
|
|
|
this.presets = data.presets;
|
|
this.populatePresetSelect();
|
|
|
|
} catch (error) {
|
|
console.error('Error loading presets:', error);
|
|
}
|
|
}
|
|
|
|
populatePresetSelect() {
|
|
const presetSelect = this.findElement('#preset-select');
|
|
if (!presetSelect) return;
|
|
|
|
// Clear existing options (except the first one)
|
|
while (presetSelect.children.length > 1) {
|
|
presetSelect.removeChild(presetSelect.lastChild);
|
|
}
|
|
|
|
// Add preset options
|
|
Object.entries(this.presets).forEach(([name, metadata]) => {
|
|
const option = document.createElement('option');
|
|
option.value = name;
|
|
option.textContent = metadata.name;
|
|
presetSelect.appendChild(option);
|
|
});
|
|
}
|
|
|
|
selectPreset(presetName) {
|
|
if (!presetName || !this.presets[presetName]) {
|
|
this.currentPreset = null;
|
|
this.clearPresetControls();
|
|
return;
|
|
}
|
|
|
|
this.currentPreset = this.presets[presetName];
|
|
this.createPresetControls();
|
|
}
|
|
|
|
createPresetControls() {
|
|
const controlsContainer = this.findElement('#preset-controls');
|
|
if (!controlsContainer) return;
|
|
|
|
// Clear existing controls
|
|
controlsContainer.innerHTML = '';
|
|
|
|
if (!this.currentPreset || !this.currentPreset.parameters) {
|
|
return;
|
|
}
|
|
|
|
// Create controls for each parameter
|
|
Object.entries(this.currentPreset.parameters).forEach(([paramName, paramConfig]) => {
|
|
const controlDiv = document.createElement('div');
|
|
controlDiv.className = 'preset-control';
|
|
|
|
const label = document.createElement('label');
|
|
label.className = 'preset-label';
|
|
label.textContent = this.formatParameterName(paramName);
|
|
controlDiv.appendChild(label);
|
|
|
|
const input = this.createParameterInput(paramName, paramConfig);
|
|
controlDiv.appendChild(input);
|
|
|
|
controlsContainer.appendChild(controlDiv);
|
|
this.presetControls.set(paramName, input);
|
|
});
|
|
}
|
|
|
|
createParameterInput(paramName, paramConfig) {
|
|
const { type, min, max, step, default: defaultValue } = paramConfig;
|
|
|
|
switch (type) {
|
|
case 'range':
|
|
const sliderInput = document.createElement('input');
|
|
sliderInput.type = 'range';
|
|
sliderInput.className = 'preset-slider';
|
|
sliderInput.min = min;
|
|
sliderInput.max = max;
|
|
sliderInput.step = step || 0.1;
|
|
sliderInput.value = defaultValue;
|
|
|
|
// Add value display
|
|
const valueDisplay = document.createElement('span');
|
|
valueDisplay.className = 'preset-value';
|
|
valueDisplay.textContent = defaultValue;
|
|
|
|
sliderInput.addEventListener('input', (e) => {
|
|
valueDisplay.textContent = parseFloat(e.target.value).toFixed(2);
|
|
this.updatePresetParameter(paramName, parseFloat(e.target.value));
|
|
});
|
|
|
|
const container = document.createElement('div');
|
|
container.style.display = 'flex';
|
|
container.style.alignItems = 'center';
|
|
container.style.gap = '0.5rem';
|
|
container.appendChild(sliderInput);
|
|
container.appendChild(valueDisplay);
|
|
|
|
return container;
|
|
|
|
case 'color':
|
|
const colorInput = document.createElement('input');
|
|
colorInput.type = 'color';
|
|
colorInput.className = 'preset-input';
|
|
colorInput.value = this.hexToColorValue(defaultValue);
|
|
|
|
colorInput.addEventListener('input', (e) => {
|
|
const hexValue = this.colorValueToHex(e.target.value);
|
|
this.updatePresetParameter(paramName, hexValue);
|
|
});
|
|
|
|
return colorInput;
|
|
|
|
default:
|
|
const textInput = document.createElement('input');
|
|
textInput.type = 'text';
|
|
textInput.className = 'preset-input';
|
|
textInput.value = defaultValue;
|
|
|
|
textInput.addEventListener('input', (e) => {
|
|
this.updatePresetParameter(paramName, e.target.value);
|
|
});
|
|
|
|
return textInput;
|
|
}
|
|
}
|
|
|
|
updatePresetParameter(parameter, value) {
|
|
// Send parameter update to server
|
|
this.viewModel.publish('updatePresetParameter', {
|
|
parameter,
|
|
value
|
|
});
|
|
}
|
|
|
|
clearPresetControls() {
|
|
const controlsContainer = this.findElement('#preset-controls');
|
|
if (controlsContainer) {
|
|
controlsContainer.innerHTML = '';
|
|
}
|
|
this.presetControls.clear();
|
|
}
|
|
|
|
formatParameterName(name) {
|
|
return name
|
|
.split(/(?=[A-Z])/)
|
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
.join(' ');
|
|
}
|
|
|
|
hexToColorValue(hex) {
|
|
// Convert hex (rrggbb) to color value (#rrggbb)
|
|
if (hex.startsWith('#')) return hex;
|
|
return `#${hex}`;
|
|
}
|
|
|
|
colorValueToHex(colorValue) {
|
|
// Convert color value (#rrggbb) to hex (rrggbb)
|
|
return colorValue.replace('#', '');
|
|
}
|
|
|
|
startStreaming() {
|
|
const presetSelect = this.findElement('#preset-select');
|
|
if (!presetSelect || !presetSelect.value) {
|
|
alert('Please select a preset first');
|
|
return;
|
|
}
|
|
|
|
const width = parseInt(this.findElement('#matrix-width')?.value) || 16;
|
|
const height = parseInt(this.findElement('#matrix-height')?.value) || 16;
|
|
|
|
this.viewModel.publish('startPreset', {
|
|
presetName: presetSelect.value,
|
|
width,
|
|
height
|
|
});
|
|
}
|
|
|
|
stopStreaming() {
|
|
this.viewModel.publish('stopStreaming', {});
|
|
}
|
|
|
|
sendTestFrame() {
|
|
// Create a test frame with a simple pattern in serpentine order
|
|
const width = parseInt(this.findElement('#matrix-width')?.value) || 16;
|
|
const height = parseInt(this.findElement('#matrix-height')?.value) || 16;
|
|
|
|
let frameData = 'RAW:';
|
|
for (let row = 0; row < height; row++) {
|
|
for (let col = 0; col < width; col++) {
|
|
// Calculate serpentine index manually
|
|
const hardwareIndex = (row % 2 === 0) ?
|
|
(row * width + col) :
|
|
(row * width + (width - 1 - col));
|
|
// Create a checkerboard pattern
|
|
if ((row + col) % 2 === 0) {
|
|
frameData += '00ff00'; // Green
|
|
} else {
|
|
frameData += '000000'; // Black
|
|
}
|
|
}
|
|
}
|
|
|
|
this.viewModel.publish('broadcastToAll', {
|
|
message: frameData
|
|
});
|
|
}
|
|
|
|
clearMatrix() {
|
|
// Send a frame with all black pixels in serpentine order
|
|
const width = parseInt(this.findElement('#matrix-width')?.value) || 16;
|
|
const height = parseInt(this.findElement('#matrix-height')?.value) || 16;
|
|
|
|
let frameData = 'RAW:';
|
|
for (let row = 0; row < height; row++) {
|
|
for (let col = 0; col < width; col++) {
|
|
// Calculate serpentine index manually
|
|
const hardwareIndex = (row % 2 === 0) ?
|
|
(row * width + col) :
|
|
(row * width + (width - 1 - col));
|
|
frameData += '000000';
|
|
}
|
|
}
|
|
|
|
this.viewModel.publish('broadcastToAll', {
|
|
message: frameData
|
|
});
|
|
}
|
|
|
|
applyMatrixConfig() {
|
|
const width = parseInt(this.findElement('#matrix-width')?.value);
|
|
const height = parseInt(this.findElement('#matrix-height')?.value);
|
|
|
|
if (width && height) {
|
|
this.viewModel.publish('setMatrixSize', { width, height });
|
|
}
|
|
}
|
|
|
|
updateStreamingState(isStreaming, preset) {
|
|
const startBtn = this.findElement('#start-btn');
|
|
const stopBtn = this.findElement('#stop-btn');
|
|
|
|
if (isStreaming) {
|
|
startBtn.disabled = true;
|
|
stopBtn.disabled = false;
|
|
} else {
|
|
startBtn.disabled = false;
|
|
stopBtn.disabled = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Export for use in other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = PresetControls;
|
|
}
|