874 lines
30 KiB
JavaScript
874 lines
30 KiB
JavaScript
/**
|
|
* SigUIManager - Handles DOM updates and signal card rendering
|
|
*/
|
|
class SigUIManager {
|
|
constructor() {
|
|
this.targetEl = null;
|
|
this.formElement = null;
|
|
this.onDeleteSignal = null;
|
|
}
|
|
|
|
/**
|
|
* Initializes the UI elements with provided IDs.
|
|
* @param {string} targetId - The ID of the HTML element where signals will be displayed.
|
|
* @param {string} formElId - The ID of the HTML element for the signal creation form.
|
|
*/
|
|
initUI(targetId, formElId) {
|
|
this.targetEl = document.getElementById(targetId);
|
|
if (!this.targetEl) {
|
|
console.warn(`Element for displaying signals "${targetId}" not found.`);
|
|
}
|
|
|
|
this.formElement = document.getElementById(formElId);
|
|
if (!this.formElement) {
|
|
console.warn(`Signals form element "${formElId}" not found.`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Displays the form for creating or editing a signal.
|
|
* @param {string} action - The action to perform ('new' or 'edit').
|
|
* @param {object|null} signalData - The data of the signal to edit (only applicable for 'edit' action).
|
|
*/
|
|
displayForm(action, signalData = null) {
|
|
if (!this.formElement) {
|
|
console.error("Form element not initialized.");
|
|
return;
|
|
}
|
|
|
|
const headerTitle = this.formElement.querySelector("h1");
|
|
const submitCreateBtn = this.formElement.querySelector("#submit-create-signal");
|
|
const submitEditBtn = this.formElement.querySelector("#submit-edit-signal");
|
|
const nameBox = this.formElement.querySelector('#signal_name');
|
|
const publicCheckbox = this.formElement.querySelector('#signal_public_checkbox');
|
|
const tblKeyInput = this.formElement.querySelector('#signal_tbl_key');
|
|
|
|
// Reset form to panel 1
|
|
const panel1 = this.formElement.querySelector('#panel_1');
|
|
const panel2 = this.formElement.querySelector('#panel_2');
|
|
const panel3 = this.formElement.querySelector('#panel_3');
|
|
if (panel1) panel1.style.display = 'grid';
|
|
if (panel2) panel2.style.display = 'none';
|
|
if (panel3) panel3.style.display = 'none';
|
|
|
|
if (action === 'new') {
|
|
if (headerTitle) headerTitle.textContent = "Add New Signal";
|
|
if (submitCreateBtn) submitCreateBtn.style.display = "inline-block";
|
|
if (submitEditBtn) submitEditBtn.style.display = "none";
|
|
if (nameBox) nameBox.value = '';
|
|
if (publicCheckbox) publicCheckbox.checked = false;
|
|
if (tblKeyInput) tblKeyInput.value = '';
|
|
} else if (action === 'edit' && signalData) {
|
|
if (headerTitle) headerTitle.textContent = "Edit Signal";
|
|
if (submitCreateBtn) submitCreateBtn.style.display = "none";
|
|
if (submitEditBtn) submitEditBtn.style.display = "inline-block";
|
|
if (nameBox) nameBox.value = signalData.name || '';
|
|
if (publicCheckbox) publicCheckbox.checked = !!signalData.public;
|
|
if (tblKeyInput) tblKeyInput.value = signalData.tbl_key || '';
|
|
|
|
// Pre-fill source fields
|
|
const sigSource = this.formElement.querySelector('#sig_source');
|
|
const sigProp = this.formElement.querySelector('#sig_prop');
|
|
const sig2Source = this.formElement.querySelector('#sig2_source');
|
|
const sig2Prop = this.formElement.querySelector('#sig2_prop');
|
|
const sigType = this.formElement.querySelector('#select_s_type');
|
|
const valueInput = this.formElement.querySelector('#value');
|
|
|
|
if (sigSource && signalData.source1) sigSource.value = signalData.source1;
|
|
if (sigProp && signalData.prop1) {
|
|
// Fill prop options first, then set value
|
|
if (UI.signals && signalData.source1) {
|
|
UI.signals.fill_prop('sig_prop', signalData.source1);
|
|
}
|
|
setTimeout(() => { if (sigProp) sigProp.value = signalData.prop1; }, 50);
|
|
}
|
|
|
|
// Handle source2 - could be 'value' for fixed value type
|
|
if (signalData.source2 === 'value') {
|
|
if (sigType) sigType.value = 'Value';
|
|
if (valueInput) valueInput.value = signalData.prop2 || '';
|
|
} else {
|
|
if (sigType) sigType.value = 'Comparison';
|
|
if (sig2Source && signalData.source2) sig2Source.value = signalData.source2;
|
|
if (sig2Prop && signalData.prop2) {
|
|
if (UI.signals && signalData.source2) {
|
|
UI.signals.fill_prop('sig2_prop', signalData.source2);
|
|
}
|
|
setTimeout(() => { if (sig2Prop) sig2Prop.value = signalData.prop2; }, 50);
|
|
}
|
|
}
|
|
|
|
// Set operator
|
|
const operatorRadios = this.formElement.querySelectorAll('input[name="Operator"]');
|
|
operatorRadios.forEach(radio => {
|
|
radio.checked = radio.value === signalData.operator;
|
|
});
|
|
|
|
// Set range if applicable
|
|
if (signalData.operator === '+/-' && signalData.range) {
|
|
const rangeVal = this.formElement.querySelector('#rangeVal');
|
|
const rangeSlider = this.formElement.querySelector('#rangeSlider');
|
|
if (rangeVal) rangeVal.value = signalData.range;
|
|
if (rangeSlider) rangeSlider.value = signalData.range;
|
|
}
|
|
}
|
|
|
|
this.formElement.style.display = "grid";
|
|
}
|
|
|
|
/**
|
|
* Hides the signal form.
|
|
*/
|
|
hideForm() {
|
|
if (this.formElement) {
|
|
this.formElement.style.display = 'none';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Updates the HTML representation of the signals as cards.
|
|
* @param {Object[]} signals - The list of signals to display.
|
|
*/
|
|
updateSignalsHtml(signals) {
|
|
if (!this.targetEl) {
|
|
console.error("Target element for displaying signals is not set.");
|
|
return;
|
|
}
|
|
|
|
// Clear existing content
|
|
while (this.targetEl.firstChild) {
|
|
this.targetEl.removeChild(this.targetEl.firstChild);
|
|
}
|
|
|
|
// Create and append new elements for all signals
|
|
for (const signal of signals) {
|
|
try {
|
|
const signalCard = this._createSignalCard(signal);
|
|
this.targetEl.appendChild(signalCard);
|
|
} catch (error) {
|
|
console.error(`Error processing signal:`, error, signal);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a signal card HTML element.
|
|
* @param {Object} signal - The signal data.
|
|
* @returns {HTMLElement} - The card element.
|
|
*/
|
|
_createSignalCard(signal) {
|
|
const signalItem = document.createElement('div');
|
|
signalItem.className = 'signal-item';
|
|
signalItem.setAttribute('data-signal-id', signal.tbl_key || signal.name);
|
|
|
|
// Add state-based styling
|
|
const isTrue = signal.state === true || signal.state === 'true' || signal.state === 1;
|
|
if (isTrue) {
|
|
signalItem.classList.add('signal-true');
|
|
} else {
|
|
signalItem.classList.add('signal-false');
|
|
}
|
|
|
|
// Delete button
|
|
const deleteButton = document.createElement('button');
|
|
deleteButton.className = 'delete-button';
|
|
deleteButton.innerHTML = '✘';
|
|
deleteButton.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
if (this.onDeleteSignal) {
|
|
this.onDeleteSignal(signal.tbl_key || signal.name);
|
|
}
|
|
});
|
|
signalItem.appendChild(deleteButton);
|
|
|
|
// Signal icon container
|
|
const signalIcon = document.createElement('div');
|
|
signalIcon.className = 'signal-icon';
|
|
signalIcon.addEventListener('click', () => {
|
|
// Open edit form when clicking on signal
|
|
this.displayForm('edit', signal);
|
|
});
|
|
|
|
// Signal name
|
|
const signalName = document.createElement('div');
|
|
signalName.className = 'signal-name';
|
|
signalName.textContent = signal.name || 'Unnamed Signal';
|
|
signalIcon.appendChild(signalName);
|
|
|
|
// State indicator
|
|
const stateIndicator = document.createElement('div');
|
|
stateIndicator.className = 'signal-state';
|
|
stateIndicator.id = `${signal.name}_state`;
|
|
stateIndicator.textContent = isTrue ? 'TRUE' : 'FALSE';
|
|
signalIcon.appendChild(stateIndicator);
|
|
|
|
signalItem.appendChild(signalIcon);
|
|
|
|
// Hover details panel
|
|
const signalHover = document.createElement('div');
|
|
signalHover.className = 'signal-hover';
|
|
|
|
// Build hover content
|
|
let hoverHtml = `<strong>${signal.name || 'Unnamed Signal'}</strong>`;
|
|
hoverHtml += `<div class="signal-details">`;
|
|
hoverHtml += `<span>Source 1: ${signal.source1} (${signal.prop1})</span>`;
|
|
hoverHtml += `<span id="${signal.name}_value1">Value: ${signal.value1 ?? signal.last_value1 ?? 'N/A'}</span>`;
|
|
hoverHtml += `<span>Operator: ${signal.operator}${signal.operator === '+/-' ? ` (range: ${signal.range})` : ''}</span>`;
|
|
|
|
if (signal.source2 === 'value') {
|
|
hoverHtml += `<span>Compare to: ${signal.prop2}</span>`;
|
|
} else {
|
|
hoverHtml += `<span>Source 2: ${signal.source2} (${signal.prop2})</span>`;
|
|
hoverHtml += `<span id="${signal.name}_value2">Value: ${signal.value2 ?? signal.last_value2 ?? 'N/A'}</span>`;
|
|
}
|
|
|
|
// State display
|
|
const stateClass = isTrue ? 'state-true' : 'state-false';
|
|
hoverHtml += `<span class="${stateClass}">State: ${isTrue ? 'TRUE' : 'FALSE'}</span>`;
|
|
|
|
if (signal.public) {
|
|
hoverHtml += `<span class="signal-public-badge">Public</span>`;
|
|
}
|
|
|
|
hoverHtml += `</div>`;
|
|
|
|
signalHover.innerHTML = hoverHtml;
|
|
signalItem.appendChild(signalHover);
|
|
|
|
return signalItem;
|
|
}
|
|
|
|
/**
|
|
* Updates a single signal's state display.
|
|
* @param {string} signalName - The name of the signal.
|
|
* @param {boolean} state - The new state.
|
|
*/
|
|
updateSignalState(signalName, state) {
|
|
const stateEl = document.getElementById(`${signalName}_state`);
|
|
if (stateEl) {
|
|
const isTrue = state === true || state === 'true' || state === 1;
|
|
stateEl.textContent = isTrue ? 'TRUE' : 'FALSE';
|
|
|
|
// Update parent card styling
|
|
const card = stateEl.closest('.signal-item');
|
|
if (card) {
|
|
card.classList.remove('signal-true', 'signal-false');
|
|
card.classList.add(isTrue ? 'signal-true' : 'signal-false');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the callback function for deleting a signal.
|
|
* @param {Function} callback - The callback function.
|
|
*/
|
|
registerDeleteSignalCallback(callback) {
|
|
this.onDeleteSignal = callback;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* SigDataManager - Manages in-memory signal data store
|
|
*/
|
|
class SigDataManager {
|
|
constructor() {
|
|
this.signals = [];
|
|
}
|
|
|
|
/**
|
|
* Fetches the saved signals from the server.
|
|
* @param {Object} comms - The communications instance.
|
|
* @param {Object} data - An object containing user data.
|
|
*/
|
|
fetchSavedSignals(comms, data) {
|
|
if (comms) {
|
|
try {
|
|
const requestData = {
|
|
request: 'signals',
|
|
user_name: data?.user_name
|
|
};
|
|
comms.sendToApp('request', requestData);
|
|
} catch (error) {
|
|
console.error("Error fetching saved signals:", error.message);
|
|
}
|
|
} else {
|
|
throw new Error('Communications instance not available.');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Adds a new signal to the local store.
|
|
* @param {Object} data - The signal data.
|
|
*/
|
|
addNewSignal(data) {
|
|
const signalData = data.signal || data;
|
|
console.log("Adding new signal:", signalData);
|
|
if (!signalData.name) {
|
|
console.error("Signal data missing 'name' field:", signalData);
|
|
return;
|
|
}
|
|
|
|
// Check for duplicates
|
|
const exists = this.signals.find(s => s.tbl_key === signalData.tbl_key || s.name === signalData.name);
|
|
if (!exists) {
|
|
this.signals.push(signalData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves a signal by its tbl_key.
|
|
* @param {string} tbl_key - The tbl_key of the signal.
|
|
* @returns {Object|null} - The signal object or null.
|
|
*/
|
|
getSignalById(tbl_key) {
|
|
return this.signals.find(signal => signal.tbl_key === tbl_key) || null;
|
|
}
|
|
|
|
/**
|
|
* Retrieves a signal by its name.
|
|
* @param {string} name - The name of the signal.
|
|
* @returns {Object|null} - The signal object or null.
|
|
*/
|
|
getSignalByName(name) {
|
|
return this.signals.find(signal => signal.name === name) || null;
|
|
}
|
|
|
|
/**
|
|
* Updates signal data.
|
|
* @param {Object} data - The updated signal data.
|
|
*/
|
|
updateSignalData(data) {
|
|
const signalData = data.signal || data;
|
|
const signalKey = signalData.tbl_key || signalData.name;
|
|
if (!signalKey) return;
|
|
|
|
const index = this.signals.findIndex(
|
|
signal => signal.tbl_key === signalKey || signal.name === signalKey
|
|
);
|
|
|
|
if (index !== -1) {
|
|
this.signals[index] = { ...this.signals[index], ...signalData };
|
|
} else {
|
|
this.signals.push(signalData);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes a signal from the store.
|
|
* @param {string} identifier - The tbl_key or name of the signal.
|
|
*/
|
|
removeSignal(identifier) {
|
|
console.log(`Removing signal: ${identifier}`);
|
|
this.signals = this.signals.filter(
|
|
sig => sig.tbl_key !== identifier && sig.name !== identifier
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updates signal states from server updates.
|
|
* @param {Object} stateUpdates - Map of signal names to states.
|
|
*/
|
|
applyStateUpdates(stateUpdates) {
|
|
for (const name in stateUpdates) {
|
|
const signal = this.getSignalByName(name);
|
|
if (signal) {
|
|
signal.state = stateUpdates[name];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns all signals.
|
|
* @returns {Object[]} - The list of signals.
|
|
*/
|
|
getAllSignals() {
|
|
return this.signals;
|
|
}
|
|
|
|
/**
|
|
* Sets all signals (used when loading from server).
|
|
* @param {Object[]} signals - The list of signals.
|
|
*/
|
|
setSignals(signals) {
|
|
this.signals = Array.isArray(signals) ? signals : [];
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Signals - Main coordinator class that manages SigUIManager, SigDataManager, and SocketIO communication
|
|
*/
|
|
class Signals {
|
|
constructor(ui) {
|
|
this.ui = ui;
|
|
this.comms = ui?.data?.comms;
|
|
this.indicatorData = ui?.data?.indicators;
|
|
this.data = ui?.data;
|
|
|
|
this.dataManager = new SigDataManager();
|
|
this.uiManager = new SigUIManager();
|
|
|
|
// Set up delete callback
|
|
this.uiManager.registerDeleteSignalCallback(this.deleteSignal.bind(this));
|
|
|
|
// Bind methods
|
|
this.submitSignal = this.submitSignal.bind(this);
|
|
|
|
this._initialized = false;
|
|
}
|
|
|
|
/**
|
|
* Initializes the Signals instance.
|
|
* @param {string} targetId - The ID of the signals container element.
|
|
* @param {string} formElId - The ID of the signal form element.
|
|
*/
|
|
initialize(targetId, formElId) {
|
|
try {
|
|
this.uiManager.initUI(targetId, formElId);
|
|
|
|
if (!this.comms) {
|
|
console.error("Communications instance not available.");
|
|
return;
|
|
}
|
|
|
|
// Register handlers with Comms
|
|
this.comms.on('signals', this.handleSignalsResponse.bind(this));
|
|
this.comms.on('signal_created', this.handleSignalCreated.bind(this));
|
|
this.comms.on('signal_updated', this.handleSignalUpdated.bind(this));
|
|
this.comms.on('signal_deleted', this.handleSignalDeleted.bind(this));
|
|
this.comms.on('signal_error', this.handleSignalError.bind(this));
|
|
this.comms.on('updates', this.handleUpdates.bind(this));
|
|
|
|
// Fetch saved signals
|
|
this.dataManager.fetchSavedSignals(this.comms, this.data);
|
|
|
|
this._initialized = true;
|
|
} catch (error) {
|
|
console.error("Error initializing Signals:", error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle initial signals list response from server.
|
|
* @param {Array} data - List of signal objects.
|
|
*/
|
|
handleSignalsResponse(data) {
|
|
console.log("Received signals list:", data);
|
|
if (Array.isArray(data)) {
|
|
this.dataManager.setSignals(data);
|
|
this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle new signal created event.
|
|
* @param {Object} data - Server response with signal data.
|
|
*/
|
|
handleSignalCreated(data) {
|
|
console.log("Signal created:", data);
|
|
if (data.success) {
|
|
this.dataManager.addNewSignal(data);
|
|
this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
|
|
} else {
|
|
alert(`Failed to create signal: ${data.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle signal updated event.
|
|
* @param {Object} data - Server response with updated signal data.
|
|
*/
|
|
handleSignalUpdated(data) {
|
|
console.log("Signal updated:", data);
|
|
if (data.success) {
|
|
this.dataManager.updateSignalData(data);
|
|
this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
|
|
} else {
|
|
alert(`Failed to update signal: ${data.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle signal deleted event.
|
|
* @param {Object} data - Server response with deleted signal info.
|
|
*/
|
|
handleSignalDeleted(data) {
|
|
console.log("Signal deleted:", data);
|
|
const identifier = data.tbl_key || data.name;
|
|
if (identifier) {
|
|
this.dataManager.removeSignal(identifier);
|
|
this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle signal error.
|
|
* @param {Object} data - Error data.
|
|
*/
|
|
handleSignalError(data) {
|
|
console.error("Signal error:", data.message);
|
|
alert(`Signal error: ${data.message}`);
|
|
}
|
|
|
|
/**
|
|
* Handle updates (including signal state changes).
|
|
* @param {Object} data - Update data from server.
|
|
*/
|
|
handleUpdates(data) {
|
|
const { s_updates } = data;
|
|
if (s_updates) {
|
|
this.dataManager.applyStateUpdates(s_updates);
|
|
// Update UI for state changes
|
|
for (const name in s_updates) {
|
|
this.uiManager.updateSignalState(name, s_updates[name]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ================ Form Methods ================
|
|
|
|
/**
|
|
* Opens the signal creation form.
|
|
*/
|
|
open_signal_Form() {
|
|
this.uiManager.displayForm('new');
|
|
}
|
|
|
|
/**
|
|
* Closes the signal form.
|
|
*/
|
|
close_signal_Form() {
|
|
this.uiManager.hideForm();
|
|
}
|
|
|
|
/**
|
|
* Requests signals from server.
|
|
*/
|
|
request_signals() {
|
|
if (this.comms) {
|
|
this.comms.sendToApp('request', { request: 'signals', user_name: this.data?.user_name });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes a signal by tbl_key or name.
|
|
* @param {string} identifier - The tbl_key or name of the signal.
|
|
*/
|
|
deleteSignal(identifier) {
|
|
if (!this.comms) {
|
|
console.error("Comms instance not available.");
|
|
return;
|
|
}
|
|
|
|
const signal = this.dataManager.getSignalById(identifier) ||
|
|
this.dataManager.getSignalByName(identifier);
|
|
|
|
const deleteData = signal
|
|
? { tbl_key: signal.tbl_key, name: signal.name }
|
|
: { name: identifier };
|
|
|
|
this.comms.sendToApp('delete_signal', deleteData);
|
|
}
|
|
|
|
/**
|
|
* Submits a new or edited signal.
|
|
* @param {string} action - 'new' or 'edit'.
|
|
*/
|
|
submitSignal(action) {
|
|
const formElement = this.uiManager.formElement;
|
|
if (!formElement) {
|
|
console.error("Form element not available.");
|
|
return;
|
|
}
|
|
|
|
const name = formElement.querySelector('#signal_name')?.value?.trim();
|
|
const source1 = formElement.querySelector('#sig_source')?.value;
|
|
const prop1 = formElement.querySelector('#sig_prop')?.value;
|
|
const source2 = formElement.querySelector('#sig2_source')?.value;
|
|
const prop2 = formElement.querySelector('#sig2_prop')?.value;
|
|
const operator = formElement.querySelector('input[name="Operator"]:checked')?.value;
|
|
const range = formElement.querySelector('#rangeVal')?.value;
|
|
const sigType = formElement.querySelector('#select_s_type')?.value;
|
|
const value = formElement.querySelector('#value')?.value;
|
|
const publicCheckbox = formElement.querySelector('#signal_public_checkbox');
|
|
const tblKey = formElement.querySelector('#signal_tbl_key')?.value;
|
|
|
|
if (!name) {
|
|
alert("Please provide a name for the signal.");
|
|
return;
|
|
}
|
|
if (!prop1) {
|
|
alert("Please select a property for the signal source.");
|
|
return;
|
|
}
|
|
|
|
// Build signal data
|
|
let actualSource2 = source2;
|
|
let actualProp2 = prop2;
|
|
|
|
if (sigType !== 'Comparison') {
|
|
actualSource2 = 'value';
|
|
actualProp2 = value;
|
|
}
|
|
|
|
const signalData = {
|
|
name,
|
|
source1,
|
|
prop1,
|
|
operator,
|
|
source2: actualSource2,
|
|
prop2: actualProp2,
|
|
state: false,
|
|
value1: null,
|
|
value2: null,
|
|
public: publicCheckbox?.checked ? 1 : 0,
|
|
user_name: this.data?.user_name
|
|
};
|
|
|
|
if (operator === '+/-') {
|
|
signalData.range = parseFloat(range) || 0;
|
|
}
|
|
|
|
if (action === 'edit' && tblKey) {
|
|
signalData.tbl_key = tblKey;
|
|
}
|
|
|
|
const messageType = action === 'new' ? 'new_signal' : 'edit_signal';
|
|
this.comms.sendToApp(messageType, signalData);
|
|
|
|
this.close_signal_Form();
|
|
}
|
|
|
|
/**
|
|
* Submits a new signal (legacy method name).
|
|
*/
|
|
submitNewSignal() {
|
|
this.submitSignal('new');
|
|
}
|
|
|
|
// ================ Helper Methods ================
|
|
|
|
/**
|
|
* Fills property dropdown based on indicator type.
|
|
* @param {string} target_id - The ID of the select element.
|
|
* @param {string} indctr - The indicator name.
|
|
*/
|
|
fill_prop(target_id, indctr) {
|
|
const target = document.getElementById(target_id);
|
|
const indicatorConfig = this.indicatorData ? this.indicatorData[indctr] : null;
|
|
|
|
if (!target) return;
|
|
|
|
// Clear existing options
|
|
while (target.options.length > 0) {
|
|
target.remove(0);
|
|
}
|
|
|
|
if (!indicatorConfig) {
|
|
console.warn(`Indicator "${indctr}" not found in indicator data`);
|
|
return;
|
|
}
|
|
|
|
// Get the indicator outputs based on type
|
|
const outputMap = {
|
|
'SMA': ['value'],
|
|
'EMA': ['value'],
|
|
'LREG': ['value'],
|
|
'RSI': ['value'],
|
|
'ATR': ['value'],
|
|
'Volume': ['value'],
|
|
'MACD': ['macd', 'signal', 'hist'],
|
|
'BOLBands': ['upper', 'middle', 'lower']
|
|
};
|
|
|
|
const indicatorType = indicatorConfig.type;
|
|
const outputs = outputMap[indicatorType] || ['value'];
|
|
|
|
for (const output of outputs) {
|
|
const opt = document.createElement("option");
|
|
opt.value = output;
|
|
opt.textContent = output;
|
|
target.appendChild(opt);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Switches between form panels.
|
|
* @param {string} p1 - Panel to hide.
|
|
* @param {string} p2 - Panel to show.
|
|
*/
|
|
switch_panel(p1, p2) {
|
|
const panel1 = document.getElementById(p1);
|
|
const panel2 = document.getElementById(p2);
|
|
if (panel1) panel1.style.display = 'none';
|
|
if (panel2) panel2.style.display = 'grid';
|
|
}
|
|
|
|
/**
|
|
* Conditionally hides an element.
|
|
* @param {*} firstValue - First value to compare.
|
|
* @param {*} scndValue - Second value to compare.
|
|
* @param {string} id - Element ID to show/hide.
|
|
*/
|
|
hideIfTrue(firstValue, scndValue, id) {
|
|
const el = document.getElementById(id);
|
|
if (el) {
|
|
el.style.display = firstValue === scndValue ? 'none' : 'block';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the current value of an indicator property from the DOM.
|
|
* @param {string} source - The indicator source name.
|
|
* @param {string} prop - The property name.
|
|
* @returns {string} - The value.
|
|
*/
|
|
_getIndicatorValue(source, prop) {
|
|
let element = document.getElementById(source + '_' + prop)
|
|
|| document.getElementById(source + '_value');
|
|
|
|
if (!element) {
|
|
console.warn(`Could not find indicator value element for ${source}_${prop}`);
|
|
return '0';
|
|
}
|
|
|
|
let rawValue = element.value || element.textContent || '0';
|
|
|
|
// Parse multi-value format if needed
|
|
if (rawValue.includes(':') && rawValue.includes(',')) {
|
|
const parts = rawValue.split(',').map(p => p.trim());
|
|
for (const part of parts) {
|
|
const [key, val] = part.split(':').map(s => s.trim());
|
|
if (key === prop) {
|
|
return val;
|
|
}
|
|
}
|
|
}
|
|
|
|
return rawValue;
|
|
}
|
|
|
|
/**
|
|
* Handles panel 1 "Next" button click.
|
|
* @param {number} n - Panel number.
|
|
*/
|
|
ns_next(n) {
|
|
if (n === 1) {
|
|
const sigName = document.getElementById('signal_name')?.value;
|
|
const sigSource = document.getElementById('sig_source')?.value;
|
|
const sigProp = document.getElementById('sig_prop')?.value;
|
|
|
|
if (!sigName) {
|
|
alert('Please give the signal a name.');
|
|
return;
|
|
}
|
|
if (!sigProp) {
|
|
alert('Please select a property.');
|
|
return;
|
|
}
|
|
|
|
const display = document.getElementById('sig_display');
|
|
if (display) {
|
|
display.innerHTML = `${sigName}: {${sigSource}:${sigProp}}`;
|
|
}
|
|
|
|
// Get current indicator value
|
|
const indctrVal = this._getIndicatorValue(sigSource, sigProp);
|
|
const valueInput = document.getElementById('value');
|
|
if (valueInput) {
|
|
valueInput.value = indctrVal || '0';
|
|
}
|
|
|
|
this.switch_panel('panel_1', 'panel_2');
|
|
}
|
|
|
|
if (n === 2) {
|
|
const sigName = document.getElementById('signal_name')?.value;
|
|
const sigSource = document.getElementById('sig_source')?.value;
|
|
const sigProp = document.getElementById('sig_prop')?.value;
|
|
const sig2Source = document.getElementById('sig2_source')?.value;
|
|
const sig2Prop = document.getElementById('sig2_prop')?.value;
|
|
const operator = document.querySelector('input[name="Operator"]:checked')?.value;
|
|
const range = document.getElementById('rangeVal')?.value;
|
|
const sigType = document.getElementById('select_s_type')?.value;
|
|
const value = document.getElementById('value')?.value;
|
|
|
|
const sig1 = `${sigSource} : ${sigProp}`;
|
|
const sig2 = sigType === 'Comparison' ? `${sig2Source} : ${sig2Prop}` : value;
|
|
const operatorStr = operator === '+/-' ? `${operator} ${range}` : operator;
|
|
|
|
const sigDisplayStr = `(${sigName}) (${sig1}) (${operatorStr}) (${sig2})`;
|
|
|
|
const sig1_realtime = this._getIndicatorValue(sigSource, sigProp);
|
|
const sig2_realtime = sigType === 'Comparison'
|
|
? this._getIndicatorValue(sig2Source, sig2Prop)
|
|
: value;
|
|
|
|
const display2 = document.getElementById('sig_display2');
|
|
const realtime = document.getElementById('sig_realtime');
|
|
const evalEl = document.getElementById('sig_eval');
|
|
|
|
if (display2) display2.innerHTML = sigDisplayStr;
|
|
if (realtime) realtime.innerHTML = `(${sigProp} : ${sig1_realtime}) (${operatorStr}) (${sig2_realtime})`;
|
|
|
|
// Evaluate
|
|
let evalStr;
|
|
if (operator === '==') evalStr = parseFloat(sig1_realtime) === parseFloat(sig2_realtime);
|
|
if (operator === '>') evalStr = parseFloat(sig1_realtime) > parseFloat(sig2_realtime);
|
|
if (operator === '<') evalStr = parseFloat(sig1_realtime) < parseFloat(sig2_realtime);
|
|
if (operator === '+/-') evalStr = Math.abs(parseFloat(sig1_realtime) - parseFloat(sig2_realtime)) <= parseFloat(range);
|
|
|
|
if (evalEl) evalEl.innerHTML = evalStr ? 'true' : 'false';
|
|
|
|
this.switch_panel('panel_2', 'panel_3');
|
|
}
|
|
}
|
|
|
|
// Legacy methods for backwards compatibility
|
|
i_update(updates) {
|
|
for (const signal of this.dataManager.getAllSignals()) {
|
|
const s1 = signal.source1;
|
|
if (s1 in updates) {
|
|
const p1 = signal.prop1;
|
|
const value1 = updates[s1].data[0][p1];
|
|
signal.value1 = value1.toFixed(2);
|
|
}
|
|
if (signal.source2 !== 'value') {
|
|
const s2 = signal.source2;
|
|
if (s2 in updates) {
|
|
const p2 = signal.prop2;
|
|
const value2 = updates[s2].data[0][p2];
|
|
signal.value2 = value2.toFixed(2);
|
|
}
|
|
}
|
|
const val1El = document.getElementById(signal.name + '_value1');
|
|
const val2El = document.getElementById(signal.name + '_value2');
|
|
if (val1El) val1El.innerHTML = signal.value1;
|
|
if (val2El) val2El.innerHTML = signal.value2;
|
|
}
|
|
}
|
|
|
|
update_signal_states(s_updates) {
|
|
for (const name in s_updates) {
|
|
this.uiManager.updateSignalState(name, s_updates[name]);
|
|
}
|
|
}
|
|
|
|
set_data(signals) {
|
|
// Legacy method - now handled by handleSignalsResponse
|
|
if (Array.isArray(signals)) {
|
|
for (const sig of signals) {
|
|
const obj = typeof sig === 'string' ? JSON.parse(sig) : sig;
|
|
this.dataManager.addNewSignal(obj);
|
|
}
|
|
this.uiManager.updateSignalsHtml(this.dataManager.getAllSignals());
|
|
}
|
|
}
|
|
|
|
delete_signal(signal_name) {
|
|
// Legacy method - redirect to new method
|
|
this.deleteSignal(signal_name);
|
|
}
|
|
}
|