The strategy popup is now draggable and resizeable.
This commit is contained in:
parent
034b8ab925
commit
4e3e8e5abf
|
|
@ -322,12 +322,31 @@ class BrighterTrades:
|
|||
:param data: A dictionary containing the attributes of the new strategy.
|
||||
:return: An error message if the required attribute is missing, or the incoming data for chaining on success.
|
||||
"""
|
||||
if 'name' not in data:
|
||||
return "The new strategy must have a 'name' attribute."
|
||||
# Extract user_name from the data and get user_id
|
||||
user_name = data.get('user_name')
|
||||
if not user_name:
|
||||
return {"success": False, "message": "User not specified"}
|
||||
|
||||
self.strategies.new_strategy(data)
|
||||
self.config.set_setting('strategies', self.strategies.get_strategies('dict'))
|
||||
return data
|
||||
# Fetch the user_id using the user_name
|
||||
user_id = self.get_user_info(user_name=user_name, info='User_id')
|
||||
if not user_id:
|
||||
return {"success": False, "message": "User ID not found"}
|
||||
|
||||
# Prepare the strategy data for insertion
|
||||
strategy_data = {
|
||||
"creator": user_id,
|
||||
"name": data['name'],
|
||||
"workspace": data['workspace'],
|
||||
"code": data['code'],
|
||||
"stats": data.get('stats', {}),
|
||||
"public": data.get('public', 0), # Default to private if not specified
|
||||
"fee": data.get('fee', None) # Default to None if not specified
|
||||
}
|
||||
|
||||
# Save the new strategy (in both cache and database)
|
||||
self.strategies.new_strategy(strategy_data)
|
||||
|
||||
return {"success": True, "message": "Strategy created successfully", "data": strategy_data}
|
||||
|
||||
def delete_strategy(self, strategy_name: str) -> None:
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
from DataCache_v2 import DataCache
|
||||
from DataCache_v3 import DataCache
|
||||
import datetime as dt
|
||||
|
||||
|
||||
class Strategy:
|
||||
|
|
@ -169,59 +170,90 @@ class Strategy:
|
|||
|
||||
class Strategies:
|
||||
def __init__(self, data: DataCache, trades):
|
||||
# Handles database connections
|
||||
self.data = data
|
||||
"""
|
||||
Initializes the Strategies class.
|
||||
|
||||
# Reference to the trades object that maintains all trading actions and data.
|
||||
:param data: Instance of DataCache to manage cache and database interactions.
|
||||
:param trades: Reference to the trades object that maintains trading actions and data.
|
||||
"""
|
||||
self.data = data # Database interaction instance
|
||||
self.trades = trades
|
||||
self.strat_list = [] # List to hold strategy objects
|
||||
|
||||
self.strat_list = []
|
||||
# Create a cache for strategies with necessary columns
|
||||
self.data.create_cache(name='strategies',
|
||||
cache_type='table',
|
||||
size_limit=100,
|
||||
eviction_policy='deny',
|
||||
default_expiration=dt.timedelta(hours=24),
|
||||
columns=["id", "creator", "name", "workspace", "code", "stats", "public", "fee"])
|
||||
|
||||
def get_all_strategy_names(self) -> list | None:
|
||||
"""Return a list of all strategies in the database"""
|
||||
# # Load existing Strategies from file.
|
||||
# loaded_strategies = self.data.get_setting('strategies')
|
||||
# if loaded_strategies is None:
|
||||
# # Populate the list and file with defaults defined in this class.
|
||||
# loaded_strategies = self.get_strategy_defaults()
|
||||
# config.set_setting('strategies', loaded_strategies)
|
||||
#
|
||||
# for entry in loaded_strategies:
|
||||
# # Initialise all the strategy objects with data from file.
|
||||
# self.strat_list.append(Strategy(**entry))
|
||||
def new_strategy(self, data: dict):
|
||||
"""
|
||||
Add a new strategy to the cache and database.
|
||||
|
||||
return None
|
||||
|
||||
def new_strategy(self, data):
|
||||
# Create an instance of the new Strategy.
|
||||
:param data: A dictionary containing strategy data such as name, code, and workspace.
|
||||
"""
|
||||
# Create a new Strategy object and add it to the list
|
||||
self.strat_list.append(Strategy(**data))
|
||||
|
||||
def delete_strategy(self, name):
|
||||
# Insert the strategy into the database and cache
|
||||
self.data.insert_row_into_datacache(
|
||||
cache_name='strategies',
|
||||
columns=("creator", "name", "workspace", "code", "stats", "public", "fee"),
|
||||
values=(data.get('creator'), data['name'], data['workspace'], data['code'], data.get('stats', {}),
|
||||
data.get('public', False), data.get('fee', 0))
|
||||
)
|
||||
|
||||
def delete_strategy(self, name: str):
|
||||
"""
|
||||
Delete a strategy from the cache and database by name.
|
||||
|
||||
:param name: The name of the strategy to delete.
|
||||
"""
|
||||
obj = self.get_strategy_by_name(name)
|
||||
if obj:
|
||||
self.strat_list.remove(obj)
|
||||
|
||||
def get_strategies(self, form):
|
||||
# Return a python object of all the strategies stored in this instance.
|
||||
# Remove the strategy from cache and database
|
||||
self.data.remove_row_from_datacache(
|
||||
cache_name='strategies',
|
||||
filter_vals=[('name', name)]
|
||||
)
|
||||
|
||||
def get_all_strategy_names(self) -> list | None:
|
||||
"""
|
||||
Return a list of all strategy names stored in the cache or database.
|
||||
"""
|
||||
# Fetch all strategy names from the cache or database
|
||||
strategies_df = self.data.get_rows_from_datacache(cache_name='strategies', filter_vals=[])
|
||||
|
||||
if not strategies_df.empty:
|
||||
return strategies_df['name'].tolist()
|
||||
return None
|
||||
|
||||
def get_strategies(self, form: str):
|
||||
"""
|
||||
Return strategies stored in this instance in various formats.
|
||||
|
||||
:param form: The desired format ('obj', 'json', or 'dict').
|
||||
:return: A list of strategies in the requested format.
|
||||
"""
|
||||
if form == 'obj':
|
||||
return self.strat_list
|
||||
# Return a JSON object of all the strategies stored in this instance.
|
||||
elif form == 'json':
|
||||
strats = self.strat_list
|
||||
json_str = []
|
||||
for strat in strats:
|
||||
json_str.append(strat.to_json())
|
||||
return json_str
|
||||
# Return a dictionary object of all the strategies stored in this instance.
|
||||
return [strat.to_json() for strat in self.strat_list]
|
||||
elif form == 'dict':
|
||||
strats = self.strat_list
|
||||
s_list = []
|
||||
for st in strats:
|
||||
dic = st.__dict__
|
||||
s_list.append(dic)
|
||||
return s_list
|
||||
return [strat.__dict__ for strat in self.strat_list]
|
||||
return None
|
||||
|
||||
def get_strategy_by_name(self, name):
|
||||
def get_strategy_by_name(self, name: str):
|
||||
"""
|
||||
Retrieve a strategy object by name.
|
||||
|
||||
:param name: The name of the strategy to retrieve.
|
||||
:return: The strategy object if found, otherwise False.
|
||||
"""
|
||||
for obj in self.strat_list:
|
||||
if obj.name == name:
|
||||
return obj
|
||||
|
|
|
|||
31
src/app.py
31
src/app.py
|
|
@ -105,39 +105,46 @@ def index():
|
|||
@sock.route('/ws')
|
||||
def ws(socket_conn):
|
||||
"""
|
||||
Open a websocket to handle two-way communication with UI without browser refreshes.
|
||||
Open a WebSocket to handle two-way communication with UI without browser refreshes.
|
||||
"""
|
||||
|
||||
def json_msg_received(msg_obj):
|
||||
"""
|
||||
Handle incoming json msg's.
|
||||
Handle incoming JSON messages with authentication.
|
||||
"""
|
||||
# Validate input
|
||||
if 'message_type' not in msg_obj:
|
||||
if 'message_type' not in msg_obj or 'data' not in msg_obj:
|
||||
return
|
||||
|
||||
msg_type, msg_data = msg_obj['message_type'], msg_obj['data']
|
||||
|
||||
print(f'\n[MSG_RECEIVED]\nMSG Type:{msg_type}\n{msg_data}')
|
||||
# Extract user_name from the incoming message data
|
||||
user_name = msg_data.get('user_name')
|
||||
if not user_name:
|
||||
socket_conn.send(json.dumps({"success": False, "message": "User not specified"}))
|
||||
return
|
||||
|
||||
# Check if the user is logged in
|
||||
if not brighter_trades.get_user_info(user_name=user_name, info='Is logged in?'):
|
||||
socket_conn.send(json.dumps({"success": False, "message": "User not logged in"}))
|
||||
return
|
||||
|
||||
# Process the incoming message based on the type
|
||||
resp = brighter_trades.process_incoming_message(msg_type=msg_type, msg_data=msg_data)
|
||||
|
||||
socket_conn.send(json.dumps(resp))
|
||||
# Send the response back to the client
|
||||
if resp:
|
||||
socket_conn.send(json.dumps(resp))
|
||||
|
||||
return
|
||||
|
||||
# The rendered page connects to the exchange_interface and relays the candle data back here
|
||||
# this socket also handles data and processing requests
|
||||
# Main loop to receive messages and handle them
|
||||
while True:
|
||||
msg = socket_conn.receive()
|
||||
if msg:
|
||||
# If msg is in json, convert the message into a dictionary then feed the message handler.
|
||||
# Otherwise, log the output.
|
||||
try:
|
||||
json_msg = json.loads(msg)
|
||||
json_msg_received(json_msg)
|
||||
except json.JSONDecodeError:
|
||||
print(f'Msg received from client: {msg}')
|
||||
print(f'Msg received from client (not JSON): {msg}')
|
||||
|
||||
|
||||
@app.route('/settings', methods=['POST'])
|
||||
|
|
|
|||
|
|
@ -30,6 +30,13 @@ class Strategies {
|
|||
// Define Python generators after workspace initialization
|
||||
this.definePythonGenerators();
|
||||
}
|
||||
resizeWorkspace() {
|
||||
const blocklyDiv = document.getElementById('blocklyDiv');
|
||||
if (this.workspace) {
|
||||
// Adjust workspace dimensions to match the blocklyDiv
|
||||
Blockly.svgResize(this.workspace);
|
||||
}
|
||||
}
|
||||
// Generate Python code from the Blockly workspace and return as JSON
|
||||
generateStrategyJson() {
|
||||
// Initialize Python generator with the current workspace
|
||||
|
|
@ -38,15 +45,43 @@ class Strategies {
|
|||
// Generate Python code from the Blockly workspace
|
||||
const pythonCode = Blockly.Python.workspaceToCode(this.workspace);
|
||||
|
||||
// Create a strategy object with generated code
|
||||
// Serialize the workspace to XML format
|
||||
const workspaceXml = Blockly.Xml.workspaceToDom(this.workspace);
|
||||
const workspaceXmlText = Blockly.Xml.domToText(workspaceXml);
|
||||
|
||||
const json = {
|
||||
name: document.getElementById('name_box').value,
|
||||
code: pythonCode // This is the generated Python code
|
||||
name: document.getElementById('name_box').value,
|
||||
code: pythonCode, // This is the generated Python code
|
||||
workspace: workspaceXmlText // Serialized workspace XML
|
||||
};
|
||||
|
||||
return JSON.stringify(json);
|
||||
}
|
||||
|
||||
restoreWorkspaceFromXml(workspaceXmlText) {
|
||||
// Convert the text back into an XML DOM object
|
||||
const workspaceXml = Blockly.Xml.textToDom(workspaceXmlText);
|
||||
|
||||
// Clear the current workspace before loading a new one
|
||||
if (this.workspace) {
|
||||
this.workspace.clear();
|
||||
}
|
||||
|
||||
// Load the workspace from the XML DOM object
|
||||
Blockly.Xml.domToWorkspace(workspaceXml, this.workspace);
|
||||
}
|
||||
fetchSavedStrategies() {
|
||||
fetch('/get_strategies', {
|
||||
method: 'GET'
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.strategies = data.strategies;
|
||||
this.update_html();
|
||||
})
|
||||
.catch(error => console.error('Error fetching strategies:', error));
|
||||
}
|
||||
|
||||
// Show the "Create New Strategy" form (open the workspace)
|
||||
open_form() {
|
||||
const formElement = document.getElementById("new_strat_form");
|
||||
|
|
@ -70,23 +105,40 @@ class Strategies {
|
|||
}
|
||||
}
|
||||
|
||||
// Submit the strategy and send it to the server
|
||||
submit() {
|
||||
// Generate the Python code as JSON
|
||||
const strategyJson = this.generateStrategyJson();
|
||||
|
||||
// Send the strategy to the server
|
||||
fetch('/new_strategy', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: strategyJson
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => console.log('Strategy submitted successfully:', data))
|
||||
.catch(error => console.error('Error submitting strategy:', error));
|
||||
// Get the fee and public checkbox values
|
||||
const fee = parseFloat(document.getElementById('fee_box').value) || 0;
|
||||
if (fee < 0) {
|
||||
alert("Fee cannot be negative");
|
||||
return;
|
||||
}
|
||||
|
||||
const is_public = document.getElementById('public_checkbox').checked ? 1 : 0;
|
||||
|
||||
// Merge additional fields into the strategy data
|
||||
const strategyData = {
|
||||
...JSON.parse(strategyJson), // Spread the existing strategy JSON data
|
||||
fee, // Add the fee
|
||||
public: is_public // Add the public status
|
||||
};
|
||||
|
||||
// Send the strategy to the server via WebSocket through the Comms class
|
||||
if (window.UI.data.comms) { // Assuming the Comms instance is accessible via window.UI.data.comms
|
||||
window.UI.data.comms.sendToApp('new_strategy', strategyData);
|
||||
} else {
|
||||
console.error("Comms instance not available.");
|
||||
}
|
||||
}
|
||||
|
||||
// Update the display of the created strategies
|
||||
toggleFeeBox() {
|
||||
const publicCheckbox = document.getElementById('public_checkbox');
|
||||
const feeBox = document.getElementById('fee_box');
|
||||
feeBox.disabled = !publicCheckbox.checked; // Enable feeBox only if publicCheckbox is checked
|
||||
}
|
||||
// Update the UI with saved strategies
|
||||
update_html() {
|
||||
let stratsHtml = '';
|
||||
const onClick = "window.UI.strats.del(this.value);";
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
class Comms {
|
||||
constructor() {
|
||||
this.connectionOpen = false;
|
||||
this.appCon = null; // WebSocket connection for app communication
|
||||
|
||||
// Callback collections that will receive various updates.
|
||||
this.candleUpdateCallbacks = [];
|
||||
|
|
@ -170,33 +172,55 @@ class Comms {
|
|||
}
|
||||
|
||||
/**
|
||||
* Sends a message to the application server.
|
||||
* Sends a message to the application server via WebSocket.
|
||||
* Automatically includes the user_name from `window.UI.data.user_name` for authentication.
|
||||
* @param {string} messageType - The type of the message.
|
||||
* @param {Object} data - The data to be sent with the message.
|
||||
*/
|
||||
sendToApp(messageType, data) {
|
||||
console.log('window.UI.data.comms.sendToApp(): Sending->');
|
||||
console.log(JSON.stringify({ message_type: messageType, data: data }));
|
||||
const user_name = window.UI.data.user_name; // Access user_name from window.UI.data
|
||||
|
||||
if (!user_name) {
|
||||
console.error('User not logged in. Cannot send message.');
|
||||
return;
|
||||
}
|
||||
|
||||
const messageData = {
|
||||
message_type: messageType,
|
||||
data: {
|
||||
...data, // Include the existing data
|
||||
user_name: user_name // Add user_name for authentication
|
||||
}
|
||||
};
|
||||
|
||||
console.log('Comms: Sending->', JSON.stringify(messageData));
|
||||
|
||||
if (this.connectionOpen) {
|
||||
this.appCon.send(JSON.stringify({ message_type: messageType, data: data }));
|
||||
this.appCon.send(JSON.stringify(messageData));
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
window.UI.data.comms.appCon.send(JSON.stringify({ message_type: messageType, data: data }));
|
||||
if (this.appCon) {
|
||||
this.appCon.send(JSON.stringify(messageData));
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up the WebSocket connection to the application server.
|
||||
*/
|
||||
setAppCon() {
|
||||
this.appCon = new WebSocket('ws://localhost:5000/ws');
|
||||
|
||||
// On connection open
|
||||
this.appCon.onopen = () => {
|
||||
console.log("WebSocket connection established");
|
||||
this.appCon.send("Connection OK");
|
||||
this.connectionOpen = true;
|
||||
};
|
||||
|
||||
// Handle incoming messages
|
||||
this.appCon.addEventListener('message', (event) => {
|
||||
if (event.data) {
|
||||
const message = JSON.parse(event.data);
|
||||
|
|
@ -207,24 +231,29 @@ class Comms {
|
|||
}
|
||||
|
||||
if (message && message.reply !== undefined) {
|
||||
// Handle different reply types from the server
|
||||
if (message.reply === 'updates') {
|
||||
const { i_updates, s_updates, stg_updts, trade_updts } = message.data;
|
||||
|
||||
// Handle indicator updates
|
||||
if (i_updates) {
|
||||
this.indicatorUpdate(i_updates);
|
||||
window.UI.signals.i_update(i_updates);
|
||||
}
|
||||
|
||||
// Handle signal updates
|
||||
if (s_updates) {
|
||||
const updates = s_updates;
|
||||
window.UI.signals.update_signal_states(updates);
|
||||
window.UI.alerts.publish_alerts('signal_changes', updates);
|
||||
window.UI.signals.update_signal_states(s_updates);
|
||||
window.UI.alerts.publish_alerts('signal_changes', s_updates);
|
||||
}
|
||||
|
||||
// Handle strategy updates
|
||||
if (stg_updts) {
|
||||
const stg_updts = stg_updts;
|
||||
window.UI.strats.update_received(stg_updts);
|
||||
}
|
||||
|
||||
// Handle trade updates
|
||||
if (trade_updts) {
|
||||
const trade_updts = trade_updts;
|
||||
window.UI.trade.update_received(trade_updts);
|
||||
}
|
||||
} else if (message.reply === 'signals') {
|
||||
|
|
@ -248,8 +277,20 @@ class Comms {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// On connection close
|
||||
this.appCon.onclose = () => {
|
||||
console.log("WebSocket connection closed");
|
||||
this.connectionOpen = false;
|
||||
};
|
||||
|
||||
// On WebSocket error
|
||||
this.appCon.onerror = (error) => {
|
||||
console.error("WebSocket error:", error);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets up a WebSocket connection to the exchange for receiving candlestick data.
|
||||
* @param {string} interval - The interval of the candlestick data.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
class User_Interface {
|
||||
constructor() {
|
||||
// Initialize all components needed by the user interface
|
||||
this.strats = new Strategies('strats_display'); // Handles strategies display and interaction
|
||||
this.exchanges = new Exchanges(); // Manages exchange-related data
|
||||
this.data = new Data(); // Stores and maintains application-wide data
|
||||
this.controls = new Controls(); // Handles user controls
|
||||
this.signals = new Signals(this.data.indicators); // Manages signals used in strategies
|
||||
this.alerts = new Alerts("alert_list"); // Manages the alerts system
|
||||
this.trade = new Trade(); // Manages trade-related data and operations
|
||||
this.users = new Users(); // Handles user login and account details
|
||||
this.indicators = new Indicators(this.data.comms); // Manages indicators and interaction with charts
|
||||
this.strats = new Strategies('strats_display');
|
||||
this.exchanges = new Exchanges();
|
||||
this.data = new Data();
|
||||
this.controls = new Controls();
|
||||
this.signals = new Signals(this.data.indicators);
|
||||
this.alerts = new Alerts("alert_list");
|
||||
this.trade = new Trade();
|
||||
this.users = new Users();
|
||||
this.indicators = new Indicators(this.data.comms);
|
||||
|
||||
// Register a callback function for when indicator updates are received from the data object
|
||||
this.data.registerCallback_i_updates(this.indicators.update);
|
||||
|
|
@ -18,41 +18,124 @@ class User_Interface {
|
|||
this.initializeAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes all components after the DOM has loaded.
|
||||
* This includes initializing charts, signals, strategies, and other interface components.
|
||||
* Components that rely on external libraries like Blockly are initialized after they are ready.
|
||||
*/
|
||||
initializeAll() {
|
||||
// Use an arrow function to preserve the value of 'this' (User_Interface instance)
|
||||
window.addEventListener('load', () => {
|
||||
// Initialize the Charts component with the required data
|
||||
let chart_init_data = {
|
||||
chart1_id: this.data.chart1_id, // ID of the first chart element
|
||||
chart2_id: this.data.chart2_id, // ID of the second chart element
|
||||
chart3_id: this.data.chart3_id, // ID of the third chart element
|
||||
trading_pair: this.data.trading_pair, // Active trading pair
|
||||
price_history: this.data.price_history // Historical price data
|
||||
};
|
||||
this.charts = new Charts(chart_init_data); // Initialize the charts
|
||||
|
||||
// Initialize indicators and link them to the charts
|
||||
let ind_init_data = {
|
||||
indicators: this.data.indicators, // List of indicators
|
||||
indicator_data: this.data.indicator_data // Data for rendering indicators
|
||||
};
|
||||
this.indicators.addToCharts(this.charts, ind_init_data); // Add indicators to the charts
|
||||
|
||||
// Initialize various components that don't depend on external libraries
|
||||
this.signals.request_signals(); // Request and initialize trading signals
|
||||
this.alerts.set_target(); // Set up alert notifications
|
||||
this.controls.init_TP_selector(); // Initialize trade parameter selectors
|
||||
this.trade.initialize(); // Initialize trade-related data and UI elements
|
||||
this.exchanges.initialize(); // Initialize exchange-related data
|
||||
|
||||
this.strats.initialize(); // Initialize strategies once Blockly is ready
|
||||
this.initializeChartsAndIndicators();
|
||||
this.initializeResizablePopup("new_strat_form", this.strats.resizeWorkspace.bind(this.strats));
|
||||
});
|
||||
}
|
||||
|
||||
initializeChartsAndIndicators() {
|
||||
let chart_init_data = {
|
||||
chart1_id: this.data.chart1_id,
|
||||
chart2_id: this.data.chart2_id,
|
||||
chart3_id: this.data.chart3_id,
|
||||
trading_pair: this.data.trading_pair,
|
||||
price_history: this.data.price_history
|
||||
};
|
||||
this.charts = new Charts(chart_init_data);
|
||||
|
||||
let ind_init_data = {
|
||||
indicators: this.data.indicators,
|
||||
indicator_data: this.data.indicator_data
|
||||
};
|
||||
this.indicators.addToCharts(this.charts, ind_init_data);
|
||||
|
||||
this.signals.request_signals();
|
||||
this.alerts.set_target();
|
||||
this.controls.init_TP_selector();
|
||||
this.trade.initialize();
|
||||
this.exchanges.initialize();
|
||||
this.strats.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make a popup resizable, and optionally pass a resize callback (like for Blockly workspaces).
|
||||
* @param {string} popupId - The ID of the popup to make resizable.
|
||||
* @param {function|null} resizeCallback - Optional callback to run when resizing (for Blockly or other elements).
|
||||
*/
|
||||
initializeResizablePopup(popupId, resizeCallback = null) {
|
||||
const popupElement = document.getElementById(popupId);
|
||||
this.dragElement(popupElement);
|
||||
this.makeResizable(popupElement, resizeCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an element draggable by dragging its header.
|
||||
* @param {HTMLElement} elm - The element to make draggable.
|
||||
*/
|
||||
dragElement(elm) {
|
||||
const header = document.getElementById("draggable_header");
|
||||
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||||
|
||||
if (header) {
|
||||
header.onmousedown = dragMouseDown;
|
||||
} else {
|
||||
elm.onmousedown = dragMouseDown;
|
||||
}
|
||||
|
||||
function dragMouseDown(e) {
|
||||
e.preventDefault();
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
document.onmouseup = closeDragElement;
|
||||
document.onmousemove = elementDrag;
|
||||
}
|
||||
|
||||
function elementDrag(e) {
|
||||
e.preventDefault();
|
||||
pos1 = pos3 - e.clientX;
|
||||
pos2 = pos4 - e.clientY;
|
||||
pos3 = e.clientX;
|
||||
pos4 = e.clientY;
|
||||
elm.style.top = (elm.offsetTop - pos2) + "px";
|
||||
elm.style.left = (elm.offsetLeft - pos1) + "px";
|
||||
}
|
||||
|
||||
function closeDragElement() {
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make an element resizable and optionally call a resize callback (like for Blockly workspace).
|
||||
* @param {HTMLElement} elm - The element to make resizable.
|
||||
* @param {function|null} resizeCallback - Optional callback to resize specific content.
|
||||
*/
|
||||
makeResizable(elm, resizeCallback = null) {
|
||||
const resizer = document.getElementById("resize-br");
|
||||
let originalWidth = 0;
|
||||
let originalHeight = 0;
|
||||
let originalMouseX = 0;
|
||||
let originalMouseY = 0;
|
||||
|
||||
resizer.addEventListener('mousedown', function(e) {
|
||||
e.preventDefault();
|
||||
originalWidth = parseFloat(getComputedStyle(elm, null).getPropertyValue('width').replace('px', ''));
|
||||
originalHeight = parseFloat(getComputedStyle(elm, null).getPropertyValue('height').replace('px', ''));
|
||||
originalMouseX = e.pageX;
|
||||
originalMouseY = e.pageY;
|
||||
|
||||
window.addEventListener('mousemove', resize);
|
||||
window.addEventListener('mouseup', stopResize);
|
||||
});
|
||||
|
||||
const resize = (e) => {
|
||||
elm.style.width = originalWidth + (e.pageX - originalMouseX) + 'px';
|
||||
elm.style.height = originalHeight + (e.pageY - originalMouseY) + 'px';
|
||||
|
||||
// Optionally call the resize callback (for Blockly or other resizable components)
|
||||
if (resizeCallback) {
|
||||
resizeCallback();
|
||||
}
|
||||
};
|
||||
|
||||
const stopResize = () => {
|
||||
window.removeEventListener('mousemove', resize);
|
||||
window.removeEventListener('mouseup', stopResize);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the User_Interface class and assign it to a global variable for access in other parts of the app
|
||||
|
|
|
|||
|
|
@ -1,24 +1,111 @@
|
|||
<!-- Strategy Form Popup -->
|
||||
<div class="form-popup" id="new_strat_form" style="display: none; overflow: auto;">
|
||||
<form class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto;">
|
||||
<!-- Panel 1 of 1 -->
|
||||
<div id="strat_pan_1" class="form_panels" style="display: grid; grid-template-columns:repeat(2,1fr); grid-template-rows: repeat(6,1fr); gap: 10px;">
|
||||
<!-- Panel title -->
|
||||
<h1 style="grid-column: 1 / span 2; grid-row: 1;">Create New Strategy</h1>
|
||||
<div style="grid-column: 1 / span 2; grid-row: 2;">Name: <input class="ietextbox" type="text" id="name_box" name="name_box" value="Indicator name"></div>
|
||||
<!-- Strategy Form Popup -->
|
||||
<div class="form-popup" id="new_strat_form" style="display: none; overflow: hidden; position: absolute; width: 400px; height: 640px; border-radius: 10px;">
|
||||
|
||||
<!-- Blockly workspace -->
|
||||
<div id="blocklyDiv" style="grid-column: 1 / span 2; grid-row: 3 / span 3; height: 300px; width: 100%;"></div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div style="grid-column: 1 / span 2; grid-row: 6; text-align: center;">
|
||||
<button type="button" class="btn cancel" onclick="UI.strats.close_form()">Close</button>
|
||||
<button type="button" class="btn next" onclick="UI.strats.submit()">Create Strategy</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<!-- Draggable Header Section -->
|
||||
<div id="draggable_header" style="cursor: move; padding: 10px; background-color: #3E3AF2; color: white; border-bottom: 1px solid #ccc;">
|
||||
<h1 style="margin: 0;">Create New Strategy</h1>
|
||||
</div>
|
||||
|
||||
<!-- Main Content (Scrollable) -->
|
||||
<form class="form-container" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto; height: calc(100% - 60px); overflow-y: auto;">
|
||||
<!-- Panel 1 of 1 -->
|
||||
<div id="strat_pan_1" class="form_panels" style="display: grid; grid-template-columns: 1fr; grid-template-rows: auto auto; gap: 10px; padding: 10px;">
|
||||
|
||||
<!-- Blockly workspace -->
|
||||
<div id="blocklyDiv" style="grid-column: 1; height: 300px; width: 100%;"></div>
|
||||
|
||||
<!-- Name of the strategy -->
|
||||
<div style="grid-column: 1;">
|
||||
<label for="name_box" style="display: inline-block; width: 20%;">Name:</label>
|
||||
<input class="ietextbox" type="text" id="name_box" name="name_box" value="Indicator name" style="width: 75%; display: inline-block;">
|
||||
</div>
|
||||
|
||||
<!-- Public checkbox -->
|
||||
<div style="grid-column: 1;">
|
||||
<label for="public_checkbox" style="display: inline-block; width: 20%;">Public:</label>
|
||||
<input type="checkbox" id="public_checkbox" name="public_checkbox" style="display: inline-block;" onclick="UI.strats.toggleFeeBox()">
|
||||
</div>
|
||||
|
||||
<!-- Fee input field -->
|
||||
<div style="grid-column: 1;">
|
||||
<label for="fee_box" style="display: inline-block; width: 20%;">Fee(%):</label>
|
||||
<input class="ietextbox" type="number" id="fee_box" name="fee_box" placeholder="0.00" step="0.01" min="0" style="width: 65px; display: inline-block;" disabled>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div style="grid-column: 1; text-align: center;">
|
||||
<button type="button" class="btn cancel" onclick="UI.strats.close_form()">Close</button>
|
||||
<button type="button" class="btn next" onclick="UI.strats.submit()">Create Strategy</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<!-- Single Resize Handle in Bottom Right -->
|
||||
<div class="resize-handle" id="resize-br"></div>
|
||||
</div>
|
||||
|
||||
<!-- Add CSS to hide scrollbars but allow scrolling and fix the resize handle -->
|
||||
<style>
|
||||
/* Hide scrollbars but still allow scrolling */
|
||||
.form-popup {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.form-popup::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* Style the draggable header section */
|
||||
#draggable_header {
|
||||
cursor: move;
|
||||
padding: 10px;
|
||||
background-color: #3E3AF2;
|
||||
color: white;
|
||||
border-bottom: 1px solid #ccc;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* Style the form container to be scrollable and take up remaining space */
|
||||
.form-container {
|
||||
height: calc(100% - 60px); /* Takes up the remaining height minus header height */
|
||||
overflow-y: auto; /* Makes it scrollable */
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
.form-container::-webkit-scrollbar {
|
||||
display: none; /* Chrome, Safari, Opera */
|
||||
}
|
||||
|
||||
/* Label and input field styling */
|
||||
.form_panels label {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.ietextbox {
|
||||
width: 75%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Resize handle styling */
|
||||
.resize-handle {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background-color: #000;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
cursor: se-resize;
|
||||
z-index: 10;
|
||||
border-radius: 3px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
<xml id="toolbox" style="display: none">
|
||||
<category name="Indicators" colour="230">
|
||||
<!-- Indicator blocks go here -->
|
||||
|
|
@ -45,6 +132,3 @@
|
|||
</category>
|
||||
</xml>
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue