Mostly working now

This commit is contained in:
Rob 2024-09-19 17:10:26 -03:00
parent f1d0f2a4b1
commit 29f30cb358
6 changed files with 425 additions and 215 deletions

View File

@ -895,8 +895,14 @@ class DatabaseInteractions(SnapshotDataCache):
if len(rows) > 1:
raise ValueError(f"Multiple rows found for {filter_vals}. Please provide a more specific filter.")
# Modify the specified field
if isinstance(new_data, str):
# Ensure consistency when storing boolean data like 'visible'
if isinstance(new_data, bool):
# If storing in a system that supports booleans, you can store directly as boolean
updated_value = new_data # For cache systems that support booleans
# If your cache or database only supports strings, convert boolean to string
# updated_value = 'true' if new_data else 'false'
elif isinstance(new_data, str):
updated_value = new_data
else:
updated_value = json.dumps(new_data) # Convert non-string data to JSON string if necessary

View File

@ -309,7 +309,7 @@ class Indicators:
:param only_enabled: bool - If True, return only indicators marked as visible.
:return: dict - A dictionary of indicator names as keys and their attributes as values.
"""
user_id = str(self.users.get_id(username))
user_id = self.users.get_id(username)
if not user_id:
raise ValueError(f"Invalid user_name: {username}")
@ -317,7 +317,7 @@ class Indicators:
# Fetch indicators based on visibility status
if only_enabled:
indicators_df = self.cache_manager.get_rows_from_datacache('indicators',
[('creator', user_id), ('visible', str(1))])
[('creator', user_id), ('visible', True)])
else:
indicators_df = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)])
@ -333,10 +333,15 @@ class Indicators:
properties = row.get('properties', {})
properties = json.loads(properties) if isinstance(properties, str) else properties
# Deserialize the 'source' field if it is a JSON string
source = row.get('source', {})
source = json.loads(source) if isinstance(source, str) else source
# Construct the result dictionary for each indicator
result[row['name']] = {
'type': row['kind'],
'visible': row['visible'],
'visible': bool(row['visible']),
'source': source,
**properties # Merge in all properties from the properties field
}
@ -344,24 +349,24 @@ class Indicators:
def toggle_indicators(self, user_id: int, indicator_names: list) -> None:
"""
Set the visibility of indicators for a user.
Set the visibility of indicators for a user.
:param user_id: The id of the user.
:param indicator_names: List of indicator names to set as visible.
:return: None
:param user_id: The id of the user.
:param indicator_names: List of indicator names to set as visible.
:return: None
"""
indicators = self.cache_manager.get_rows_from_datacache('indicators', [('creator', user_id)])
# Validate inputs
if indicators.empty:
return
# Set visibility for all indicators off
self.cache_manager.modify_datacache_item('indicators', [('creator', user_id)],
field_name='visible', new_data=0, overwrite='name')
field_name='visible', new_data=False, overwrite='name')
# Set visibility for the specified indicators on
self.cache_manager.modify_datacache_item('indicators', [('creator', user_id), ('name', indicator_names)],
field_name='visible', new_data=1, overwrite='name')
field_name='visible', new_data=True, overwrite='name')
def edit_indicator(self, user_name: str, params: dict):
"""
@ -374,27 +379,58 @@ class Indicators:
raise ValueError("Indicator name is required for editing.")
# Get the indicator from the user's indicator list
user_id = str(self.users.get_id(user_name))
user_id = self.users.get_id(user_name)
indicator = self.cache_manager.get_rows_from_datacache('indicators',
[('name', indicator_name), ('creator', user_id)])
if indicator.empty:
raise ValueError(f"Indicator '{indicator_name}' not found for user '{user_name}'.")
# Modify indicator.
self.cache_manager.modify_datacache_item('indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='properties', new_data=params.get('properties'),
overwrite='name')
# Compare existing_properties and new_properties as strings
new_properties = params.get('properties')
if new_properties is not None:
new_properties_str = json.dumps(new_properties, sort_keys=True) # Convert new properties to a JSON string
existing_properties_str = indicator['properties'].iloc[0] # Existing properties are already a JSON string
new_visible = params.get('visible')
current_visible = indicator['visible'].iloc[0]
# Compare the strings directly
if existing_properties_str != new_properties_str:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='properties',
new_data=new_properties,
overwrite='name'
)
# Compare existing_source and new_source as strings
new_source = params.get('source')
if new_source is not None:
new_source_str = json.dumps(new_source, sort_keys=True) # Convert new source to a JSON string
existing_source_str = indicator['source'].iloc[
0] if 'source' in indicator else None # Existing source as JSON string
# Compare the strings directly
if existing_source_str != new_source_str and new_source_str is not None:
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='source',
new_data=new_source,
overwrite='name'
)
# Convert current_visible to boolean and check if it has changed
current_visible = str(indicator['visible'].iloc[0]).lower() in ['true', '1', 't', 'yes']
new_visible = bool(params.get('visible'))
if current_visible != new_visible:
self.cache_manager.modify_datacache_item('indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='visible', new_data=new_visible,
overwrite='name')
self.cache_manager.modify_datacache_item(
'indicators',
[('creator', user_id), ('name', indicator_name)],
field_name='visible',
new_data=new_visible,
overwrite='name'
)
def new_indicator(self, user_name: str, params) -> None:
"""
@ -404,26 +440,22 @@ class Indicators:
:param params: The request parameters containing indicator information.
:return: None
"""
indcr = params['newi_name']
indtyp = params['newi_type']
indcr = params['name']
indtyp = params['type']
# Validate indicator name and type
if not indcr:
raise ValueError("Indicator name is required.")
if indtyp not in ['SMA', 'EMA', 'RSI', 'LREG', 'ATR', 'BOLBands', 'MACD', 'Volume']:
if indtyp not in indicators_registry:
raise ValueError("Unsupported indicator type.")
# Create a dictionary of properties from the values in request form.
source = {
'symbol': params['ei_symbol'],
'timeframe': params['ei_timeframe'],
'exchange_name': params['ei_exchange_name']
}
source = params['source']
# Validate properties (assuming properties is a dictionary)
properties = {}
if params['new_prop_obj']:
properties = json.loads(params['new_prop_obj'])
if params['properties']:
properties = json.loads(params['properties'])
# Create indicator.
self.create_indicator(creator=user_name, name=indcr, kind=indtyp, source=source, properties=properties)
@ -438,7 +470,7 @@ class Indicators:
"""
username = self.users.get_username(indicator.creator)
src = indicator.source
symbol, timeframe, exchange_name = src['symbol'], src['timeframe'], src['exchange_name']
symbol, timeframe, exchange_name = src['market'], src['timeframe'], src['exchange']
# Retrieve necessary details to instantiate the indicator
name = indicator.name
@ -502,11 +534,11 @@ class Indicators:
# Extract fields from indicators['source'] and compare directly
mask = (indicators['source'].apply(lambda s: s.get('timeframe')) == source_timeframe) & \
(indicators['source'].apply(lambda s: s.get('exchange_name')) == source_exchange) & \
(indicators['source'].apply(lambda s: s.get('symbol')) == source_symbol)
(indicators['source'].apply(lambda s: s.get('exchange')) == source_exchange) & \
(indicators['source'].apply(lambda s: s.get('market')) == source_symbol)
# Filter the DataFrame using the mask
filtered_indicators = indicators[mask]
indicators = indicators[mask]
# If no indicators match the filtered source, return None.
if indicators.empty:
@ -515,7 +547,7 @@ class Indicators:
# Process each indicator, convert DataFrame to JSON-serializable format, and collect the results
json_ready_results = {}
for indicator in filtered_indicators.itertuples(index=False):
for indicator in indicators.itertuples(index=False):
indicator_results = self.process_indicator(indicator=indicator, num_results=num_results)
# Convert DataFrame to list of dictionaries if necessary
@ -569,15 +601,22 @@ class Indicators:
if kind not in self.indicator_registry:
raise ValueError(f"Requested an unsupported type of indicator: ({kind})")
# Instantiate the indicator class from the registry
IndicatorClass = indicators_registry[kind]
indicator_instance = IndicatorClass(name=name, indicator_type=kind, properties=properties)
# Sanitize source and properties by converting them to JSON strings
sanitized_source = json.dumps(source) # Converts the dictionary to a JSON string
sanitized_properties = json.dumps(indicator_instance.properties) # Same for properties
# Add the new indicator to a pandas dataframe.
creator_id = self.users.get_id(creator)
row_data = pd.DataFrame([{
'creator': creator_id,
'name': name,
'kind': kind,
'visible': visible,
'source': source,
'properties': properties
'visible': bool(visible),
'source': sanitized_source,
'properties': sanitized_properties
}])
self.cache_manager.insert_df_into_datacache(df=row_data, cache_name="indicators", skip_cache=False)

View File

@ -147,6 +147,28 @@ class Comms {
}
}
/**
* Sends a request to update an indicator's properties.
* @param {Object} indicatorData - An object containing the updated properties of the indicator.
* @returns {Promise<Object>} - The response from the server.
*/
async submitIndicator(indicatorData) {
try {
const response = await fetch('/settings', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
setting: 'new_indicator',
indicator: indicatorData
})
});
return await response.json();
} catch (error) {
console.error('Error updating indicator:', error);
return { success: false };
}
}
/**
* Sends a message to the application server.
* @param {string} messageType - The type of the message.

View File

@ -460,51 +460,61 @@ class Indicators {
}
// This updates a specific indicator
updateIndicator(event) {
const row = event.target.closest('.indicator-row');
const inputs = row.querySelectorAll('input, select');
updateIndicator(event) {
const row = event.target.closest('.indicator-row');
const inputs = row.querySelectorAll('input, select');
// Gather the indicator name from the row
const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name
const indicatorName = nameDiv.innerText.trim(); // Get the indicator name
// Gather the indicator name from the row
const nameDiv = row.querySelector('div:nth-child(2)'); // Second <div> contains the name
const indicatorName = nameDiv.innerText.trim(); // Get the indicator name
// Gather input data
// Initialize formObj with the name of the indicator
const formObj = {
name: indicatorName,
visible: false, // Default value for visible (will be updated based on the checkbox input)
properties: {}
};
// Initialize formObj with the name of the indicator
const formObj = {
name: indicatorName,
visible: false, // Default value for visible (will be updated based on the checkbox input)
source: {}, // Initialize the source object directly at the top level
properties: {} // Initialize the properties object
};
// Iterate over each input (text, checkbox, select) and add its name and value to formObj
inputs.forEach(input => {
if (input.name === 'visible') {
// Handle the visible checkbox separately
formObj.visible = input.checked;
} else {
// Add all other inputs (type, period, color) to the properties object
formObj.properties[input.name] = input.type === 'checkbox' ? input.checked : input.value;
}
});
// Iterate over each input (text, checkbox, select) and add its name and value to formObj
inputs.forEach(input => {
if (input.name === 'visible') {
// Handle the visible checkbox separately
formObj.visible = input.checked;
} else if (input.name === 'market' || input.name === 'timeframe' || input.name === 'exchange') {
// Directly map inputs to source object fields
formObj.source[input.name] = input.value;
} else {
// Add all other inputs (type, period, color) to the properties object
formObj.properties[input.name] = input.type === 'checkbox' ? input.checked : input.value;
}
});
// Call comms to send data to the server
this.comms.updateIndicator(formObj).then(response => {
if (response.success) {
window.location.reload(); // This triggers a full page refresh
} else {
alert('Failed to update the indicator.');
}
});
}
// Call comms to send data to the server
this.comms.updateIndicator(formObj).then(response => {
if (response.success) {
window.location.reload(); // This triggers a full page refresh
} else {
alert('Failed to update the indicator.');
}
});
}
add_to_list(){
// Adds user input to a list and displays it in a HTML element.
// Used in the Create indicator panel (!)Called from inline html.
let n = document.getElementById("new_prop_name").value;
let v = document.getElementById("new_prop_value").value;
// Collect the property name and value input by the user
let n = document.getElementById("new_prop_name").value.trim();
let v = document.getElementById("new_prop_value").value.trim();
// Ensure the property name and value are not empty
if (!n || !v) {
alert("Property name and value are required.");
return;
}
// Converts css color name to hex
if (n == 'color'){
if (n === 'color'){
// list of valid css colors
let colours = {
"aliceblue":"#f0f8ff", "antiquewhite":"#faebd7", "aqua":"#00ffff", "aquamarine":"#7fffd4", "azure":"#f0ffff", "beige":"#f5f5dc", "bisque":"#ffe4c4", "black":"#000000", "blanchedalmond":"#ffebcd", "blue":"#0000ff", "blueviolet":"#8a2be2", "brown":"#a52a2a", "burlywood":"#deb887", "cadetblue":"#5f9ea0", "chartreuse":"#7fff00", "chocolate":"#d2691e", "coral":"#ff7f50", "cornflowerblue":"#6495ed", "cornsilk":"#fff8dc", "crimson":"#dc143c", "cyan":"#00ffff", "darkblue":"#00008b", "darkcyan":"#008b8b", "darkgoldenrod":"#b8860b", "darkgray":"#a9a9a9", "darkgreen":"#006400", "darkkhaki":"#bdb76b", "darkmagenta":"#8b008b", "darkolivegreen":"#556b2f", "darkorange":"#ff8c00", "darkorchid":"#9932cc", "darkred":"#8b0000", "darksalmon":"#e9967a", "darkseagreen":"#8fbc8f", "darkslateblue":"#483d8b", "darkslategray":"#2f4f4f", "darkturquoise":"#00ced1", "darkviolet":"#9400d3", "deeppink":"#ff1493", "deepskyblue":"#00bfff", "dimgray":"#696969", "dodgerblue":"#1e90ff", "firebrick":"#b22222", "floralwhite":"#fffaf0", "forestgreen":"#228b22", "fuchsia":"#ff00ff", "gainsboro":"#dcdcdc", "ghostwhite":"#f8f8ff", "gold":"#ffd700", "goldenrod":"#daa520", "gray":"#808080", "green":"#008000", "greenyellow":"#adff2f",
@ -512,28 +522,52 @@ class Indicators {
"palegreen":"#98fb98", "paleturquoise":"#afeeee", "palevioletred":"#d87093", "papayawhip":"#ffefd5", "peachpuff":"#ffdab9", "peru":"#cd853f", "pink":"#ffc0cb", "plum":"#dda0dd", "powderblue":"#b0e0e6", "purple":"#800080", "rebeccapurple":"#663399", "red":"#ff0000", "rosybrown":"#bc8f8f", "royalblue":"#4169e1", "saddlebrown":"#8b4513", "salmon":"#fa8072", "sandybrown":"#f4a460", "seagreen":"#2e8b57", "seashell":"#fff5ee", "sienna":"#a0522d", "silver":"#c0c0c0", "skyblue":"#87ceeb", "slateblue":"#6a5acd", "slategray":"#708090", "snow":"#fffafa", "springgreen":"#00ff7f", "steelblue":"#4682b4", "tan":"#d2b48c", "teal":"#008080", "thistle":"#d8bfd8", "tomato":"#ff6347", "turquoise":"#40e0d0", "violet":"#ee82ee", "wheat":"#f5deb3", "white":"#ffffff", "whitesmoke":"#f5f5f5", "yellow":"#ffff00", "yellowgreen":"#9acd32"
};
// if the value is in the list of colors convert it.
if (typeof colours[v.toLowerCase()] != 'undefined')
if (v.toLowerCase() in colours) {
v = colours[v.toLowerCase()];
}
}
// Create a new property row with a clear button
const newPropHTML = `
<div class="property-item" style="display:flex;align-items:center;">
<button style="color:darkred;margin-right:5px;" type="button" onclick="UI.indicators.remove_prop(this)">&#10008;</button>
<span>${n}: ${v}</span>
</div>`;
// Insert the new property into the property list
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', newPropHTML);
let p={};
p[n] = v;
if (document.getElementById("new_prop_list").innerHTML ==""){
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', JSON.stringify(p));
}else{
document.getElementById("new_prop_list").insertAdjacentHTML('beforeend', ',' + JSON.stringify(p)); }
// Update the hidden property object with the new property
const propObj = JSON.parse(document.getElementById("new_prop_obj").value || '{}');
propObj[n] = v;
document.getElementById("new_prop_obj").value = JSON.stringify(propObj);
// Clear the input fields
document.getElementById("new_prop_name").value = '';
document.getElementById("new_prop_value").value = '';
}
// Function to remove a property from the list
remove_prop(buttonElement) {
const propertyDiv = buttonElement.parentElement;
const propertyText = propertyDiv.querySelector('span').textContent;
const [propName] = propertyText.split(':');
// Remove the property div from the DOM
propertyDiv.remove();
// Remove the property from the hidden input object
const propObj = JSON.parse(document.getElementById("new_prop_obj").value || '{}');
delete propObj[propName.trim()];
document.getElementById("new_prop_obj").value = JSON.stringify(propObj);
}
// Call to display Create new signal dialog.
open_form() {
// Show the form
document.getElementById("new_ind_form").style.display = "grid";
// Prefill the form fields with the current chart data (if available)
const marketField = document.querySelector('[name="ei_symbol"]');
const timeframeField = document.querySelector('[name="ei_timeframe"]');
const exchangeField = document.querySelector('[name="ei_exchange_name"]');
const marketField = document.getElementById('ei_symbol');
const timeframeField = document.getElementById('ei_timeframe');
const exchangeField = document.getElementById('ei_exchange_name');
// Set default values if fields are empty
if (!marketField.value) {
@ -553,11 +587,11 @@ class Indicators {
Used in the create indicator panel.*/
// Perform validation
const name = document.querySelector('[name="newi_name"]').value;
const type = document.querySelector('[name="newi_type"]').value;
let market = document.querySelector('[name="ei_symbol"]').value;
const timeframe = document.querySelector('[name="ei_timeframe"]').value;
const exchange = document.querySelector('[name="ei_exchange_name"]').value;
const name = document.getElementById('newi_name').value;
const type = document.getElementById('newi_type').value;
let market = document.getElementById('ei_symbol').value;
const timeframe = document.getElementById('ei_timeframe').value;
const exchange = document.getElementById('ei_exchange_name').value;
let errorMsg = '';
@ -568,8 +602,7 @@ class Indicators {
errorMsg += 'Indicator type is required.\n';
}
if (!market) {
market = window.UI.data.trading_pair;
document.querySelector('[name="ei_symbol"]').value = market; // Set the form field
errorMsg += 'Indicator market is required.\n';
}
if (!timeframe) {
errorMsg += 'Timeframe is required.\n';
@ -583,13 +616,24 @@ class Indicators {
return; // Stop form submission if there are errors
}
// If validation passes, proceed with form submission
let pl = document.getElementById("new_prop_list").innerHTML;
if (pl) {
pl = '[' + pl + ']';
}
document.getElementById("new_prop_obj").value = pl;
document.getElementById("new_i_form").submit();
// Collect and update properties
const propObj = {}
// Optionally add name, type, market, timeframe, and exchange to the properties if needed
propObj["name"] = name;
propObj["type"] = type;
propObj["source"] = {'market':market,'timeframe': timeframe,'exchange':exchange};
propObj["properties"] = document.getElementById("new_prop_obj").value;
// Call comms to send data to the server
this.comms.submitIndicator(propObj).then(response => {
if (response.success) {
window.location.reload(); // This triggers a full page refresh
} else {
alert('Failed to create a new Indicator.');
}
});
}
}

View File

@ -6,55 +6,41 @@
<div class="section" style="grid-column: 1;">
<button class="btn" onclick="UI.indicators.open_form()">New Indicator</button>
</div>
<!-- Button for displaying indicator options -->
<div class="section" style="grid-column: 2;">
<button class="btn">Indicator Options</button>
</div>
</div>
<!-- Section for displaying the list of indicators -->
<div class="section" style="margin-top: 15px; overflow-x: auto; display: grid; grid-auto-rows: minmax(80px, auto); grid-template-columns: 75px 200px 150px 100px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<!-- Background color change for this section -->
<div style="background-color: #F7E1C1; grid-column: 1 / span 8; padding: 5px 0; display: grid; grid-template-columns: 75px 200px 150px 100px 75px 100px 100px auto; gap: 10px;">
<!-- Indicator list display section -->
<div class="section" style="margin-top: 15px; overflow-x: auto; display: grid; grid-auto-rows: minmax(80px, auto); grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<div style="background-color: #F7E1C1; grid-column: 1 / span 9; padding: 5px 0; display: grid; grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px;">
<div style="text-align: center;"><h3 class="header-text">Remove or Edit</h3></div>
<div style="text-align: center;"><h3 class="header-text">Name</h3></div>
<div style="text-align: center;"><h3 class="header-text">Type</h3></div>
<div style="text-align: center;"><h3 class="header-text">Value</h3></div>
<div style="text-align: center;"><h3 class="header-text">Type</h3></div>
<div style="text-align: center;"><h3 class="header-text">Source</h3></div>
<div style="text-align: center;"><h3 class="header-text">Visible</h3></div>
<div style="text-align: center;"><h3 class="header-text">Period</h3></div>
<div style="text-align: center;"><h3 class="header-text">Color</h3></div>
<div style="text-align: center;"><h3 class="header-text">Properties</h3></div>
<hr style="grid-column: 1 / span 8;width: 100%; height: 1px; border: 1px solid #ccc; margin: 5px 0;">
<hr style="grid-column: 1 / span 9; width: 100%; height: 1px; border: 1px solid #ccc; margin: 5px 0;">
</div>
<!-- Loop through each indicator in indicator_list -->
{% for indicator in indicator_list %}
<div class="indicator-row" style="display: grid; grid-column: 1 / span 8; align-items: center; grid-template-columns: 75px 200px 150px 100px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<div class="indicator-row" style="display: grid; grid-column: 1 / span 9; align-items: center; grid-template-columns: 75px 200px 100px 150px 150px 75px 100px 100px auto; gap: 10px; padding-left: 10px;">
<!-- Buttons to delete or update the indicator -->
<!-- Edit and Remove buttons -->
<div style="text-align: center;">
<button type="button" class="e_btn" onclick="UI.indicators.deleteIndicator('{{indicator}}', event)">&#10008;</button>
<button type="button" class="e_btn" style="color:darkgreen;" onclick="UI.indicators.updateIndicator(event)">&#x2714;</button>
<button type="button" class="e_btn edit" onclick="UI.indicators.updateIndicator(event)">&#x2714;</button>
</div>
<!-- Display the indicator name -->
<!-- Indicator Name -->
<div style="text-align: center;">{{indicator}}</div>
<!-- Fixed property: Type (Dropdown) -->
<div style="text-align: center;">
{% if 'type' in indicator_list[indicator] %}
<select class="ietextbox" id="{{indicator}}_type" name="type">
{% for i_type in indicator_types %}
<option value="{{i_type}}" {% if indicator_list[indicator]['type'] == i_type %} selected="selected"{% endif %}>{{i_type}}</option>
{% endfor %}
</select>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Fixed property: Value (Readonly Number) -->
<!-- Fixed property: Value -->
<div style="text-align: center;">
{% if 'value' in indicator_list[indicator] %}
<input class="ie_value" type="number" id="{{indicator}}_value" value="{{indicator_list[indicator]['value']}}" name="value" readonly>
@ -63,16 +49,49 @@
{% endif %}
</div>
<!-- Fixed property: Visible (Checkbox) -->
<!-- Fixed property: Type -->
<div style="text-align: center;">
{% if 'visible' in indicator_list[indicator] %}
<input class="ietextbox" type="checkbox" id="{{indicator}}_visible" value="{{indicator_list[indicator]['visible']}}" name="visible" {% if indicator in checked %} checked {% endif %}>
{% if 'type' in indicator_list[indicator] %}
<span id="{{indicator}}_type">{{indicator_list[indicator]['type']}}</span>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Fixed property: Period (Number Input) -->
<!-- Source fields for Symbol, Timeframe, Exchange -->
<div style="text-align: center;">
<input list="symbols" class="ietextbox" id="{{indicator}}_source_symbol" name="market" value="{{indicator_list[indicator]['source']['market']}}">
<datalist id="symbols">
{% for symbol in symbols %}
<option value="{{symbol}}"></option>
{% endfor %}
</datalist>
<input list="timeframes" class="ietextbox" id="{{indicator}}_source_timeframe" name="timeframe" value="{{indicator_list[indicator]['source']['timeframe']}}">
<datalist id="timeframes">
{% for timeframe in intervals %}
<option value="{{timeframe}}"></option>
{% endfor %}
</datalist>
<input list="exchanges" class="ietextbox" id="{{indicator}}_source_exchange_name" name="exchange" value="{{indicator_list[indicator]['source']['exchange']}}">
<datalist id="exchanges">
{% for exchange in exchanges %}
<option value="{{exchange}}"></option>
{% endfor %}
</datalist>
</div>
<!-- Checkbox for Visibility -->
<div style="text-align: center;">
{% if 'visible' in indicator_list[indicator] %}
<input type="checkbox" id="{{indicator}}_visible" value="{{indicator_list[indicator]['visible']}}" name="visible" {% if indicator in checked %} checked {% endif %}>
{% else %}
<span>-</span>
{% endif %}
</div>
<!-- Period field -->
<div style="text-align: center;">
{% if 'period' in indicator_list[indicator] %}
<input class="ietextbox" type="number" id="{{indicator}}_period" value="{{indicator_list[indicator]['period']}}" name="period">
@ -81,7 +100,7 @@
{% endif %}
</div>
<!-- Fixed property: Color (Color Picker) -->
<!-- Color Picker -->
<div style="text-align: center;">
{% if 'color' in indicator_list[indicator] %}
<input class="ietextbox" type="color" id="{{indicator}}_color" value="{{indicator_list[indicator]['color']}}" name="color">
@ -90,17 +109,15 @@
{% endif %}
</div>
<!-- Dynamic properties (remaining ones not covered by known properties) -->
<!-- Dynamic properties -->
<div style="grid-column: span 1; text-align: left; display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;">
{% for property, value in indicator_list[indicator].items() %}
{% if property not in ['type', 'value', 'color', 'period', 'visible'] %}
{% if property not in ['type', 'value', 'color', 'period', 'visible', 'source'] %}
<div style="margin-bottom: 17px;">
<label for="{{indicator}}_{{property}}" class="ietextbox-label">{{property}}</label>
{% if 'color' in property %}
<!-- Color picker for properties with 'color' in the name -->
<input class="ietextbox" type="color" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
{% else %}
<!-- Regular text input for other properties -->
<input class="ietextbox" type="text" id="{{indicator}}_{{property}}" value="{{value}}" name="{{property}}">
{% endif %}
</div>
@ -111,29 +128,68 @@
</div>
{% endfor %}
</div>
</div>
<!-- Styles -->
<style>
.section .column {
padding: 10px;
max-width: 200px; /* Set max-width for all columns */
/* General shadow styling for all elements except checkboxes */
.ietextbox, .e_btn, input[type="color"] {
box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.3);
transition: all 0.3s ease;
}
.section h3 {
margin-bottom: 0;
margin-top: 0;
/* Style for Edit and Remove buttons */
.e_btn {
display: flex;
align-items: center;
justify-content: center;
border-radius: 10px;
padding: 8px 12px;
background-color: #f44336; /* Red for remove */
color: white;
border: 1px solid black;
cursor: pointer;
}
.section .buttons-column {
max-width: 75px; /* Buttons column */
/* Green background for Edit button */
.e_btn.edit {
background-color: #4CAF50;
}
.section .visible-column {
max-width: 75px; /* Visible checkbox column */
/* Hover effect for buttons */
.e_btn:hover {
opacity: 0.9;
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.15);
}
/* Alternate row background colors */
.e_btn:focus {
outline: none;
}
/* Color picker input */
input[type="color"] {
border: 1px solid black;
padding: 5px;
width: 60px;
height: 40px;
border-radius: 17px;
box-sizing: border-box;
}
/* Hover effect for color picker */
input[type="color"]:hover {
box-shadow: 0px 6px 8px rgba(0, 0, 0, 0.15);
}
/* Styling for input fields and textboxes */
.ietextbox {
width: 100%;
padding: 5px;
border-radius: 15px;
box-sizing: border-box;
}
/* Alternating row colors */
.indicator-row:nth-child(even) {
background-color: #E0E0E0;
}
@ -142,14 +198,42 @@
background-color: #F7E1C1;
}
/* Bold labels with drop shadow for dynamic properties */
.ietextbox-label {
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
/* Opposite colors for textboxes */
.indicator-row:nth-child(even) .ietextbox {
background-color: #F7E1C1;
}
/* Drop shadow for header text */
.header-text {
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.3);
.indicator-row:nth-child(odd) .ietextbox {
background-color: #E0E0E0;
}
input[type="checkbox"] {
width: 20px; /* Increase the size of the checkbox */
height: 20px; /* Adjust the height to match */
cursor: pointer; /* Add a pointer cursor for better UX */
margin: 0; /* Reset margin */
}
</style>
<!-- JavaScript -->
<script>
document.addEventListener('DOMContentLoaded', function() {
// Handle focus and restore behavior for each dropdown
document.querySelectorAll('.ietextbox').forEach(function(input) {
let originalValue = input.value;
// Clear the input when focused
input.addEventListener('focus', function() {
originalValue = input.value;
input.value = ''; // Clear the input
});
// Restore the original value if no new selection is made
input.addEventListener('blur', function() {
if (!input.value) {
input.value = originalValue; // Restore the original value if input is empty
}
});
});
});
</script>

View File

@ -1,66 +1,81 @@
<div class="form-popup" id="new_ind_form" style="overflow: auto;">
<form id="new_i_form" action="/settings" method="POST" class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;">
<!-- Panel 1 of 1 (5 rows, 2 columns) -->
<div id="indicator_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(5,1fr); gap: 10px;">
<div class="form-popup" id="new_ind_form" style="overflow: auto;">
<div id="new_i_form" class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;">
<!-- Panel 1 of 1 (5 rows, 2 columns) -->
<div id="indicator_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(5,1fr); gap: 10px;">
<!-- Panel title (row 1/5)-->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Indicator</h1>
<!-- Panel title (row 1/5)-->
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Indicator</h1>
<input type="hidden" name="setting" value="new_indicator"/>
<!-- Create indicator div container --!>
<div style="grid-column: 1 / span 2; grid-row: 2 / span 3;">
<label for "newi_name">Indicator Name</label><input type ="text" name="newi_name" value="New Indicator">
<label for "newi_type">Type</label>
<select id="newi_type" name="newi_type">
{% for i_type in indicator_types %}
<option value="{{i_type}}">{{i_type}}</option>
{% endfor %}
</select>
<br><br>
<span>Properties: <span id="new_prop_list"></span></span>
<button name="add_prop_clear" id="add_prop_clear" style="color:darkred;" type="button" value="add_prop_clear" onclick="document.getElementById('new_prop_list').innerHTML=''">&#10008;</button>
<!-- Create indicator div container -->
<div style="grid-column: 1 / span 2; grid-row: 2 / span 3;">
<label for="newi_name">Indicator Name</label><input type="text" id="newi_name" value="New Indicator">
<label for="newi_type">Type</label>
<select id="newi_type">
{% for i_type in indicator_types %}
<option value="{{i_type}}">{{i_type}}</option>
{% endfor %}
</select>
<br><br>
<span>Properties: <span id="new_prop_list"></span></span>
<br><br>
<div id="add_prop_container" name="add_prop_container">
<label for "new_prop_name">Property Name</label>
<input type="text" id="new_prop_name" name="new_prop_name" value="name">
<br>
<label for "new_prop_value">Property Value</label>
<input type="text" id="new_prop_value" name="new_prop_value" value="value">
<button name="add_prop" id="add_prop" type="button" value="add_prop" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">&#10009;</button>
<label for "add_prop">Add Property</label>
<br><br>
<div id="add_prop_container">
<label for="new_prop_name">Property Name</label>
<input type="text" id="new_prop_name" name="new_prop_name" list="default_properties" value="name" onclick="this.value='';">
<datalist id="default_properties">
<option value="period">
<option value="devup">
<option value="devdn">
<option value="ma">
<option value="fast_p">
<option value="slow_p">
<option value="signal_p">
<option value="color">
<option value="color_1">
<option value="color_2">
<option value="color_3">
</datalist>
<br>
<label for="new_prop_value">Property Value</label>
<input type="text" id="new_prop_value" name="new_prop_value" value="value" onclick="this.value='';">
<button id="add_prop" type="button" style="color:darkgreen;" onclick="UI.indicators.add_to_list()">&#10009;</button>
<label for="add_prop">Add Property</label>
</div>
<br>
</div>
<br>
<!-- Trading pair selector -->
<label for="ei_symbols" >Trading Pair</label>
<input list="ei_symbols" name="ei_symbol" id="ei_symbol" style="width: 96px;">
<datalist id="ei_symbols">
{% for symbol in symbols %}
<option>{{ symbol }}</option>
{% endfor %}
</datalist>
<!-- Time timeframe selector -->
<select id="ei_timeframe" name="ei_timeframe">
{% for time_frame in intervals %}
<option {% if time_frame == interval_state %} selected="selected" {%endif%}>{{ time_frame }}</option>
{% endfor %}
</select>
<!-- Exchange selector -->
<select id="ei_exchange_name" name="ei_exchange_name">
{% for exchange in exchanges %}
<option {% if exchange == selected_exchange %} selected="selected" {%endif%}>{{ exchange }}</option>
{% endfor %}
</select>
<input type="hidden" id="new_prop_obj" name="new_prop_obj" value="">
</div>
<!-- Trading pair selector -->
<label for="ei_symbols">Trading Pair</label>
<input list="ei_symbols" id="ei_symbol" style="width: 96px;">
<datalist id="ei_symbols">
{% for symbol in symbols %}
<option value="{{symbol}}"></option>
{% endfor %}
</datalist>
<!-- Buttons (row 5/5)-->
<div style="grid-column: 1 / span 2; grid-row: 5; text-align: center;">
<button type="button" class="btn cancel" onclick="UI.indicators.close_form()">Close</button>
<button type="button" class="btn submit" onclick="UI.indicators.submit_new_i()">Create Indicator</button>
</div>
<!-- Time timeframe selector -->
<select id="ei_timeframe">
{% for time_frame in intervals %}
<option {% if time_frame == interval_state %} selected="selected" {% endif %}>{{ time_frame }}</option>
{% endfor %}
</select>
<!-- Exchange selector -->
<select id="ei_exchange_name">
{% for exchange in exchanges %}
<option {% if exchange == selected_exchange %} selected="selected" {% endif %}>{{ exchange }}</option>
{% endfor %}
</select>
<input type="hidden" id="new_prop_obj" value="">
</div>
<!-- Buttons (row 5/5) -->
<div style="grid-column: 1 / span 2; grid-row: 5; text-align: center;">
<button type="button" class="btn cancel" onclick="UI.indicators.close_form()">Close</button>
<button type="button" class="btn submit" onclick="UI.indicators.submit_new_i()">Create Indicator</button>
</div>
</div><!----End panel 1--------->
</form>
</div>
<!-- End panel 1 -->
</div>
</div>