|
|
|
|
@ -11,6 +11,9 @@ export class ChartSettingsManager {
|
|
|
|
|
this.lastSettingValues = {};
|
|
|
|
|
this.settingDebounceTimers = {};
|
|
|
|
|
this.lastUiParameters = new Map();
|
|
|
|
|
this.lastUserInteractionTime = new Map();
|
|
|
|
|
this.parameterChangeTimestamps = {};
|
|
|
|
|
this.containerListeners = new Map(); // Store listeners per container
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateSettings(processorId, settingsContainer, latestData) {
|
|
|
|
|
@ -26,6 +29,13 @@ export class ChartSettingsManager {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check if user recently interacted with controls
|
|
|
|
|
const lastInteraction = this.lastUserInteractionTime.get(processorId);
|
|
|
|
|
if (lastInteraction && Date.now() - lastInteraction < 1000) {
|
|
|
|
|
console.log(`Skipping update for ${processorId} - user is actively editing`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`Updating settings for ${processorId}`);
|
|
|
|
|
this.lastUiParameters.set(processorId, uiParameters ? JSON.parse(JSON.stringify(uiParameters)) : null);
|
|
|
|
|
|
|
|
|
|
@ -47,13 +57,18 @@ export class ChartSettingsManager {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Generate settings HTML
|
|
|
|
|
const settingsHtml = uiParameters.map(param =>
|
|
|
|
|
createParameterControl(param, processorId, 'chart')
|
|
|
|
|
).join('');
|
|
|
|
|
|
|
|
|
|
settingsContainer.innerHTML = settingsHtml;
|
|
|
|
|
this.setupEvents(settingsContainer, processorId);
|
|
|
|
|
// Smart update: only update changed parameters, skip focused elements
|
|
|
|
|
const hasExistingControls = settingsContainer.children.length > 0;
|
|
|
|
|
if (hasExistingControls) {
|
|
|
|
|
this.updateParametersSelectively(processorId, settingsContainer, uiParameters);
|
|
|
|
|
} else {
|
|
|
|
|
// Initial render: full rebuild
|
|
|
|
|
const settingsHtml = uiParameters.map(param =>
|
|
|
|
|
createParameterControl(param, processorId, 'chart')
|
|
|
|
|
).join('');
|
|
|
|
|
settingsContainer.innerHTML = settingsHtml;
|
|
|
|
|
this.setupEvents(settingsContainer, processorId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize last values
|
|
|
|
|
if (uiParameters) {
|
|
|
|
|
@ -66,6 +81,73 @@ export class ChartSettingsManager {
|
|
|
|
|
renderIcons(settingsContainer);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateParametersSelectively(processorId, settingsContainer, newParameters) {
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
|
|
|
|
|
// Temporarily remove event listeners to prevent programmatic changes from triggering events
|
|
|
|
|
this.removeEventListeners(settingsContainer);
|
|
|
|
|
|
|
|
|
|
for (const param of newParameters) {
|
|
|
|
|
const settingElement = settingsContainer.querySelector(`[data-param="${param.name}"]`);
|
|
|
|
|
if (!settingElement) continue;
|
|
|
|
|
|
|
|
|
|
const settingKey = `${processorId}_${param.name}`;
|
|
|
|
|
const currentValue = this.lastSettingValues[settingKey];
|
|
|
|
|
|
|
|
|
|
console.log(`[UPDATE CHECK] ${param.name}: currentValue=${currentValue}, newValue=${param.value}`);
|
|
|
|
|
|
|
|
|
|
// ALWAYS sync lastSettingValues with backend value
|
|
|
|
|
const valueChanged = currentValue !== param.value;
|
|
|
|
|
if (valueChanged) {
|
|
|
|
|
this.lastSettingValues[settingKey] = param.value;
|
|
|
|
|
console.log(`[SYNCED] lastSettingValues["${settingKey}"] = ${param.value}`);
|
|
|
|
|
} else {
|
|
|
|
|
console.log(`[SKIP] ${param.name}: value unchanged`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip UI update if element has focus
|
|
|
|
|
if (settingElement.contains(document.activeElement)) {
|
|
|
|
|
console.log(`Skipping UI update for ${param.name} - has focus`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Skip UI update if user recently changed this parameter
|
|
|
|
|
const lastChange = this.parameterChangeTimestamps[settingKey];
|
|
|
|
|
if (lastChange && now - lastChange < 2000) {
|
|
|
|
|
console.log(`Skipping UI update for ${param.name} - user changed it ${now - lastChange}ms ago`);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update UI control based on type (without triggering events)
|
|
|
|
|
if (param.type === 'toggle') {
|
|
|
|
|
const checkbox = settingElement.querySelector('input[type="checkbox"]');
|
|
|
|
|
if (checkbox && checkbox.checked !== param.value) {
|
|
|
|
|
console.log(`[UPDATE] Toggle ${param.name}: ${checkbox.checked} -> ${param.value}`);
|
|
|
|
|
checkbox.checked = param.value;
|
|
|
|
|
}
|
|
|
|
|
} else if (param.type === 'slider') {
|
|
|
|
|
const slider = settingElement.querySelector('input[type="range"]');
|
|
|
|
|
const valueDisplay = settingElement.querySelector('.chart-setting__value');
|
|
|
|
|
if (slider && parseFloat(slider.value) !== param.value) {
|
|
|
|
|
slider.value = param.value;
|
|
|
|
|
if (valueDisplay) {
|
|
|
|
|
valueDisplay.textContent = param.value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else if (param.type === 'select') {
|
|
|
|
|
const select = settingElement.querySelector('select');
|
|
|
|
|
if (select && select.value !== String(param.value)) {
|
|
|
|
|
console.log(`[UPDATE] Select ${param.name}: ${select.value} -> ${param.value}`);
|
|
|
|
|
select.value = String(param.value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Re-add event listeners after all updates are complete
|
|
|
|
|
this.restoreEventListeners(settingsContainer, processorId);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setupEvents(settingsContainer, processorId) {
|
|
|
|
|
const onParamChange = (e) => {
|
|
|
|
|
if (!e.target.closest('.chart-setting')) return;
|
|
|
|
|
@ -77,16 +159,52 @@ export class ChartSettingsManager {
|
|
|
|
|
this.handleButtonClick(e, processorId);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Store listeners for later removal/restoration
|
|
|
|
|
this.containerListeners.set(settingsContainer, {
|
|
|
|
|
input: onParamChange,
|
|
|
|
|
change: onParamChange,
|
|
|
|
|
click: onButtonClick
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
settingsContainer.addEventListener('input', onParamChange);
|
|
|
|
|
settingsContainer.addEventListener('change', onParamChange);
|
|
|
|
|
settingsContainer.addEventListener('click', onButtonClick);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
removeEventListeners(settingsContainer) {
|
|
|
|
|
const listeners = this.containerListeners.get(settingsContainer);
|
|
|
|
|
if (!listeners) return;
|
|
|
|
|
|
|
|
|
|
settingsContainer.removeEventListener('input', listeners.input);
|
|
|
|
|
settingsContainer.removeEventListener('change', listeners.change);
|
|
|
|
|
settingsContainer.removeEventListener('click', listeners.click);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
restoreEventListeners(settingsContainer, processorId) {
|
|
|
|
|
const listeners = this.containerListeners.get(settingsContainer);
|
|
|
|
|
if (!listeners) {
|
|
|
|
|
// If listeners don't exist, set them up
|
|
|
|
|
this.setupEvents(settingsContainer, processorId);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
settingsContainer.addEventListener('input', listeners.input);
|
|
|
|
|
settingsContainer.addEventListener('change', listeners.change);
|
|
|
|
|
settingsContainer.addEventListener('click', listeners.click);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleSettingChange(event, processorId) {
|
|
|
|
|
const settingElement = event.target.closest('.chart-setting');
|
|
|
|
|
if (!settingElement) return;
|
|
|
|
|
|
|
|
|
|
const now = Date.now();
|
|
|
|
|
const paramName = settingElement.dataset.param;
|
|
|
|
|
const settingKey = `${processorId}_${paramName}`;
|
|
|
|
|
|
|
|
|
|
// Mark user interaction time for both processor and specific parameter
|
|
|
|
|
this.lastUserInteractionTime.set(processorId, now);
|
|
|
|
|
this.parameterChangeTimestamps[settingKey] = now;
|
|
|
|
|
|
|
|
|
|
const input = event.target;
|
|
|
|
|
|
|
|
|
|
let value;
|
|
|
|
|
@ -105,18 +223,21 @@ export class ChartSettingsManager {
|
|
|
|
|
value = value === 'true';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const settingKey = `${processorId}_${paramName}`;
|
|
|
|
|
let lastValue = this.lastSettingValues[settingKey];
|
|
|
|
|
if (typeof lastValue === 'string' && (lastValue === 'true' || lastValue === 'false')) {
|
|
|
|
|
lastValue = lastValue === 'true';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
console.log(`[CHANGE] ${processorId}.${paramName}: ${lastValue} -> ${value} (type: ${typeof value})`);
|
|
|
|
|
|
|
|
|
|
// Check for duplicate
|
|
|
|
|
if (lastValue === value) {
|
|
|
|
|
console.log(`Skipping duplicate value for ${processorId}.${paramName}: ${value}`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.lastSettingValues[settingKey] = value;
|
|
|
|
|
console.log(`[SAVED] lastSettingValues["${settingKey}"] = ${value}`);
|
|
|
|
|
|
|
|
|
|
// Debounce updates
|
|
|
|
|
const debounceKey = `${processorId}_${paramName}`;
|
|
|
|
|
|