feat: node canvas grid

This commit is contained in:
2025-10-11 21:18:21 +02:00
parent 294d86f24b
commit 32f35c70cf
11 changed files with 1444 additions and 421 deletions

View File

@@ -6,6 +6,8 @@ class PresetControls extends Component {
this.presets = {};
this.currentPreset = null;
this.presetControls = new Map();
this.selectedNode = null; // Track which node is currently selected
this.nodeParameters = new Map(); // Store parameters per node: nodeIp -> { presetName, parameters }
}
mount() {
@@ -80,28 +82,36 @@ class PresetControls extends Component {
}
setupViewModelListeners() {
this.subscribeToEvent('streamingStarted', (data) => {
this.updateStreamingState(true, data.preset);
// Listen for node selection
this.subscribeToEvent('selectNode', (data) => {
this.selectedNode = data.nodeIp;
this.loadNodeSettings();
});
this.subscribeToEvent('streamingStopped', () => {
this.updateStreamingState(false);
this.subscribeToEvent('streamingStarted', (data) => {
this.updateStreamingState(true, data.preset, data.nodeIp);
});
this.subscribeToEvent('streamingStopped', (data) => {
this.updateStreamingState(false, null, data.nodeIp);
});
this.subscribeToEvent('presetParameterUpdated', (data) => {
// 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);
// Only update if this is for the currently selected node
if (data.nodeIp === this.selectedNode) {
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;
}
} else if (control.type === 'color') {
control.value = this.hexToColorValue(data.value);
} else {
control.value = data.value;
}
}
});
@@ -202,9 +212,60 @@ class PresetControls extends Component {
}
this.currentPreset = this.presets[presetName];
// Store preset selection for current node
if (this.selectedNode) {
const nodeParams = this.nodeParameters.get(this.selectedNode) || {};
nodeParams.presetName = presetName;
this.nodeParameters.set(this.selectedNode, nodeParams);
}
this.createPresetControls();
}
loadNodeSettings() {
if (!this.selectedNode) return;
// Get stored parameters for this node
const nodeParams = this.nodeParameters.get(this.selectedNode);
if (nodeParams && nodeParams.presetName) {
// Load the preset and restore parameters
const presetSelect = this.findElement('#preset-select');
if (presetSelect && presetSelect.value !== nodeParams.presetName) {
presetSelect.value = nodeParams.presetName;
this.selectPreset(nodeParams.presetName);
}
// Restore parameter values
if (nodeParams.parameters) {
Object.entries(nodeParams.parameters).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;
}
}
});
}
} else {
// Reset to default
const presetSelect = this.findElement('#preset-select');
if (presetSelect) {
presetSelect.value = '';
this.clearPresetControls();
}
}
}
createPresetControls() {
const controlsContainer = this.findElement('#preset-controls');
if (!controlsContainer) return;
@@ -313,13 +374,24 @@ class PresetControls extends Component {
}
updatePresetParameter(parameter, value) {
// Store parameter for current node
if (this.selectedNode) {
const nodeParams = this.nodeParameters.get(this.selectedNode) || {};
if (!nodeParams.parameters) {
nodeParams.parameters = {};
}
nodeParams.parameters[parameter] = value;
this.nodeParameters.set(this.selectedNode, nodeParams);
}
// Send parameter update to server immediately (real-time)
this.viewModel.publish('updatePresetParameter', {
parameter,
value
value,
nodeIp: this.selectedNode
});
console.log(`Parameter updated: ${parameter} = ${value}`);
console.log(`Parameter updated for ${this.selectedNode}: ${parameter} = ${value}`);
}
clearPresetControls() {
@@ -355,21 +427,39 @@ class PresetControls extends Component {
return;
}
if (!this.selectedNode) {
alert('Please select a node first');
return;
}
const width = parseInt(this.findElement('#matrix-width')?.value) || 16;
const height = parseInt(this.findElement('#matrix-height')?.value) || 16;
// Get current parameters for this node
const nodeParams = this.nodeParameters.get(this.selectedNode);
const parameters = nodeParams?.parameters || {};
this.viewModel.publish('startPreset', {
presetName: presetSelect.value,
width,
height
height,
nodeIp: this.selectedNode,
parameters
});
}
stopStreaming() {
this.viewModel.publish('stopStreaming', {});
this.viewModel.publish('stopStreaming', {
nodeIp: this.selectedNode
});
}
sendTestFrame() {
if (!this.selectedNode) {
alert('Please select a node first');
return;
}
// 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;
@@ -390,12 +480,18 @@ class PresetControls extends Component {
}
}
this.viewModel.publish('broadcastToAll', {
this.viewModel.publish('sendToNode', {
nodeIp: this.selectedNode,
message: frameData
});
}
clearMatrix() {
if (!this.selectedNode) {
alert('Please select a node first');
return;
}
// 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;
@@ -411,7 +507,8 @@ class PresetControls extends Component {
}
}
this.viewModel.publish('broadcastToAll', {
this.viewModel.publish('sendToNode', {
nodeIp: this.selectedNode,
message: frameData
});
}
@@ -425,7 +522,12 @@ class PresetControls extends Component {
}
}
updateStreamingState(isStreaming, preset) {
updateStreamingState(isStreaming, preset, nodeIp) {
// Only update UI if this is for the currently selected node
if (nodeIp !== this.selectedNode && nodeIp !== null) {
return;
}
const toggleBtn = this.findElement('#toggle-stream-btn');
const btnIcon = toggleBtn?.querySelector('.btn-icon');
const btnText = toggleBtn?.querySelector('.btn-text');