diff --git a/Integrations/Projects/Ethereum/Function-Libraries/ActionSwitch.js b/Integrations/Projects/Ethereum/Function-Libraries/ActionSwitch.js new file mode 100644 index 000000000..e7f7a2239 --- /dev/null +++ b/Integrations/Projects/Ethereum/Function-Libraries/ActionSwitch.js @@ -0,0 +1,37 @@ +function newEthereumActionSwitch() { + + let thisObject = { + executeAction: executeAction, + initialize: initialize, + finalize: finalize + } + + let functionLibraryGeth = newGeth() + + return thisObject + + function finalize() { + functionLibraryGeth = undefined + } + + function initialize() { + /* Nothing to initialize since a Function Library does not hold any state. */ + } + + async function executeAction(action) { + switch (action.name) { + case 'Install Geth': { + functionLibraryGeth.install(action.node) + break + } + case 'Run Geth': { + functionLibraryGeth.run(action.node) + break + } + case 'Stop Geth': { + functionLibraryGeth.stop(action.node) + break + } + } + } +} diff --git a/Integrations/Projects/Ethereum/Function-Libraries/Geth.js b/Integrations/Projects/Ethereum/Function-Libraries/Geth.js new file mode 100644 index 000000000..c4e4f2d66 --- /dev/null +++ b/Integrations/Projects/Ethereum/Function-Libraries/Geth.js @@ -0,0 +1,21 @@ +function newGeth() { + thisObject = { + install: install, + run: run, + stop: stop + } + + return thisObject + + function install(node) { + callWebServer('ExecuteTerminalCommand/dir') + } + + function run(node) { + + } + + function stop(node) { + + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/ActionSwitch.js b/Integrations/Projects/Superalgos/Function-Libraries/ActionSwitch.js new file mode 100644 index 000000000..32693f0d3 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/ActionSwitch.js @@ -0,0 +1,546 @@ +function newSuperalgosActionSwitch() { + + let thisObject = { + executeAction: executeAction, + initialize: initialize, + finalize: finalize + } + + let functionLibraryReferenceAttachDetach = newReferenceAttachDetach() + let functionLibraryChainAttachDetach = newChainAttachDetach() + let functionLibraryNodeDeleter = newNodeDeleter() + let functionLibraryUiObjectsFromNodes = newUiObjectsFromNodes() + let functionLibraryProtocolNode = newProtocolNode() + let functionLibraryNodeCloning = newNodeCloning() + let functionLibraryShortcutKeys = newShortcutKeys() + let functionLibraryOnFocus = newOnFocus() + let functionLibrarySuperScripts = newSuperScriptsFunctions() + let functionLibraryWebhookFunctions = newWebhookFunctions() + let functionLibraryDependenciesFilter = newDependenciesFilter() + let functionLibraryNodePath = newNodePath() + let functionLibraryTaskFunctions = newTaskFunctions() + let functionLibrarySessionFunctions = newSessionFunctions() + let functionLibraryCryptoEcosystemFunctions = newCryptoEcosystemFunctions() + let functionLibraryDataMineFunctions = newDataMineFunctions() + let functionLibraryDataStorageFunctions = newDataStorageFunctions() + let functionLibraryChartingSpaceFunctions = newChartingSpaceFunctions() + let functionLibraryPluginsFunctions = newPluginsFunctions() + + return thisObject + + function finalize() { + functionLibraryReferenceAttachDetach = undefined + functionLibraryChainAttachDetach = undefined + functionLibraryNodeDeleter = undefined + functionLibraryUiObjectsFromNodes = undefined + functionLibraryProtocolNode = undefined + functionLibraryNodeCloning = undefined + functionLibraryShortcutKeys = undefined + functionLibraryOnFocus = undefined + functionLibrarySuperScripts = undefined + functionLibraryWebhookFunctions = undefined + functionLibraryDependenciesFilter = undefined + functionLibraryNodePath = undefined + functionLibraryTaskFunctions = undefined + functionLibrarySessionFunctions = undefined + functionLibraryCryptoEcosystemFunctions = undefined + functionLibraryDataMineFunctions = undefined + functionLibraryDataStorageFunctions = undefined + functionLibraryChartingSpaceFunctions = undefined + functionLibraryPluginsFunctions = undefined + } + + function initialize() { + /* Nothing to initialize since a Function Library does not hold any state. */ + } + + async function executeAction(action) { + switch (action.name) { + case 'Get Node On Focus': { + return functionLibraryOnFocus.getNodeThatIsOnFocus(action.node) + } + + case 'Get Node By Shortcut Key': { + return functionLibraryShortcutKeys.getNodeByShortcutKey(action.node, action.extraParameter) + } + + case 'Get Node Data Structure': { + return functionLibraryProtocolNode.getProtocolNode(action.node, action.extraParameter, false, true, true, true) + } + + case 'Create UI Object': { + functionLibraryUiObjectsFromNodes.createUiObjectFromNode(action.node, undefined, undefined, action.extraParameter) + } + break + case 'Connect Children to Reference Parents': { + functionLibraryUiObjectsFromNodes.tryToConnectChildrenWithReferenceParents() + } + break + case 'Get Node By Id': { + return functionLibraryUiObjectsFromNodes.getNodeById(action.relatedNodeId) + } + + case 'Syncronize Tasks': { + functionLibraryUiObjectsFromNodes.syncronizeTasksFoundAtWorkspaceWithBackEnd(functionLibraryTaskFunctions) + } + break + case 'Syncronize Sessions': { + functionLibraryUiObjectsFromNodes.syncronizeSessionsFoundAtWorkspaceWithBackEnd(functionLibrarySessionFunctions) + } + break + case 'Play Tutorials': { + functionLibraryUiObjectsFromNodes.playTutorials() + } + break + case 'Recreate Workspace': { + functionLibraryUiObjectsFromNodes.recreateWorkspace(action.node, action.callBackFunction) + } + break + case 'Delete Workspace': { + functionLibraryNodeDeleter.deleteWorkspace(action.node, action.rootNodes) + } + break + case 'Copy Node Path': + { + let nodePath = functionLibraryNodePath.getNodePath(action.node) + + UI.projects.superalgos.utilities.clipboard.copyTextToClipboard(nodePath) + + UI.projects.superalgos.spaces.cockpitSpace.setStatus( + nodePath + ' copied to the Clipboard.' + , 50, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.ALL_GOOD) + } + break + case 'Add UI Object': + { + functionLibraryUiObjectsFromNodes.addUIObject(action.node, action.relatedNodeType, action.rootNodes) + } + break + case 'Add Missing Children': + { + functionLibraryUiObjectsFromNodes.addMissingChildren(action.node) + } + break + case 'Delete UI Object': + { + functionLibraryNodeDeleter.deleteUIObject(action.node, action.rootNodes) + } + break + case 'Edit Code': + + break + case 'Share': + { + let text = JSON.stringify(functionLibraryProtocolNode.getProtocolNode(action.node, true, false, true, true, true)) + + let nodeName = action.node.name + if (nodeName === undefined) { + nodeName = '' + } else { + nodeName = '.' + nodeName + } + let fileName = 'Share - ' + action.node.type + ' - ' + nodeName + '.json' + UI.projects.superalgos.utilities.download.downloadText(fileName, text) + } + + break + case 'Backup': + { + let text = JSON.stringify(functionLibraryProtocolNode.getProtocolNode(action.node, false, false, true, true, true)) + + let nodeName = action.node.name + if (nodeName === undefined) { + nodeName = '' + } else { + nodeName = ' ' + nodeName + } + let fileName = 'Backup - ' + action.node.type + ' - ' + nodeName + '.json' + UI.projects.superalgos.utilities.download.downloadText(fileName, text) + } + + break + case 'Clone': + { + let text = JSON.stringify(functionLibraryNodeCloning.getNodeClone(action.node)) + + let nodeName = action.node.name + if (nodeName === undefined) { + nodeName = '' + } else { + nodeName = ' ' + nodeName + } + let fileName = 'Clone - ' + action.node.type + ' - ' + nodeName + '.json' + UI.projects.superalgos.utilities.download.downloadText(fileName, text) + } + + break + case 'Debug Task': + { + functionLibraryTaskFunctions.runTask(action.node, functionLibraryProtocolNode, true, action.callBackFunction) + } + break + case 'Run Task': + { + functionLibraryTaskFunctions.runTask(action.node, functionLibraryProtocolNode, false, action.callBackFunction) + } + break + case 'Stop Task': + { + functionLibraryTaskFunctions.stopTask(action.node, functionLibraryProtocolNode, action.callBackFunction) + } + break + case 'Run All Tasks': + { + functionLibraryTaskFunctions.runAllTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Tasks': + { + functionLibraryTaskFunctions.stopAllTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Task Managers': + { + functionLibraryTaskFunctions.runAllTaskManagers(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Task Managers': + { + functionLibraryTaskFunctions.stopAllTaskManagers(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Exchange Data Tasks': + { + functionLibraryTaskFunctions.runAllExchangeDataTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Exchange Data Tasks': + { + functionLibraryTaskFunctions.stopAllExchangeDataTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Exchange Trading Tasks': + { + functionLibraryTaskFunctions.runAllExchangeTradingTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Exchange Trading Tasks': + { + functionLibraryTaskFunctions.stopAllExchangeTradingTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Market Data Tasks': + { + functionLibraryTaskFunctions.runAllMarketDataTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Market Data Tasks': + { + functionLibraryTaskFunctions.stopAllMarketDataTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Market Trading Tasks': + { + functionLibraryTaskFunctions.runAllMarketTradingTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Market Trading Tasks': + { + functionLibraryTaskFunctions.stopAllMarketTradingTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Data Mine Tasks': + { + functionLibraryTaskFunctions.runAllDataMineTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Data Mine Tasks': + { + functionLibraryTaskFunctions.stopAllDataMineTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Run All Trading Mine Tasks': + { + functionLibraryTaskFunctions.runAllTradingMineTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Stop All Trading Mine Tasks': + { + functionLibraryTaskFunctions.stopAllTradingMineTasks(action.node, functionLibraryProtocolNode) + } + break + case 'Add Missing Exchange Data Tasks': + { + functionLibraryTaskFunctions.addMissingExchangeDataTasks(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Market Data Tasks': + { + functionLibraryTaskFunctions.addMissingMarketDataTasks(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Data Mine Tasks': + { + functionLibraryTaskFunctions.addMissingDataMineTasks(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Exchange Trading Tasks': + { + functionLibraryTaskFunctions.addMissingExchangeTradingTasks(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Market Trading Tasks': + { + functionLibraryTaskFunctions.addMissingMarketTradingTasks(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Trading Mine Tasks': + { + functionLibraryTaskFunctions.addMissingTradingMineTasks(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Tasks': + { + functionLibraryTaskFunctions.addAllTasks(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Crypto Exchanges': + { + functionLibraryCryptoEcosystemFunctions.addMissingExchanges(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Assets': + { + functionLibraryCryptoEcosystemFunctions.addMissingAssets(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Markets': + { + functionLibraryCryptoEcosystemFunctions.addMissingMarkets(action.node, functionLibraryUiObjectsFromNodes, functionLibraryNodeCloning) + } + break + case 'Install Market': + { + functionLibraryCryptoEcosystemFunctions.installMarket(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter, functionLibraryChartingSpaceFunctions, functionLibraryDataStorageFunctions) + } + break + case 'Uninstall Market': + { + functionLibraryCryptoEcosystemFunctions.uninstallMarket(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter, functionLibraryChartingSpaceFunctions, functionLibraryDataStorageFunctions) + } + break + case 'Add All Output Datasets': + { + functionLibraryDataMineFunctions.addAllOutputDatasets(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Data Products': + { + functionLibraryDataStorageFunctions.addAllDataProducts(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Data Mine Products': + { + functionLibraryDataStorageFunctions.addAllDataMineProducts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Trading Mine Products': + { + functionLibraryDataStorageFunctions.addAllTradingMineProducts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Session References': + { + functionLibraryDataStorageFunctions.addMissingSessionReferences(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Market Data Products': + { + functionLibraryDataStorageFunctions.addMissingMarketDataProducts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Market Trading Products': + { + functionLibraryDataStorageFunctions.addMissingMarketTradingProducts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Exchange Trading Products': + { + functionLibraryDataStorageFunctions.addMissingExchangeTradingProducts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Exchange Data Products': + { + functionLibraryDataStorageFunctions.addMissingExchangeDataProducts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Data Dependencies': + { + functionLibraryDataMineFunctions.addAllDataDependencies(action.node, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Data Mine Dependencies': + { + functionLibraryDataMineFunctions.addAllDataMineDataDependencies(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add All Mine Layers': + { + functionLibraryChartingSpaceFunctions.addAllMineLayers(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + } + break + case 'Add Missing Time Machines': + { + functionLibraryChartingSpaceFunctions.addMissingTimeMachines(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + } + break + case 'Add Missing Dashboards': + { + functionLibraryChartingSpaceFunctions.addMissingDashboards(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + } + break + case 'Play Tutorial': + { + UI.projects.superalgos.spaces.tutorialSpace.playTutorial(action.node) + } + break + case 'Play Tutorial Topic': + { + UI.projects.superalgos.spaces.tutorialSpace.playTutorialTopic(action.node) + } + break + case 'Play Tutorial Step': + { + UI.projects.superalgos.spaces.tutorialSpace.playTutorialStep(action.node) + } + break + case 'Resume Tutorial': + { + UI.projects.superalgos.spaces.tutorialSpace.resumeTutorial(action.node) + } + break + case 'Resume Tutorial Topic': + { + UI.projects.superalgos.spaces.tutorialSpace.resumeTutorialTopic(action.node) + } + break + case 'Resume Tutorial Step': + { + UI.projects.superalgos.spaces.tutorialSpace.resumeTutorialStep(action.node) + } + break + case 'Reset Tutorial Progress': + { + UI.projects.superalgos.spaces.tutorialSpace.resetTutorialProgress(action.node) + } + break + case 'Send Webhook Test Message': + { + functionLibraryWebhookFunctions.sendTestMessage(action.node, action.callBackFunction) + } + break + case 'Run Session': + { + functionLibrarySessionFunctions.runSession(action.node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, false, action.callBackFunction) + } + break + case 'Resume Session': + { + functionLibrarySessionFunctions.runSession(action.node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, true, action.callBackFunction) + } + break + case 'Stop Session': + { + functionLibrarySessionFunctions.stopSession(action.node, functionLibraryProtocolNode, action.callBackFunction) + } + break + case 'Run Super Action': + { + functionLibrarySuperScripts.runSuperScript(action.node, action.rootNodes, functionLibraryNodeCloning, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + } + break + case 'Parent Attach': { + functionLibraryChainAttachDetach.chainAttachNode(action.node, action.relatedNode, action.rootNodes) + } + break + case 'Reference Attach': { + functionLibraryReferenceAttachDetach.referenceAttachNode(action.node, action.relatedNode, action.rootNodes) + } + break + case 'Parent Detach': + { + functionLibraryChainAttachDetach.chainDetachNode(action.node, action.rootNodes) + } + break + case 'Reference Detach': + { + functionLibraryReferenceAttachDetach.referenceDetachNode(action.node) + } + break + case 'Push Code to Javascript Code': + { + action.node.javascriptCode.code = action.node.code + } + break + case 'Fetch Code to Javascript Code': + { + action.node.code = action.node.javascriptCode.code + } + break + case 'Open Documentation': + { + let definition = getNodeDefinition(action.node) + if (definition !== undefined) { + if (definition.docURL !== undefined) { + UI.projects.superalgos.spaces.docSpace.navigateTo(definition.docURL) + } else { + let headName = UI.projects.superalgos.utilities.hierarchy.getHiriarchyHead(action.node).type + headName = headName.toLowerCase() + headName = headName.split(" ").join("-") + let nodeName = action.node.type + nodeName = nodeName.toLowerCase() + nodeName = nodeName.split(" ").join("-") + url = UI.projects.superalgos.globals.docs.DOCUMENTATION_URL_PREFIX + "suite-hierarchy-" + headName + ".html#" + nodeName + UI.projects.superalgos.spaces.docSpace.navigateTo(url) + } + } + } + break + case 'Add Missing Plugin Projects': + { + functionLibraryPluginsFunctions.pluginMissingProjects(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Plugin Data Mines': + { + functionLibraryPluginsFunctions.pluginMissingDataMines(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Plugin Trading Mines': + { + functionLibraryPluginsFunctions.pluginMissingTradingMines(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Plugin Trading Systems': + { + functionLibraryPluginsFunctions.pluginMissingTradingSystems(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Plugin Trading Engines': + { + functionLibraryPluginsFunctions.pluginMissingTradingEngines(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Plugin Super Scripts': + { + functionLibraryPluginsFunctions.pluginMissingSuperScripts(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + case 'Add Missing Plugin Tutorials': + { + functionLibraryPluginsFunctions.pluginMissingTutorials(action.node, action.rootNodes, functionLibraryUiObjectsFromNodes) + } + break + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/ChainAttachDetach.js b/Integrations/Projects/Superalgos/Function-Libraries/ChainAttachDetach.js new file mode 100644 index 000000000..faff8bafb --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/ChainAttachDetach.js @@ -0,0 +1,221 @@ +function newChainAttachDetach() { + thisObject = { + chainDetachNode: chainDetachNode, + chainAttachNode: chainAttachNode + } + + return thisObject + + function chainDetachNode(node, rootNodes) { + if (node.payload.parentNode === undefined) { return } + switch (node.type) { + case 'Phase': { + let payload = node.payload + for (let i = 0; i < payload.parentNode.phases.length; i++) { + let phase = payload.parentNode.phases[i] + if (phase.id === node.id) { + if (i < payload.parentNode.phases.length - 1) { + let nextPhase = payload.parentNode.phases[i + 1] + if (i > 0) { + let previousPhase = payload.parentNode.phases[i - 1] + nextPhase.payload.chainParent = previousPhase + } else { + nextPhase.payload.chainParent = payload.parentNode + } + } + payload.parentNode.phases.splice(i, 1) + completeDetachment(node, rootNodes) + return + } + } + return + } + default: { + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + /* Detach from parent */ + if (node.payload.parentNode !== undefined) { + let parentNodeDefinition = getNodeDefinition(node.payload.parentNode) + if (parentNodeDefinition !== undefined) { + if (parentNodeDefinition.properties !== undefined) { + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (nodeDefinition.propertyNameAtParent === property.name) { + switch (property.type) { + case 'node': { + node.payload.parentNode[property.name] = undefined + } + break + case 'array': { + let nodePropertyArray = node.payload.parentNode[property.name] + if (nodePropertyArray !== undefined) { + for (let j = 0; j < nodePropertyArray.length; j++) { + let arrayItem = nodePropertyArray[j] + if (arrayItem.id === node.id) { + nodePropertyArray.splice(j, 1) + } + } + } + } + break + } + } + } + } + } + } + completeDetachment(node, rootNodes) + } + } + } + } + + function chainAttachNode(node, attachToNode, rootNodes) { + switch (node.type) { + + case 'Exchange Account Key': { + node.payload.parentNode = attachToNode + node.payload.chainParent = attachToNode + if (node.payload.parentNode.keys !== undefined) { + node.payload.parentNode.keys.push(node) + } else { + node.payload.parentNode.key = node + } + completeAttachment(node, rootNodes) + } + break + + case 'Phase': { + switch (attachToNode.type) { + case 'Stop': { + node.payload.parentNode = attachToNode + if (attachToNode.phases.length > 0) { + let phase = attachToNode.phases[attachToNode.phases.length - 1] + node.payload.chainParent = phase + } else { + node.payload.chainParent = attachToNode + } + attachToNode.phases.push(node) + completeAttachment(node, rootNodes) + } + break + case 'Take Profit': { + node.payload.parentNode = attachToNode + if (attachToNode.phases.length > 0) { + let phase = attachToNode.phases[attachToNode.phases.length - 1] + node.payload.chainParent = phase + } else { + node.payload.chainParent = attachToNode + } + attachToNode.phases.push(node) + completeAttachment(node, rootNodes) + } + break + case 'Initial Stop': { + if (attachToNode.phases.length >= 1) { + return + } + + node.payload.parentNode = attachToNode + if (attachToNode.phases.length > 0) { + let phase = attachToNode.phases[attachToNode.phases.length - 1] + node.payload.chainParent = phase + } else { + node.payload.chainParent = attachToNode + } + attachToNode.phases.push(node) + completeAttachment(node, rootNodes) + } + break + case 'Initial Take Profit': { + if (attachToNode.phases.length >= 1) { + return + } + node.payload.parentNode = attachToNode + if (attachToNode.phases.length > 0) { + let phase = attachToNode.phases[attachToNode.phases.length - 1] + node.payload.chainParent = phase + } else { + node.payload.chainParent = attachToNode + } + attachToNode.phases.push(node) + completeAttachment(node, rootNodes) + } + break + case 'Phase': { + node.payload.parentNode = attachToNode.payload.parentNode + for (let i = 0; i < node.payload.parentNode.phases.length; i++) { + let phase = node.payload.parentNode.phases[i] + if (attachToNode.id === phase.id) { + if (i === node.payload.parentNode.phases.length - 1) { + node.payload.chainParent = attachToNode + node.payload.parentNode.phases.push(node) + completeAttachment(node, rootNodes) + } else { + node.payload.chainParent = attachToNode + let nextPhase = node.payload.parentNode.phases[i + 1] + nextPhase.payload.chainParent = node + node.payload.parentNode.phases.splice(i + 1, 0, node) + completeAttachment(node, rootNodes) + return + } + } + } + } + } + } + break + default: { + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + node.payload.parentNode = attachToNode + node.payload.chainParent = attachToNode + /* Attach to new parent */ + if (node.payload.parentNode !== undefined) { + let parentNodeDefinition = getNodeDefinition(node.payload.parentNode) + if (parentNodeDefinition !== undefined) { + if (parentNodeDefinition.properties !== undefined) { + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (nodeDefinition.propertyNameAtParent === property.name) { + switch (property.type) { + case 'node': { + node.payload.parentNode[property.name] = node + } + break + case 'array': { + if (node.payload.parentNode[property.name] === undefined) { + node.payload.parentNode[property.name] = [] + } + let nodePropertyArray = node.payload.parentNode[property.name] + nodePropertyArray.push(node) + } + break + } + } + } + } + } + } + completeAttachment(node, rootNodes) + } + } + } + } + + function completeDetachment(node, rootNodes) { + node.payload.parentNode = undefined + node.payload.chainParent = undefined + rootNodes.push(node) + } + + function completeAttachment(node, rootNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.id === node.id) { + rootNodes.splice(i, 1) + return + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/ChartingSpaceFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/ChartingSpaceFunctions.js new file mode 100644 index 000000000..30b99b63a --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/ChartingSpaceFunctions.js @@ -0,0 +1,247 @@ +function newChartingSpaceFunctions() { + thisObject = { + addAllMineLayers: addAllMineLayers, + addMissingDashboards: addMissingDashboards, + addMissingTimeMachines: addMissingTimeMachines, + createTimeMachine: createTimeMachine + } + + return thisObject + + function addAllMineLayers(node, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) { + + /* Validations to see if we can do this or not. */ + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (node.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a reference parent node to execute this action.') + return + } + if (node.payload.referenceParent.payload === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a reference parent node to execute this action.') + return + } + + /* + We are going to go through the referenced branch and recreate the structure + we find there inside the layers manager. We will end up having at the end + all layers referencing the data products inside the referenced branch. + */ + let dataMine = node.payload.referenceParent + scanBotArray(dataMine.botProducts) + + function scanBotArray(botArray) { + for (let i = 0; i < botArray.length; i++) { + let bot = botArray[i] + let botLayers = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Bot Layers') + botLayers.name = bot.name + + UI.projects.superalgos.utilities.folders.asymetricalFolderStructureCloning( + bot, + botLayers, + 'dataProducts', + 'dataProductFolders', + 'layerFolders', + 'Layer', + 'Layer Folder', + undefined, + functionLibraryUiObjectsFromNodes + ) + /* + There are some layers that should not exist, for example the ones related to Data Products + that do not have a plotter module. Since our previous action created all layers no matter + what, we need now to delete all the ones that do not have a plotter module. + */ + let allLayers = UI.projects.superalgos.utilities.branches.nodeBranchToArray(botLayers, 'Layer') + for (let j = 0; j < allLayers.length; j++) { + let layer = allLayers[j] + layer.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_45 + + let plotterModule = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(layer, 'Plotter Module', undefined, true, false, false, true) + if (plotterModule === undefined) { + functionLibraryNodeDeleter.deleteUIObject(layer, rootNodes) + } + } + } + } + } + + function addMissingTimeMachines(node, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) { + if (node.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('This node needs to have a Reference Parent for this command tu run.') + return + } + + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Network') { + let networkNode = rootNode + scanNetworkNode(networkNode) + } + } + + function scanNetworkNode(networkNode) { + if (networkNode === undefined) { return } + + let backtestingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Backtesting Session') + let fordwardTestingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Forward Testing Session') + let paperTradingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Paper Trading Session') + let liveTradingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Live Trading Session') + + scanSessionArray(backtestingSessionsArray) + scanSessionArray(fordwardTestingSessionsArray) + scanSessionArray(paperTradingSessionsArray) + scanSessionArray(liveTradingSessionsArray) + + function scanSessionArray(sessionsArray) { + for (let i = 0; i < sessionsArray.length; i++) { + let session = sessionsArray[i] + let environment = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(session, node.payload.referenceParent.type, undefined, true, false, true, false) + if (environment === undefined) { continue } + if (environment.id !== node.payload.referenceParent.id) { continue } + let market = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(session, 'Market Trading Tasks', undefined, true, false, true, false) + if (market.payload.referenceParent === undefined) { continue } + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, session, true) === true) { + createTimeMachine(node, session, market.payload.referenceParent, networkNode, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + } + } + } + } + } + + function createTimeMachine(dashboard, session, market, networkNode, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) { + let mineProducts + let timeMachine = functionLibraryUiObjectsFromNodes.addUIObject(dashboard, 'Time Machine') + timeMachine.payload.referenceParent = session + timeMachine.name = session.name + ' ' + session.type + ' ' + networkNode.name + timeMachine.payload.floatingObject.collapseToggle() + timeMachine.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + timeMachine.timeFrameScale.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + timeMachine.timeScale.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + timeMachine.rateScale.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + /* + We need to create a Timeline Chart for each Data Mine Indicators. + */ + mineProducts = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Data Mine Products') + for (let j = 0; j < mineProducts.length; j++) { + let mineProduct = mineProducts[j] + /* + We need to filter out the ones that do not belong to the market where + the session is running at. + */ + if (mineProduct.payload.parentNode.payload.referenceParent === undefined) { continue } + if (mineProduct.payload.parentNode.payload.referenceParent.id !== market.id) { continue } + + let timelineChart = functionLibraryUiObjectsFromNodes.addUIObject(timeMachine, 'Timeline Chart') + /* + The Mine Product Node might be collapesd and since its creation it never + received the physics call, so we will do the call so that it properly + sets its own name, which we are going to reuse here. + */ + mineProduct.payload.uiObject.invisiblePhysics() + timelineChart.name = mineProduct.name + timelineChart.layerManager.payload.referenceParent = mineProduct + timelineChart.payload.floatingObject.collapseToggle() + timelineChart.layerManager.payload.floatingObject.collapseToggle() + timelineChart.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + timelineChart.layerManager.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + + let menu = timelineChart.layerManager.payload.uiObject.menu + menu.internalClick('Add All Mine Layers') + menu.internalClick('Add All Mine Layers') + } + + mineProducts = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Trading Mine Products') + for (let j = 0; j < mineProducts.length; j++) { + let mineProduct = mineProducts[j] + /* + The mine products found so far, belongs to any session. To filer all the sessions + that are not the one we are interested in, we do the following: + */ + if (mineProduct.payload.parentNode === undefined) { continue } + if (mineProduct.payload.parentNode.payload.referenceParent.id !== session.id) { continue } + /* + At the current version of Superalgos, beta 6, there is only one Trading Mine, + with only one bot, and it has so many data products that we want to put them + in 3 different timeline charts. So we will create 3 charts, connect them, + and delete from each one 1/3 of the layers. We do all that next: + */ + + for (let k = 0; k < 3; k++) { + let timelineChart = functionLibraryUiObjectsFromNodes.addUIObject(timeMachine, 'Timeline Chart') + + timelineChart.layerManager.payload.referenceParent = mineProduct + timelineChart.payload.floatingObject.collapseToggle() + timelineChart.layerManager.payload.floatingObject.collapseToggle() + timelineChart.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + timelineChart.layerManager.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + + let menu = timelineChart.layerManager.payload.uiObject.menu + menu.internalClick('Add All Mine Layers') + menu.internalClick('Add All Mine Layers') + + switch (k) { + case 0: { + timelineChart.name = 'Trading Engine' + deleteNodeByName('Trading System') + deleteNodeByName('Simulation Objects') + break + } + case 1: { + timelineChart.name = 'Trading System' + deleteNodeByName('Trading Engine') + deleteNodeByName('Simulation Objects') + break + } + case 2: { + timelineChart.name = 'Simulation Objects' + deleteNodeByName('Trading Engine') + deleteNodeByName('Trading System') + break + } + } + function deleteNodeByName(nodeName) { + let nodeToDelete = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(timelineChart.layerManager, undefined, nodeName, true, true, false, false) + if (nodeToDelete === undefined) { return } + functionLibraryNodeDeleter.deleteUIObject(nodeToDelete, rootNodes) + } + } + } + } + + function addMissingDashboards(node, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Network') { + let network = rootNode + scanNetwork(network) + } + } + + function scanNetwork(network) { + if (network === undefined) { return } + + for (let j = 0; j < network.networkNodes.length; j++) { + let networkNode = network.networkNodes[j] + scanNetworkNode(networkNode) + } + + function scanNetworkNode(networkNode) { + let testingEnvironment = UI.projects.superalgos.utilities.branches.findInBranch(networkNode, 'Testing Environment', node, true) + let productionEnvironment = UI.projects.superalgos.utilities.branches.findInBranch(networkNode, 'Production Environment', node, true) + + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, testingEnvironment, true) === true) { + let dashboard = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Dashboard') + dashboard.payload.referenceParent = testingEnvironment + dashboard.name = testingEnvironment.type + ' ' + networkNode.name + } + + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, productionEnvironment, true) === true) { + let dashboard = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Dashboard') + dashboard.payload.referenceParent = productionEnvironment + dashboard.name = productionEnvironment.type + ' ' + networkNode.name + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/CryptoEcosystemFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/CryptoEcosystemFunctions.js new file mode 100644 index 000000000..c9329af3c --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/CryptoEcosystemFunctions.js @@ -0,0 +1,460 @@ +function newCryptoEcosystemFunctions() { + thisObject = { + addMissingExchanges: addMissingExchanges, + addMissingAssets: addMissingAssets, + addMissingMarkets: addMissingMarkets, + installMarket: installMarket, + uninstallMarket: uninstallMarket + } + + return thisObject + + function addMissingExchanges(node, functionLibraryUiObjectsFromNodes) { + currentExchanges = new Map() + let parent = node.payload.parentNode + if (parent !== undefined) { + for (let i = 0; i < parent.cryptoExchanges.length; i++) { + let cryptoExchanges = parent.cryptoExchanges[i] + for (let j = 0; j < cryptoExchanges.exchanges.length; j++) { + let exchange = cryptoExchanges.exchanges[j] + let codeName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(exchange.payload, 'codeName') + currentExchanges.set(codeName, exchange) + } + } + } + + let params = { + method: 'listExchanges', + has: { + fetchOHLCV: true, + fetchMarkets: true + } + } + + callWebServer(JSON.stringify(params), 'CCXT', onResponse) + + function onResponse(err, data) { + if (err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + node.payload.uiObject.setErrorMessage('Failed to Fetch Assets from the Exchange') + return + } + + let exchanges = JSON.parse(data) + for (let i = 0; i < exchanges.length; i++) { + let exchange = exchanges[i] + let existingExchange = currentExchanges.get(exchange.id) + if (existingExchange === undefined) { + let newExchange = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Crypto Exchange') + newExchange.name = exchange.name + newExchange.config = '{ \n\"codeName\": \"' + exchange.id + '\"\n}' + newExchange.payload.floatingObject.collapseToggle() + newExchange.exchangeAssets.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + newExchange.exchangeMarkets.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + newExchange.exchangeAccounts.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + newExchange.exchangeAssets.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_050X + newExchange.exchangeMarkets.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_100X + newExchange.exchangeAccounts.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_025X + newExchange.exchangeAssets.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + newExchange.exchangeMarkets.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + newExchange.exchangeAccounts.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + } + } + } + } + + function addMissingAssets(node, functionLibraryUiObjectsFromNodes) { + if (node.payload.parentNode === undefined) { return } + + let currentAssets = new Map() + for (let j = 0; j < node.assets.length; j++) { + let asset = node.assets[j] + let codeName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(asset.payload, 'codeName') + currentAssets.set(codeName, asset) + } + + let exchangeId = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(node.payload.parentNode.payload, 'codeName') + + try { + let params = { + exchangeId: exchangeId, + method: 'fetchMarkets' + } + callWebServer(JSON.stringify(params), 'CCXT', onResponse) + + function onResponse(err, data) { + if (err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + node.payload.uiObject.setErrorMessage('Failed to Fetch Assets from the Exchange') + return + } + let queryParams = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(node.payload, 'addMissingAssetsFilter') + + let markets = JSON.parse(data) + for (let i = 0; i < markets.length; i++) { + let market = markets[i] + + if (queryParams !== undefined) { + if (queryParams.baseAsset !== undefined) { + if (market.base.indexOf(queryParams.baseAsset) < 0) { + continue + } + } + if (queryParams.quotedAsset !== undefined) { + if (market.quote.indexOf(queryParams.quotedAsset) < 0) { + continue + } + } + } + if (currentAssets.get(market.base) === undefined) { + addAsset(market.base) + currentAssets.set(market.base, market.base) + } + if (currentAssets.get(market.quote) === undefined) { + addAsset(market.quote) + currentAssets.set(market.quote, market.quote) + } + + function addAsset(name) { + let newAsseet = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Asset') + newAsseet.name = name + newAsseet.config = '{ \n\"codeName\": \"' + name + '\"\n}' + } + } + } + } catch (err) { + node.payload.uiObject.setErrorMessage('Failed to Fetch Assets from the Exchange') + console.log(err.stack) + } + } + + function addMissingMarkets(node, functionLibraryUiObjectsFromNodes, functionLibraryNodeCloning) { + if (node.payload.parentNode === undefined) { return } + if (node.payload.parentNode.exchangeAssets === undefined) { return } + if (node.payload.parentNode.payload.parentNode === undefined) { return } + if (node.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { return } + + let currentAssets = new Map() + let exchangeAssets = node.payload.parentNode.exchangeAssets + for (let j = 0; j < exchangeAssets.assets.length; j++) { + let asset = exchangeAssets.assets[j] + let codeName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(asset.payload, 'codeName') + currentAssets.set(codeName, asset) + } + + let currentMarkets = new Map() + let exchangeMarkets = node + for (let j = 0; j < exchangeMarkets.markets.length; j++) { + let asset = exchangeMarkets.markets[j] + let codeName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(asset.payload, 'codeName') + currentMarkets.set(codeName, asset) + } + + let exchangeId = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(node.payload.parentNode.payload, 'codeName') + + try { + let params = { + exchangeId: exchangeId, + method: 'fetchMarkets' + } + callWebServer(JSON.stringify(params), 'CCXT', onResponse) + + function onResponse(err, data) { + if (err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + node.payload.uiObject.setErrorMessage('Failed to Fetch Assets from the Exchange') + return + } + + let markets = JSON.parse(data) + for (let i = 0; i < markets.length; i++) { + let market = markets[i] + let baseAsset = currentAssets.get(market.base) + let quotedAsset = currentAssets.get(market.quote) + + if (baseAsset === undefined) { + continue + } + if (quotedAsset === undefined) { + continue + } + + if (currentMarkets.get(market.symbol) === undefined) { + addMarket(market.symbol, baseAsset, quotedAsset) + } + + function addMarket(name, baseAsset, quotedAsset) { + let newMarket = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Market') + newMarket.name = name + newMarket.config = '{ \n\"codeName\": \"' + name + '\"\n}' + newMarket.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + newMarket.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_100X + newMarket.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + newMarket.baseAsset.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_45 + newMarket.quotedAsset.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_45 + newMarket.baseAsset.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_100X + newMarket.quotedAsset.payload.floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_100X + newMarket.baseAsset.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + newMarket.quotedAsset.payload.floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + newMarket.baseAsset.payload.referenceParent = baseAsset + newMarket.quotedAsset.payload.referenceParent = quotedAsset + + currentMarkets.set(name, newMarket) + } + } + } + } catch (err) { + node.payload.uiObject.setErrorMessage('Failed to Fetch Assets from the Exchange') + console.log(err.stack) + } + } + + function installMarket(node, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter, functionLibraryChartingSpaceFunctions, functionLibraryDataStorageFunctions) { + + let market = node + let cryptoExchange = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(node, 'Crypto Exchange', undefined, true, false, true, false) + if (cryptoExchange === undefined) { + node.payload.uiObject.setErrorMessage('Market must be a descendant of a Crypto Exchange') + return + } + let dashboardsArray = [] + + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Network') { + installInNetwork(rootNode) + } + } + + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Charting Space') { + installInChartingSpace(rootNode) + } + } + + function installInNetwork(network) { + + for (let j = 0; j < network.networkNodes.length; j++) { + let networkNode = network.networkNodes[j] + installInNetworkNode(networkNode) + } + + function installInNetworkNode(networkNode) { + /* + Here we complete the missing stuff at Data Mining + */ + let dataMining = UI.projects.superalgos.utilities.branches.findInBranch(networkNode, 'Data Mining', node, true) + if (dataMining === undefined) { + node.payload.uiObject.setErrorMessage('Data Mining node not found at Network Node ' + networkNode.name) + return + } + + let exchangeDataTasks = UI.projects.superalgos.utilities.children.findOrCreateChildWithReference(dataMining, 'Exchange Data Tasks', cryptoExchange, functionLibraryUiObjectsFromNodes) + exchangeDataTasks.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + let marketDataTask = UI.projects.superalgos.utilities.children.findAndRecreateChildWithReference(exchangeDataTasks, 'Market Data Tasks', market, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + + UI.projects.superalgos.utilities.menu.menuClick(marketDataTask, 'Add Missing Data Mine Tasks', true) + UI.projects.superalgos.utilities.menu.menuClickOfNodeArray(marketDataTask.dataMineTasks, 'Add All Tasks', true) + + let sessionsCreatedArray = [] + /* + Next we complete the missing stuff at Testing Environment + */ + installEnvironment('Testing Environment') + /* + Next we complete the missing stuff at Production Environment + */ + installEnvironment('Production Environment') + + function installEnvironment(environment) { + /* + Now we install the environmnet at the current Network Node + */ + let environmentFound = UI.projects.superalgos.utilities.branches.findInBranch(networkNode, environment, node, true) + if (environmentFound === undefined) { + node.payload.uiObject.setErrorMessage(environment + ' node not found at Network Node ' + networkNode.name) + return + } + + let exchangeTradingTasks = UI.projects.superalgos.utilities.children.findOrCreateChildWithReference(environmentFound, 'Exchange Trading Tasks', cryptoExchange, functionLibraryUiObjectsFromNodes) + exchangeTradingTasks.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + let marketTradingTask = UI.projects.superalgos.utilities.children.findAndRecreateChildWithReference(exchangeTradingTasks, 'Market Trading Tasks', market, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + + UI.projects.superalgos.utilities.menu.menuClick(marketTradingTask, 'Add Missing Trading Mine Tasks', true) + UI.projects.superalgos.utilities.menu.menuClickOfNodeArray(marketTradingTask.tradingMineTasks, 'Add All Tasks', true) + + /* This will be needed at the charging space, for creating Dashboards */ + let backtestingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(marketTradingTask, 'Backtesting Session') + let liveTradingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(marketTradingTask, 'Live Trading Session') + let allSessionsArray = backtestingSessionsArray.concat(liveTradingSessionsArray) + sessionsCreatedArray = sessionsCreatedArray.concat(allSessionsArray) + + dashboardsArray.push({ environmentNode: environmentFound, networkNode: networkNode, sessionsArray: allSessionsArray }) + } + /* + Here we complete the missing stuff at Data Mines Data + */ + let dataMinesData = UI.projects.superalgos.utilities.branches.findInBranch(networkNode, 'Data Mines Data', node, true) + if (dataMinesData === undefined) { + node.payload.uiObject.setErrorMessage('Data Mines Data node not found at Network Node ' + networkNode.name) + return + } + + let exchangeDataProducts = UI.projects.superalgos.utilities.children.findOrCreateChildWithReference(dataMinesData, 'Exchange Data Products', cryptoExchange, functionLibraryUiObjectsFromNodes) + exchangeDataProducts.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + let marketDataProducts = UI.projects.superalgos.utilities.children.findAndRecreateChildWithReference(exchangeDataProducts, 'Market Data Products', market, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + marketDataProducts.payload.floatingObject.collapseToggle() + + UI.projects.superalgos.utilities.menu.menuClick(marketDataProducts, 'Add All Data Mine Products', true) + UI.projects.superalgos.utilities.menu.menuClickOfNodeArray(marketDataProducts.dataMineProducts, 'Add All Data Products', true) + /* + Finally we complete the missing stuff at Trading Mines Data + */ + let tradingMinesData = UI.projects.superalgos.utilities.branches.findInBranch(networkNode, 'Trading Mines Data', node, true) + if (tradingMinesData === undefined) { + node.payload.uiObject.setErrorMessage('Trading Mines Data node not found at Network Node ' + networkNode.name) + return + } + + let exchangeTradingProducts = UI.projects.superalgos.utilities.children.findOrCreateChildWithReference(tradingMinesData, 'Exchange Trading Products', cryptoExchange, functionLibraryUiObjectsFromNodes) + exchangeTradingProducts.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + let marketTradingProducts = UI.projects.superalgos.utilities.children.findAndRecreateChildWithReference(exchangeTradingProducts, 'Market Trading Products', market, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + marketTradingProducts.payload.floatingObject.collapseToggle() + /* + Create the new session references. + */ + for (let i = 0; i < sessionsCreatedArray.length; i++) { + let session = sessionsCreatedArray[i] + if (UI.projects.superalgos.utilities.children.isMissingChildren(marketTradingProducts, session, true) === true) { + functionLibraryDataStorageFunctions.createSessionReference(marketTradingProducts, session, functionLibraryUiObjectsFromNodes) + } + } + /* + Create everything inside the session references. + */ + for (let j = 0; j < marketTradingProducts.sessionReferences.length; j++) { + let sessionReference = marketTradingProducts.sessionReferences[j] + UI.projects.superalgos.utilities.menu.menuClick(sessionReference, 'Add All Trading Mine Products', true) + UI.projects.superalgos.utilities.menu.menuClickOfNodeArray(sessionReference.tradingMineProducts, 'Add All Data Products', true) + sessionReference.payload.floatingObject.collapseToggle() + } + } + } + + function installInChartingSpace(chartingSpace) { + + for (let i = 0; i < dashboardsArray.length; i++) { + /* + If the Dashboard we need is not already there we create a new one. + */ + let arrayItem = dashboardsArray[i] + let dashboard = UI.projects.superalgos.utilities.children.findOrCreateChildWithReference(chartingSpace, 'Dashboard', arrayItem.environmentNode, functionLibraryUiObjectsFromNodes) + dashboard.name = arrayItem.environmentNode.type + ' ' + arrayItem.networkNode.name + /* + We delete all the existing Time Machines related to the market we are currently installing. + For that, we make a new array with the existing Time Machines so that the deleting + of each node does not affect the proccessing of the whole set. + */ + let timeMachines = [] + for (let i = 0; i < dashboard.timeMachines.length; i++) { + let timeMachine = dashboard.timeMachines[i] + timeMachines.push(timeMachine) + } + for (let i = 0; i < timeMachines.length; i++) { + let timeMachine = timeMachines[i] + let session = timeMachine.payload.referenceParent + if (session === undefined || session.cleaned === true) { + /* + This is what usually happens, that the intall process make these + time machines to lose their reference parent since the install + process deletes them. + */ + functionLibraryNodeDeleter.deleteUIObject(timeMachine, rootNodes) + continue + } + let marketTradingTasks = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(session, 'Market Trading Tasks', undefined, true, false, true, false) + if (marketTradingTasks === undefined) { continue } + if (marketTradingTasks.payload === undefined) { continue } + if (marketTradingTasks.payload.referenceParent === undefined) { continue } + if (marketTradingTasks.payload.referenceParent.id === market.id) { + functionLibraryNodeDeleter.deleteUIObject(timeMachine, rootNodes) + } + } + /* + We create a time machine for each session added during the previous processing. + */ + for (let j = 0; j < arrayItem.sessionsArray.length; j++) { + let session = arrayItem.sessionsArray[j] + functionLibraryChartingSpaceFunctions.createTimeMachine(dashboard, session, node, arrayItem.networkNode, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) + } + } + } + } + + function uninstallMarket(node, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter, functionLibraryChartingSpaceFunctions, functionLibraryDataStorageFunctions) { + let market = node + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Charting Space') { + uninstallInChartingSpace(rootNode) + } + } + + function uninstallInChartingSpace(chartingSpace) { + + /* Delete all time machines which are referencing sessions inside the market being unistalled. */ + let timeMachines = UI.projects.superalgos.utilities.branches.nodeBranchToArray(chartingSpace, 'Time Machine') + for (let i = 0; i < timeMachines.length; i++) { + let timeMachine = timeMachines[i] + let session = timeMachine.payload.referenceParent + if (session === undefined) { continue } + let marketTradingTasks = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(session, 'Market Trading Tasks', undefined, true, false, true, false) + if (marketTradingTasks === undefined) { continue } + if (marketTradingTasks.payload === undefined) { continue } + if (marketTradingTasks.payload.referenceParent === undefined) { continue } + if (marketTradingTasks.payload.referenceParent.id === market.id) { + functionLibraryNodeDeleter.deleteUIObject(timeMachine, rootNodes) + } + } + + /* Delete all Dashboards that does not have time machines inside. */ + let dashboardArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(chartingSpace, 'Dashboard') + for (let i = 0; i < dashboardArray.length; i++) { + let dashboard = dashboardArray[i] + if (dashboard.timeMachines.length === 0) { + functionLibraryNodeDeleter.deleteUIObject(dashboard, rootNodes) + } + } + + /* Scan Networks for the market being unistalled to delete it. */ + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Network') { + uninstallInNetwork(rootNode) + } + } + } + + function uninstallInNetwork(network) { + + let marketDataTasksArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(network, 'Market Data Tasks') + let marketTradingTasksArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(network, 'Market Trading Tasks') + let marketDataProductsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(network, 'Market Data Products') + let marketTradingProductsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(network, 'Market Trading Products') + + uninstalMarketArray(marketDataTasksArray) + uninstalMarketArray(marketTradingTasksArray) + uninstalMarketArray(marketDataProductsArray) + uninstalMarketArray(marketTradingProductsArray) + + function uninstalMarketArray(marketArray) { + for (let i = 0; i < marketArray.length; i++) { + let marketReference = marketArray[i] + if (marketReference.payload === undefined) { continue } + if (marketReference.payload.referenceParent === undefined) { continue } + if (marketReference.payload.referenceParent.id === market.id) { + functionLibraryNodeDeleter.deleteUIObject(marketReference, rootNodes) + } + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/DataMineFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/DataMineFunctions.js new file mode 100644 index 000000000..625431786 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/DataMineFunctions.js @@ -0,0 +1,103 @@ +function newDataMineFunctions() { + thisObject = { + addAllOutputDatasets: addAllOutputDatasets, + addAllDataDependencies: addAllDataDependencies, + addAllDataMineDataDependencies: addAllDataMineDataDependencies + } + + return thisObject + + function addAllOutputDatasets(node, functionLibraryUiObjectsFromNodes) { + + /* Validations to see if we can do this or not. */ + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (node.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a parent node to execute this action.') + return + } + if (node.payload.parentNode.payload === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a parent node to execute this action.') + return + } + if (node.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a grand parent node to execute this action.') + return + } + + /* + Next, we are going to scan recursively through all nodes of type Product Definition Folder + and Product Definition. For each Product Definition Folder we will create a counterpart + Output Dataset Folder, and for each Product Definition found we will create a Output Definition + for each Dataset defined inside the Product Definition. + */ + UI.projects.superalgos.utilities.folders.asymetricalFolderStructureCloning( + node.payload.parentNode.payload.parentNode, + node, + 'products', + 'productDefinitionFolders', + 'outputDatasetFolders', + 'Output Dataset', + 'Output Dataset Folder', + 'datasets', + functionLibraryUiObjectsFromNodes + ) + } + + function addAllDataDependencies(node, functionLibraryUiObjectsFromNodes) { + + /* Validations to see if we can do this or not. */ + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (node.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a reference parent node to execute this action.') + return + } + if (node.payload.referenceParent.payload === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a reference parent node to execute this action.') + return + } + + /* + Next, we are going to scan through all the bots of the Data Mine referenced, and for each bot we will + create a Bots Data Dependency node. Later we will scan all the Products Definitions of each bot, and for each + Dataset of each Product we will create a Data Dependency. In case we find Product Definition Folders, we will + recreate that structure too, using in this case Data Dependency Folders. + */ + let dataMine = node.payload.referenceParent + scanBotArray(dataMine.sensorBots) + scanBotArray(dataMine.indicatorBots) + scanBotArray(dataMine.tradingBots) + + function scanBotArray(botArray) { + for (let i = 0; i < botArray.length; i++) { + let bot = botArray[i] + let botProducts = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Bot Data Dependencies') + botProducts.name = bot.name + + UI.projects.superalgos.utilities.folders.asymetricalFolderStructureCloning( + bot, + botProducts, + 'products', + 'productDefinitionFolders', + 'dataDependencyFolders', + 'Data Dependency', + 'Data Dependency Folder', + 'datasets', + functionLibraryUiObjectsFromNodes + ) + } + } + } + + function addAllDataMineDataDependencies(node, rootNodes, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + + if (rootNode.type === 'Data Mine') { + let dataMineDataDependencies = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Data Mine Data Dependencies') + dataMineDataDependencies.payload.referenceParent = rootNode + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/DataStorageFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/DataStorageFunctions.js new file mode 100644 index 000000000..b1b42fd6b --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/DataStorageFunctions.js @@ -0,0 +1,167 @@ +function newDataStorageFunctions() { + thisObject = { + addAllDataProducts: addAllDataProducts, + addAllDataMineProducts: addAllDataMineProducts, + addAllTradingMineProducts: addAllTradingMineProducts, + addMissingSessionReferences: addMissingSessionReferences, + addMissingMarketDataProducts: addMissingMarketDataProducts, + addMissingMarketTradingProducts: addMissingMarketTradingProducts, + addMissingExchangeTradingProducts: addMissingExchangeTradingProducts, + addMissingExchangeDataProducts: addMissingExchangeDataProducts, + createSessionReference: createSessionReference + } + + return thisObject + + function addAllDataProducts(node, functionLibraryUiObjectsFromNodes) { + + /* Validations to see if we can do this or not. */ + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (node.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a reference parent node to execute this action.') + return + } + if (node.payload.referenceParent.payload === undefined) { + node.payload.uiObject.setErrorMessage('You need to have a reference parent node to execute this action.') + return + } + + /* + Next, we are going to scan through all the bots of the Data Mine referenced, and for each bot we will + create a Bots Products node. Later we will scan all the Products Definitions of each bot, and for each one + we will create a Data Product. In case we find Product Definition Folders, we will recreate that structure + too, using in this case Data Product Folders. + */ + let mine = node.payload.referenceParent + scanBotArray(mine.sensorBots) + scanBotArray(mine.indicatorBots) + scanBotArray(mine.tradingBots) + + function scanBotArray(botArray) { + if (botArray === undefined) { return } + + for (let i = 0; i < botArray.length; i++) { + let bot = botArray[i] + let botProducts = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Bot Products') + botProducts.name = bot.name + botProducts.payload.floatingObject.collapseToggle() + + UI.projects.superalgos.utilities.folders.asymetricalFolderStructureCloning( + bot, + botProducts, + 'products', + 'productDefinitionFolders', + 'dataProductFolders', + 'Data Product', + 'Data Product Folder', + undefined, + functionLibraryUiObjectsFromNodes + ) + } + } + } + + function addAllDataMineProducts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + + if (rootNode.type === 'Data Mine') { + let dataMineProducts = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Data Mine Products') + dataMineProducts.payload.referenceParent = rootNode + } + } + } + + function addAllTradingMineProducts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + + if (rootNode.type === 'Trading Mine') { + let tradingMineProducts = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Trading Mine Products') + tradingMineProducts.payload.referenceParent = rootNode + } + } + } + + function addMissingSessionReferences(node, rootNodes, functionLibraryUiObjectsFromNodes) { + let networkNode = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(node, 'Network Node', undefined, true, false, true, false) + if (networkNode === undefined) { return } + + let backtestingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Backtesting Session') + let fordwardTestingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Forward Testing Session') + let paperTradingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Paper Trading Session') + let liveTradingSessionsArray = UI.projects.superalgos.utilities.branches.nodeBranchToArray(networkNode, 'Live Trading Session') + + addMissingSession(backtestingSessionsArray) + addMissingSession(fordwardTestingSessionsArray) + addMissingSession(paperTradingSessionsArray) + addMissingSession(liveTradingSessionsArray) + + function addMissingSession(sessionsArray) { + for (let i = 0; i < sessionsArray.length; i++) { + let session = sessionsArray[i] + /* We will filter out all the sessions that does not belong to the market we are in */ + let marketTradingTasks = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(session, 'Market Trading Tasks', undefined, true, false, true, false) + if (node.payload.referenceParent.id !== marketTradingTasks.payload.referenceParent.id) { continue } + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, session, true) === true) { + createSessionReference(node, session, functionLibraryUiObjectsFromNodes) + } + } + } + } + + function createSessionReference(node, session, functionLibraryUiObjectsFromNodes) { + let sessionReference = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Session Reference') + sessionReference.payload.referenceParent = session + } + + function addMissingMarketDataProducts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingMarketProducts(node, rootNodes, 'Market Data Products', functionLibraryUiObjectsFromNodes) + } + + function addMissingMarketTradingProducts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingMarketProducts(node, rootNodes, 'Market Trading Products', functionLibraryUiObjectsFromNodes) + } + + function addMissingMarketProducts(node, rootNodes, newNodeType, functionLibraryUiObjectsFromNodes) { + if (node.payload.referenceParent === undefined) { return } + if (node.payload.referenceParent.exchangeMarkets === undefined) { return } + let marketsArray = node.payload.referenceParent.exchangeMarkets.markets + + for (let i = 0; i < marketsArray.length; i++) { + let market = marketsArray[i] + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, market, true) === true) { + let marketDataProducts = functionLibraryUiObjectsFromNodes.addUIObject(node, newNodeType) + marketDataProducts.payload.referenceParent = market + } + } + } + + function addMissingExchangeTradingProducts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingExchange(node, rootNodes, 'Exchange Trading Products', functionLibraryUiObjectsFromNodes) + } + + function addMissingExchangeDataProducts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingExchange(node, rootNodes, 'Exchange Data Products', functionLibraryUiObjectsFromNodes) + } + + function addMissingExchange(node, rootNodes, newNodeType, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Crypto Ecosystem') { + let cryptoEcosystem = rootNode + for (let j = 0; j < cryptoEcosystem.cryptoExchanges.length; j++) { + let cryptoExchanges = cryptoEcosystem.cryptoExchanges[j] + for (let k = 0; k < cryptoExchanges.exchanges.length; k++) { + let cryptoExchange = cryptoExchanges.exchanges[k] + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, cryptoExchange, true) === true) { + let exchange = functionLibraryUiObjectsFromNodes.addUIObject(node, newNodeType) + exchange.payload.referenceParent = cryptoExchange + } + } + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/DependenciesFilter.js b/Integrations/Projects/Superalgos/Function-Libraries/DependenciesFilter.js new file mode 100644 index 000000000..88b9eb45d --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/DependenciesFilter.js @@ -0,0 +1,74 @@ +function newDependenciesFilter() { + thisObject = { + createFilter: createFilter + } + + return thisObject + + function createFilter(node) { + let filters = new Map() + recursiveFilter(node) + + return Array.from(filters.keys()) + + function recursiveFilter(node) { + filter(node.code) + + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + recursiveFilter(node[property.name]) + } + previousPropertyName = property.name + } + } + break + case 'array': { + let nodePropertyArray = node[property.name] + if (nodePropertyArray !== undefined) { + for (let j = 0; j < nodePropertyArray.length; j++) { + let nodeProperty = nodePropertyArray[j] + recursiveFilter(nodeProperty) + } + } + } + break + } + } + } + } + } + + function filter(code) { + if (code === undefined) { return } + + let instructionsArray = code.split(' ') + for (let i = 0; i < instructionsArray.length; i++) { + let instruction = instructionsArray[i] + if (instruction.indexOf('chart') >= 0) { + let parts = instruction.split('.') + let timeFrame = parts[1] + let product = parts[2] + let productParts = product.split(',') + product = productParts[0] + productParts = product.split(')') + product = productParts[0] + + // Example: chart.at01hs.popularSMA.sma200 - chart.at01hs.popularSMA.sma100 < 10 + if (timeFrame !== 'atAnyTimeFrame') { + timeFrame = timeFrame.substring(2, 4) + '-' + timeFrame.substring(4, 7) + } + filters.set(timeFrame + '-' + product, true) + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/NodeChildren.js b/Integrations/Projects/Superalgos/Function-Libraries/NodeChildren.js new file mode 100644 index 000000000..30178d28f --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/NodeChildren.js @@ -0,0 +1,67 @@ +function newNodeChildren() { + thisObject = { + childrenCount: childrenCount + } + + return thisObject + + function childrenCount(parentNode, childNode) { + return countChildrenUiObject(parentNode, childNode) + } + + function countChildrenUiObject(parentNode, childNode) { + let response = { + childrenCount: 0, + childIndex: undefined + } + let parentNodeDefinition = getNodeDefinition(parentNode) + if (parentNodeDefinition !== undefined) { + if (parentNodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (parentNode[property.name] !== undefined) { + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + response.childrenCount++ + if (parentNode[property.name].id === childNode.id) { + response.childIndex = response.childrenCount + } + previousPropertyName = property.name + } + } + break + case 'array': { + let nodeDefinition = getNodeDefinition(childNode) + if (nodeDefinition !== undefined) { + let nodePropertyArray = parentNode[property.name] + for (let j = 0; j < nodePropertyArray.length; j++) { + let child = nodePropertyArray[j] + if (nodeDefinition.chainedToSameType === true) { + if (j < 1) { + response.childrenCount++ + if (child.id === childNode.id) { + response.childIndex = response.childrenCount + } + } + } else { + response.childrenCount++ + if (child.id === childNode.id) { + response.childIndex = response.childrenCount + } + } + } + } + } + break + } + } + } + } + } + + return response + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/NodeCloning.js b/Integrations/Projects/Superalgos/Function-Libraries/NodeCloning.js new file mode 100644 index 000000000..78d08b249 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/NodeCloning.js @@ -0,0 +1,179 @@ +function newNodeCloning() { + const MODULE_NAME = 'Node Cloning' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + thisObject = { + getNodeClone: getNodeClone + } + return thisObject + + function getNodeClone(node) { + let idConversionMap = new Map() + + let inmatureClone = cloneNode(node) + let matureClone = redirectReferenceParents(inmatureClone) + + return matureClone + + function cloneNode(node) { + if (node === undefined) { return } + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + let object = { + type: node.type, + name: node.name, + code: node.code, + config: node.config, + project: node.project + } + + /* Children Nodes */ + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + object[property.name] = cloneNode(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + object[property.name] = [] + for (let m = 0; m < nodePropertyArray.length; m++) { + let protocolNode = cloneNode(nodePropertyArray[m]) + if (protocolNode !== undefined) { + object[property.name].push(protocolNode) + } + } + } + break + } + } + } + } + + object.id = newUniqueId() + idConversionMap.set(node.id, object.id) + + object.savedPayload = getSavedPayload(node) + + return object + } + } + + function redirectReferenceParents(node) { + if (node === undefined) { return } + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + let object = { + type: node.type, + name: node.name, + code: node.code, + config: node.config, + project: node.project + } + + /* Children Nodes */ + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + object[property.name] = redirectReferenceParents(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + object[property.name] = [] + for (let m = 0; m < nodePropertyArray.length; m++) { + let protocolNode = redirectReferenceParents(nodePropertyArray[m]) + if (protocolNode !== undefined) { + object[property.name].push(protocolNode) + } + } + } + break + } + } + } + } + + object.id = node.id + + object.savedPayload = node.savedPayload + + if (object.savedPayload !== undefined) { + if (object.savedPayload.referenceParent !== undefined) { + let newId = idConversionMap.get(object.savedPayload.referenceParent.id) + if (newId !== undefined) { + object.savedPayload.referenceParent.id = newId + } + } + } + + return object + } + } + } + + function getSavedPayload(node) { + if (node.payload === undefined) { return } + let savedPayload = { + position: { + x: node.payload.position.x, + y: node.payload.position.y + }, + targetPosition: { + x: node.payload.targetPosition.x, + y: node.payload.targetPosition.y + }, + floatingObject: { + isPinned: node.payload.floatingObject.isPinned, + isFrozen: (node.payload.floatingObject.isFrozen && node.payload.floatingObject.frozenManually), + isCollapsed: (node.payload.floatingObject.isCollapsed && node.payload.floatingObject.collapsedManually), + angleToParent: (node.payload.floatingObject.angleToParent), + distanceToParent: (node.payload.floatingObject.distanceToParent), + arrangementStyle: (node.payload.floatingObject.arrangementStyle) + }, + uiObject: { + isPlaying: node.payload.uiObject.isPlaying, + isRunning: node.payload.uiObject.isRunning, + shortcutKey: node.payload.uiObject.shortcutKey + } + } + + /* Next for the ones that have a reference parent, we include it */ + if (node.payload.referenceParent !== undefined) { + savedPayload.referenceParent = { + type: node.payload.referenceParent.type, + name: node.payload.referenceParent.name, + id: node.payload.referenceParent.id + } + } else { + /* The referenceParent property can be inherited from the previous saved payload. */ + if (node.savedPayload !== undefined) { + savedPayload.referenceParent = node.savedPayload.referenceParent + } + } + + return savedPayload + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/NodeDeleter.js b/Integrations/Projects/Superalgos/Function-Libraries/NodeDeleter.js new file mode 100644 index 000000000..e1ea53cc4 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/NodeDeleter.js @@ -0,0 +1,138 @@ +function newNodeDeleter() { + thisObject = { + deleteWorkspace: deleteWorkspace, + deleteUIObject: deleteUIObject + } + + return thisObject + + function destroyUiObject(node) { + UI.projects.superalgos.spaces.floatingSpace.uiObjectConstructor.destroyUiObject(node.payload) + } + + function deleteWorkspace(node, rootNodes) { + if (node.rootNodes !== undefined) { + while (node.rootNodes.length > 0) { + let rootNode = node.rootNodes[0] + deleteUIObject(rootNode, rootNodes) + } + } + + completeDeletion(node, rootNodes) + destroyUiObject(node) + cleanNode(node) + } + + function deleteUIObject(node, rootNodes) { + /* First we restore the reference parent to its default state */ + if (node.payload !== undefined && node.payload.referenceParent !== undefined && node.payload.referenceParent.payload !== undefined && node.payload.referenceParent.payload.uiObject !== undefined) { + node.payload.referenceParent.payload.uiObject.isShowing = false + } + + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + /* Remove all of its own children nodes. */ + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + deleteUIObject(node[property.name], rootNodes) + } + previousPropertyName = property.name + } + } + break + case 'array': { + let nodePropertyArray = node[property.name] + if (nodePropertyArray !== undefined) { + while (nodePropertyArray.length > 0) { + deleteUIObject(nodePropertyArray[0], rootNodes) + } + } + } + break + } + } + } + + /* Remove node from parent */ + if (node.payload !== undefined) { + if (node.payload.parentNode !== undefined) { + let parentNodeDefinition = getNodeDefinition(node.payload.parentNode) + if (parentNodeDefinition !== undefined) { + if (parentNodeDefinition.properties !== undefined) { + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (nodeDefinition.propertyNameAtParent === property.name) { + switch (property.type) { + case 'node': { + node.payload.parentNode[property.name] = undefined + } + break + case 'array': { + let nodePropertyArray = node.payload.parentNode[property.name] + if (nodePropertyArray !== undefined) { + for (let j = 0; j < nodePropertyArray.length; j++) { + let arrayItem = nodePropertyArray[j] + if (arrayItem.id === node.id) { + /* If this object is chained to the ones of the same type we need to give the next in the chain the reference to the current chain parent */ + if (nodeDefinition.chainedToSameType === true) { + if (j < nodePropertyArray.length - 1) { + let nextArrayItem = nodePropertyArray[j + 1] + nextArrayItem.payload.chainParent = node.payload.chainParent + } + } + + nodePropertyArray.splice(j, 1) + } + } + } + } + break + } + } + } + } + } + } + } + + completeDeletion(node, rootNodes) + destroyUiObject(node) + cleanNode(node) + } + } + + function completeDeletion(node, rootNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.id === node.id) { + rootNodes.splice(i, 1) + return + } + } + } + + function cleanNode(node) { + if (node === undefined) { return } + if (node.payload === undefined) { return } + + node.payload.targetPosition.x = undefined + node.payload.targetPosition.y = undefined + node.payload.visible = undefined + node.payload.subTitle = undefined + node.payload.title = undefined + node.payload.node = undefined + node.payload.parentNode = undefined + node.payload.chainParent = undefined + node.payload.executeAction = undefined + node.handle = undefined + node.payload = undefined + node.cleaned = true + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/NodePath.js b/Integrations/Projects/Superalgos/Function-Libraries/NodePath.js new file mode 100644 index 000000000..02dc6362c --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/NodePath.js @@ -0,0 +1,56 @@ +function newNodePath() { + thisObject = { + getNodePath: getNodePath + } + + return thisObject + + function getNodePath(node) { + + let nodePath = 'value' + + recursiveGet(node) + + return 'tradingEngine.' + nodePath + + function recursiveGet(node) { + + if (node.payload === undefined) { return } + if (node.payload.parentNode === undefined) { return } + + let nodeDefinition = getNodeDefinition(node.payload.parentNode) + if (nodeDefinition === undefined) { return } + if (nodeDefinition.properties === undefined) { return } + + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + let parentProperty = node.payload.parentNode[property.name] + if (parentProperty === undefined) { continue } + + switch (property.type) { + case 'node': { + if (parentProperty.id === node.id) { + /* We found it */ + nodePath = property.name + '.' + nodePath + recursiveGet(node.payload.parentNode) + } + } + break + case 'array': { + for (let j = 0; j < parentProperty.length; j++) { + let arrayProperty = parentProperty[j] + if (arrayProperty === undefined) {continue} + if (arrayProperty.id === node.id) { + /* We found it */ + nodePath = property.name + '[' + j + ']' + '.' + nodePath + recursiveGet(node.payload.parentNode) + } + } + } + break + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/OnFocus.js b/Integrations/Projects/Superalgos/Function-Libraries/OnFocus.js new file mode 100644 index 000000000..bbbed3985 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/OnFocus.js @@ -0,0 +1,55 @@ +function newOnFocus() { + thisObject = { + getNodeThatIsOnFocus: getNodeThatIsOnFocus + } + return thisObject + + function getNodeThatIsOnFocus(node) { + if (node === undefined || node === null) { return } + if (node.payload === undefined) { return } + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + /* First we ask the question to ourself */ + if (node.payload.uiObject.isOnFocus === true) { + return node + } + + /* Then we check all of its own children nodes. */ + if (node.payload.floatingObject.isCollapsed !== true) { + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + let child = getNodeThatIsOnFocus(node[property.name]) + if (child !== undefined) { + return child + } + } + previousPropertyName = property.name + } + } + break + case 'array': { + let nodePropertyArray = node[property.name] + if (nodePropertyArray !== undefined) { + for (let m = 0; m < nodePropertyArray.length; m++) { + let child = getNodeThatIsOnFocus(nodePropertyArray[m]) + if (child !== undefined) { + return child + } + } + } + } + break + } + } + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/PluginsFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/PluginsFunctions.js new file mode 100644 index 000000000..045fd386b --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/PluginsFunctions.js @@ -0,0 +1,101 @@ +function newPluginsFunctions() { + thisObject = { + pluginMissingProjects: pluginMissingProjects, + pluginMissingDataMines: pluginMissingDataMines, + pluginMissingTradingMines: pluginMissingTradingMines, + pluginMissingTradingSystems: pluginMissingTradingSystems, + pluginMissingTradingEngines: pluginMissingTradingEngines, + pluginMissingSuperScripts: pluginMissingSuperScripts, + pluginMissingTutorials: pluginMissingTutorials + } + return thisObject + + function getPluginFileNames(pluginType, callBack) { + callWebServer(undefined, 'PluginFileNames/' + 'Superalgos' + '/' + pluginType, onResponse) + + function onResponse(err, data) { + if (err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + console.log('Failed to Fetch Plugin ' + pluginType + ' from the Backend') + return + } + + let pluginFileNames = JSON.parse(data) + callBack(pluginFileNames) + } + } + + function addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < fileNames.length; i++) { + let fileName = fileNames[i] + fileName = fileName.replace('.json', '') + if (UI.projects.superalgos.utilities.children.isMissingChildrenByName(node, fileName) === true) { + let child = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Plugin File') + child.name = fileName + } + } + } + + function pluginMissingProjects(node, rootNodes, functionLibraryUiObjectsFromNodes) { + let url = 'ProjectNames' + callWebServer(undefined, url, onResponse) + + function onResponse(err, pProjects) { + let projects = JSON.parse(pProjects) + for (let i = 0; i < projects.length; i++) { + let project = projects[i] + if (UI.projects.superalgos.utilities.children.isMissingChildrenByName(node, project) === true) { + let child = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Plugin Project') + child.name = project + } + } + } + } + + function pluginMissingDataMines(node, rootNodes, functionLibraryUiObjectsFromNodes) { + getPluginFileNames('Data-Mines', onNamesArrived) + + function onNamesArrived(fileNames) { + addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) + } + } + + function pluginMissingTradingMines(node, rootNodes, functionLibraryUiObjectsFromNodes) { + getPluginFileNames('Trading-Mines', onNamesArrived) + + function onNamesArrived(fileNames) { + addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) + } + } + + function pluginMissingTradingSystems(node, rootNodes, functionLibraryUiObjectsFromNodes) { + getPluginFileNames('Trading-Systems', onNamesArrived) + + function onNamesArrived(fileNames) { + addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) + } + } + + function pluginMissingTradingEngines(node, rootNodes, functionLibraryUiObjectsFromNodes) { + getPluginFileNames('Trading-Engines', onNamesArrived) + + function onNamesArrived(fileNames) { + addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) + } + } + + function pluginMissingSuperScripts(node, rootNodes, functionLibraryUiObjectsFromNodes) { + getPluginFileNames('Super-Scripts', onNamesArrived) + + function onNamesArrived(fileNames) { + addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) + } + } + + function pluginMissingTutorials(node, rootNodes, functionLibraryUiObjectsFromNodes) { + getPluginFileNames('Tutorials', onNamesArrived) + + function onNamesArrived(fileNames) { + addPluginFileIfNeeded(node, fileNames, functionLibraryUiObjectsFromNodes) + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/ProtocolNode.js b/Integrations/Projects/Superalgos/Function-Libraries/ProtocolNode.js new file mode 100644 index 000000000..a303b79c4 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/ProtocolNode.js @@ -0,0 +1,207 @@ +function newProtocolNode() { + const MODULE_NAME = 'Protocol Node' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + thisObject = { + getProtocolNode: getProtocolNode + } + return thisObject + + function getProtocolNode(node, removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, lightingPath) { + if (node === undefined) { return } + if (node.payload === undefined) { return } + + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + if (removePersonalData === true && nodeDefinition.isPersonalData === true) { return } + let object = { + type: node.type, + name: node.name, + code: node.code, + config: node.config, + project: node.project + } + + /* Children Nodes */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (node[property.name] !== undefined) { + if (lightingPath === undefined) { + object[property.name] = getProtocolNode(node[property.name], removePersonalData, parseConfig, includeIds, includeReferencesInSavedPayload, includePayload) + } else { + let newLightingPath = getNewLightingPath(lightingPath, node.type, property.childType) + if (newLightingPath !== undefined) { + object[property.name] = getProtocolNode(node[property.name], removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + } + } + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + if (lightingPath === undefined) { + let nodePropertyArray = node[property.name] + object[property.name] = [] + for (let m = 0; m < nodePropertyArray.length; m++) { + let protocolNode = getProtocolNode(nodePropertyArray[m], removePersonalData, parseConfig, includeIds, includeReferencesInSavedPayload, includePayload) + if (protocolNode !== undefined) { + object[property.name].push(protocolNode) + } + } + } else { + let newLightingPath = getNewLightingPath(lightingPath, node.type, property.childType) + if (newLightingPath !== undefined) { + let nodePropertyArray = node[property.name] + object[property.name] = [] + for (let m = 0; m < nodePropertyArray.length; m++) { + let protocolNode = getProtocolNode(nodePropertyArray[m], removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + if (protocolNode !== undefined) { + object[property.name].push(protocolNode) + } + } + } + } + } + break + } + } + } + } + + /* Parent Nodes */ + if (node.payload.parentNode !== undefined) { + let newLightingPath = getNewLightingPath(lightingPath, node.type, node.payload.parentNode.type) + if (newLightingPath !== undefined) { + object.parentNode = getProtocolNode(node.payload.parentNode, removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + } else { + let newLightingPath = getNewLightingPath(lightingPath, node.type, node.payload.parentNode.type) + } + } + if (node.payload.referenceParent !== undefined) { + let newLightingPath = getNewLightingPath(lightingPath, node.type, node.payload.referenceParent.type) + if (newLightingPath !== undefined) { + object.referenceParent = getProtocolNode(node.payload.referenceParent, removePersonalData, parseConfig, includeIds, includePayload, includeReferencesInSavedPayload, newLightingPath) + } + } + + if (parseConfig && object.config !== undefined && nodeDefinition.editors !== undefined) { + if (nodeDefinition.editors.config === true) { + try { + object.config = JSON.parse(node.config) + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> err = ' + err.stack) } + if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.type = ' + node.type) } + if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.name = ' + node.name) } + if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.config = ' + node.config) } + if (ERROR_LOG === true) { logger.write('[ERROR] getProtocolNode -> default -> node.id = ' + node.id) } + } + } + } + + if (includeIds) { + object.id = node.id + } + if (includePayload) { + object.savedPayload = getSavedPayload(node, includeReferencesInSavedPayload) + } + + return object + } + } + + function getNewLightingPath(lightingPath, currentNodeType, nextNodeType) { + if (lightingPath === undefined) { return } + + if (currentNodeType === undefined) { return } + if (nextNodeType === undefined) { return } + + let currentNodePosition = lightingPath.indexOf('->' + currentNodeType + '->') + let nextNodePosition = lightingPath.indexOf('->' + nextNodeType + '->') + + /* + In some situations, the current and next node are of the same type. + When that happens, we need to search for the second instance of that type + at the lighting path. + */ + if (currentNodeType === nextNodeType) { + let tempLightingPath = lightingPath.substring(currentNodePosition + currentNodeType.length, lightingPath.length + 1) + nextNodePosition = tempLightingPath.indexOf('->' + nextNodeType + '->') + } + + let newLightingPath = lightingPath.substring(nextNodePosition, lightingPath.length + 1) + + if (nextNodePosition > currentNodePosition) { + return newLightingPath + } else { + return + } + } + + function getSavedPayload(node, includeReferencesInSavedPayload) { + if (node.payload === undefined) { return } + let savedPayload = { + position: { + x: node.payload.position.x, + y: node.payload.position.y + }, + targetPosition: { + x: node.payload.targetPosition.x, + y: node.payload.targetPosition.y + }, + floatingObject: { + isPinned: node.payload.floatingObject.isPinned, + isFrozen: (node.payload.floatingObject.isFrozen && node.payload.floatingObject.frozenManually), + isCollapsed: (node.payload.floatingObject.isCollapsed && node.payload.floatingObject.collapsedManually), + angleToParent: (node.payload.floatingObject.angleToParent), + distanceToParent: (node.payload.floatingObject.distanceToParent), + arrangementStyle: (node.payload.floatingObject.arrangementStyle) + }, + uiObject: { + isPlaying: node.payload.uiObject.isPlaying, + isRunning: node.payload.uiObject.isRunning, + shortcutKey: node.payload.uiObject.shortcutKey + } + } + + if (node.payload.frame !== undefined) { + savedPayload.frame = { + position: { + x: node.payload.frame.position.x, + y: node.payload.frame.position.y + }, + width: node.payload.frame.width, + height: node.payload.frame.height, + radius: node.payload.frame.radius + } + } + + if (node.payload.tutorial !== undefined) { + savedPayload.tutorial = { + status: node.payload.tutorial.status + } + } + + if (includeReferencesInSavedPayload) { + /* Next for the ones that have a reference parent, we include it */ + if (node.payload.referenceParent !== undefined) { + savedPayload.referenceParent = { + type: node.payload.referenceParent.type, + name: node.payload.referenceParent.name, + id: node.payload.referenceParent.id + } + } else { + /* The referenceParent property can be inherited from the previous saved payload. */ + if (node.savedPayload !== undefined) { + savedPayload.referenceParent = node.savedPayload.referenceParent + } + } + } + return savedPayload + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/ReferenceAttachDetach.js b/Integrations/Projects/Superalgos/Function-Libraries/ReferenceAttachDetach.js new file mode 100644 index 000000000..028e35842 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/ReferenceAttachDetach.js @@ -0,0 +1,36 @@ +function newReferenceAttachDetach() { + thisObject = { + referenceDetachNode: referenceDetachNode, + referenceAttachNode: referenceAttachNode + } + + return thisObject + + function referenceDetachNode(node) { + completeDetachment(node) + } + + function referenceAttachNode(node, attachToNode) { + completeAttachment(node, attachToNode) + } + + function completeAttachment(node, attachToNode) { + node.payload.referenceParent = attachToNode + } + + function completeDetachment(node) { + if (node.payload !== undefined) { + if (node.payload.referenceParent !== undefined) { + if (node.payload.referenceParent.payload !== undefined) { + if (node.payload.referenceParent.payload.uiObject !== undefined) { + node.payload.referenceParent.payload.uiObject.isShowing = false + } + } + } + node.payload.referenceParent = undefined + if (node.savedPayload !== undefined) { + node.savedPayload.referenceParent = undefined + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/SessionFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/SessionFunctions.js new file mode 100644 index 000000000..d875ae330 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/SessionFunctions.js @@ -0,0 +1,235 @@ +function newSessionFunctions() { + thisObject = { + syncronizeSessionWithBackEnd: syncronizeSessionWithBackEnd, + runSession: runSession, + stopSession: stopSession + } + + return thisObject + + function syncronizeSessionWithBackEnd(node) { + let networkNode = validations(node) + if (networkNode === undefined) { + /* Nodes that do not belong to a network can not get ready. */ + return + } + + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(networkNode.id) + + /* First we setup everything so as to listen to the response from the Task Server */ + let eventSubscriptionIdOnStatus + let key = node.name + '-' + node.type + '-' + node.id + eventsServerClient.listenToEvent(key, 'Status Response', undefined, node.id, onResponse, onStatus) + + function onResponse(message) { + eventSubscriptionIdOnStatus = message.eventSubscriptionId + } + + function onStatus(message) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnStatus, node.id) + if (message.event.status === 'Session Runnning' ) { + node.payload.uiObject.menu.internalClick('Run Session') + } + } + + /* Second we ask the Task Server if this Session is Running. */ + eventsServerClient.raiseEvent(key, 'Session Status') + } + + function runSession(node, functionLibraryProtocolNode, functionLibraryDependenciesFilter, resume, callBackFunction) { + let networkNode = validations(node) + if (networkNode === undefined) { + /* This means that the validations failed. */ + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(networkNode.id) + + node.payload.uiObject.run(eventsServerClient, callBackFunction) + + let key = node.name + '-' + node.type + '-' + node.id + + let lightingPath = '' + + 'Trading System->' + + 'Dynamic Indicators->Indicator Function->Formula->' + + 'Trading Strategy->' + + 'Trigger Stage->Trigger On Event->Trigger Off Event->Take Position Event->' + + 'Announcement->Announcement Formula->' + + 'Open Stage->' + + 'Manage Stage->' + + 'Managed Stop Loss->Managed Take Profit->' + + 'Phase->Formula->Next Phase Event->Move To Phase Event->Phase->' + + 'Situation->Condition->Javascript Code->' + + 'Close Stage->' + + 'Initial Targets->Target Size In Base Asset->Target Size In Quoted Asset->Target Rate->Formula->' + + 'Announcement->Announcement Formula->' + + 'Open Execution->Close Execution->' + + 'Execution Algorithm->Market Buy Order->Market Sell Order->Limit Buy Order->Limit Sell Order->' + + 'Order Rate->Formula->' + + 'Create Order Event->Cancel Order Event->' + + 'Size In Base Asset->Size In Quoted Asset->Position Rate->Formula->' + + 'Situation->Condition->Javascript Code->' + + 'Market Order->Limit Order->' + + 'Simulated Exchange Events->Simulated Partial Fill->Simulated Actual Rate->Simulated Fees Paid->Formula->' + + let tradingSystem = functionLibraryProtocolNode.getProtocolNode(node.tradingSystemReference.payload.referenceParent, false, true, true, false, false, lightingPath) + + lightingPath = '' + + 'Trading Engine->' + + 'Dynamic Indicators->Indicator Function->' + + 'Current->Last->Previous->' + + 'Episode->' + + 'Episode Base Asset->Episode Quoted Asset->Episode Counters->Episode Statistics->' + + 'Strategies->Positions->Orders->Hits->Fails->' + + 'Profit Loss->Hit Ratio->Hit Fail->Days->ROI->Annualized Rate Of Return->User Defined Statistic->' + + 'Candle->Cycle->' + + 'Begin->End->Last Begin->Last End->Open->Close->Min->Max->Index->' + + 'Distance To Event->' + + 'Head Of The Market->Process Date->' + + 'Trigger On->Trigger Off->Take Position->Close Position->Next Phase->Move To Phase->Create Order->Cancel Order->Close Order->' + + 'Strategy->' + + 'Strategy Counters->' + + 'Position->' + + 'Entry Target Rate->Exit Target Rate->' + + 'Stop Loss->Stop Loss Phase->Stop Loss Position->' + + 'Take Profit->Take Profit Phase->Take Profit Position->' + + 'Position Counters->' + + 'Position Statistics->Days->User Defined Statistic->' + + 'Position Base Asset->Position Quoted Asset->Entry Target Size->Exit Target Size->' + + 'Profit Loss->ROI->Hit Fail->' + + 'Exchange Orders->Market Buy Orders->Market Sell Orders->Limit Buy Orders->Limit Sell Orders->' + + 'Market Order->Limit Order->' + + 'Exchange Id->Rate->Order Name->Algorithm Name->Lock->' + + 'Order Counters->' + + 'Order Base Asset->Order Quoted Asset->' + + 'Size->Size Filled->Fees Paid->' + + 'Order Statistics->Percentage Filled->Actual Rate->Days->User Defined Statistic->' + + 'Strategy Trigger Stage->Strategy Open Stage->Strategy Manage Stage->Strategy Close Stage->' + + 'Begin->End->Exit Type->Status->Begin Rate->End Rate->Stage Base Asset->Stage Quoted Asset->Size Placed->Target Size->Size Filled->Fees Paid->Stage Defined In->' + + 'Serial Number->Identifier->Begin->End->Begin Rate->End Rate->Strategy Name->Status->Exit Type->' + + 'Balance->Begin Balance->End Balance->' + + 'Index->Situation Name->Formula->Periods->' + + let tradingEngine = functionLibraryProtocolNode.getProtocolNode(node.tradingEngineReference.payload.referenceParent, false, true, true, false, false, lightingPath) + + lightingPath = '' + + 'Backtesting Session->Paper Trading Session->Forward Testing Session->Live Trading Session->' + + 'Parameters->' + + 'Session Base Asset->Session Quoted Asset->Time Range->Time Frame->Slippage->Fee Structure->Snapshots->Heartbeats->User Defined Parameters->' + + 'Exchange Account Asset->Asset->' + + 'Social Bots->Telegram Bot->' + + let session = functionLibraryProtocolNode.getProtocolNode(node, false, true, true, false, false, lightingPath) + + let dependencyFilter = functionLibraryDependenciesFilter.createFilter(node.tradingSystemReference.payload.referenceParent) + + /* Raise event to run the session */ + let event = { + session: JSON.stringify(session), + tradingSystem: JSON.stringify(tradingSystem), + tradingEngine: JSON.stringify(tradingEngine), + dependencyFilter: JSON.stringify(dependencyFilter), + resume: resume + } + + if (resume !== true) { + eventsServerClient.raiseEvent(key, 'Run Session', event) + } else { + eventsServerClient.raiseEvent(key, 'Resume Session', event) + } + + if (node.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + } + + function stopSession(node, functionLibraryProtocolNode, callBackFunction) { + let networkNode = validations(node) + if (networkNode === undefined) { + /* This means that the validations failed. */ + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(networkNode.id) + + let key = node.name + '-' + node.type + '-' + node.id + eventsServerClient.raiseEvent(key, 'Stop Session') + + node.payload.uiObject.stop(callBackFunction) + } + + function validations(node) { + if (node.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs a Process Instance parent to be able to run.') + return + } + + if (node.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside a Trading Process Instance.') + return + } + + if (node.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside a Task.') + return + } + + if (node.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside a Task Manager.') + return + } + + let taskManager = node.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode + + if (taskManager.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside Mine Tasks.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside Market Tasks.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside Exchange Tasks.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside a Testing or Production Environment.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Session needs to be inside a Network Node.') + return + } + + let networkNode = taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode + + if (node.tradingSystemReference === undefined) { + node.payload.uiObject.setErrorMessage('Session needs a child Trading System Reference.') + return + } + + if (node.tradingEngineReference === undefined) { + node.payload.uiObject.setErrorMessage('Session needs a child Trading Engine Reference.') + return + } + + if (node.tradingSystemReference.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('Trading System Reference needs to reference a Trading System.') + return + } + + if (node.tradingEngineReference.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('Trading Engine Reference needs to reference a Trading Engine.') + return + } + return networkNode + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/ShortcutKeys.js b/Integrations/Projects/Superalgos/Function-Libraries/ShortcutKeys.js new file mode 100644 index 000000000..6d3a76aa5 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/ShortcutKeys.js @@ -0,0 +1,54 @@ +function newShortcutKeys() { + thisObject = { + getNodeByShortcutKey: getNodeByShortcutKey + } + return thisObject + + function getNodeByShortcutKey(node, searchingKey) { + if (node === undefined) { return } + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + /* Check Self first */ + if (node.payload.uiObject.shortcutKey === searchingKey) { + return node + } + + /* Check all of its own children nodes. */ + if (node.payload.floatingObject.isCollapsed !== true) { + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + let child = getNodeByShortcutKey(node[property.name], searchingKey) + if (child !== undefined) { + return child + } + } + previousPropertyName = property.name + } + } + break + case 'array': { + let nodePropertyArray = node[property.name] + if (nodePropertyArray !== undefined) { + for (let m = 0; m < nodePropertyArray.length; m++) { + let child = getNodeByShortcutKey(nodePropertyArray[m], searchingKey) + if (child !== undefined) { + return child + } + } + } + } + break + } + } + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/SuperScripts.js b/Integrations/Projects/Superalgos/Function-Libraries/SuperScripts.js new file mode 100644 index 000000000..bbb214083 --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/SuperScripts.js @@ -0,0 +1,90 @@ +function newSuperScriptsFunctions() { + thisObject = { + runSuperScript: runSuperScript + } + + return thisObject + + function runSuperScript(node, rootNodes, functionLibraryNodeCloning, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) { + try { + let clone = functionLibraryNodeCloning.getNodeClone + let executionResult = true + let superAction = node + + function setup(templateClone, target, exchange, exchangeNodeType, arrayPropertyName) { + spawnPosition.x = target.payload.position.x + spawnPosition.y = target.payload.position.y + + if (exchange === undefined) { + functionLibraryUiObjectsFromNodes.createUiObjectFromNode(templateClone, target, target) + } else { + let definition = getNodeDefinition(target) + for (let i = 0; i < definition.properties.length; i++) { + let property = definition.properties[i] + if (property.childType === exchangeNodeType) { + let exchanges = target[property.name] + if (exchanges === undefined) { + exchanges = [] + } + let targetExchange + for (let j = 0; j < exchanges.length; j++) { + let thisExchange = exchanges[j] + if (thisExchange.payload.referenceParent !== undefined) { + if (thisExchange.payload.referenceParent.id === exchange.id) { + targetExchange = thisExchange + } + } + } + if (targetExchange === undefined) { + targetExchange = functionLibraryUiObjectsFromNodes.addUIObject(target, exchangeNodeType) + targetExchange.payload.referenceParent = exchange + } + let nodesArray = targetExchange[arrayPropertyName] + nodesArray.push(templateClone) + functionLibraryUiObjectsFromNodes.createUiObjectFromNode(templateClone, targetExchange, targetExchange) + } + } + } + } + + function deleteNode(node) { + functionLibraryNodeDeleter.deleteUIObject(node, rootNodes) + } + + /* Validations */ + if (executionResult === true && node.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Super Action node needs a Parent Node.') + executionResult = false + } + if (executionResult === true && node.payload.referenceParent === undefined) { + node.payload.uiObject.setErrorMessage('Super Action node needs to reference a Master Script Node.') + executionResult = false + } + if (executionResult === true && node.payload.referenceParent.javascriptCode === undefined) { + node.payload.uiObject.setErrorMessage('Master Script Node needs to have a child Javascript Code Node.') + executionResult = false + } + + let masterScript = node.payload.referenceParent + + eval(masterScript.javascriptCode.code) + + let masterScriptInstance = newNewMasterScriptInstance() + + if (executionResult === true) { + executionResult = masterScriptInstance.runValidations(superAction, masterScript) + } + if (executionResult === true) { + executionResult = masterScriptInstance.runScript(superAction, masterScript) + } + if (executionResult === true) { + superAction.payload.uiObject.setValue('Execution Completed Successfully') + } else { + superAction.payload.uiObject.setValue('Execution Failed') + } + } catch (err) { + superAction.payload.uiObject.setErrorMessage(err.message) + superAction.payload.uiObject.setValue('Execution Failed') + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/TaskFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/TaskFunctions.js new file mode 100644 index 000000000..b13af9c9a --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/TaskFunctions.js @@ -0,0 +1,619 @@ +function newTaskFunctions() { + thisObject = { + syncronizeTaskWithBackEnd: syncronizeTaskWithBackEnd, + + runTask: runTask, + stopTask: stopTask, + runAllTasks: runAllTasks, + stopAllTasks: stopAllTasks, + runAllTaskManagers: runAllTaskManagers, + stopAllTaskManagers: stopAllTaskManagers, + + runAllExchangeDataTasks: runAllExchangeDataTasks, + stopAllExchangeDataTasks: stopAllExchangeDataTasks, + runAllExchangeTradingTasks: runAllExchangeTradingTasks, + stopAllExchangeTradingTasks: stopAllExchangeTradingTasks, + + runAllMarketDataTasks: runAllMarketDataTasks, + stopAllMarketDataTasks: stopAllMarketDataTasks, + runAllMarketTradingTasks: runAllMarketTradingTasks, + stopAllMarketTradingTasks: stopAllMarketTradingTasks, + + runAllDataMineTasks: runAllDataMineTasks, + stopAllDataMineTasks: stopAllDataMineTasks, + runAllTradingMineTasks: runAllTradingMineTasks, + stopAllTradingMineTasks: stopAllTradingMineTasks, + + addMissingExchangeDataTasks: addMissingExchangeDataTasks, + addMissingMarketDataTasks: addMissingMarketDataTasks, + addMissingDataMineTasks: addMissingDataMineTasks, + addMissingExchangeTradingTasks: addMissingExchangeTradingTasks, + addMissingMarketTradingTasks: addMissingMarketTradingTasks, + addMissingTradingMineTasks: addMissingTradingMineTasks, + addAllTasks: addAllTasks + } + + return thisObject + + function syncronizeTaskWithBackEnd(node) { + let networkNode = validations(node) + if (networkNode === undefined) { + /* Nodes that do not belong to a network can not get ready. */ + return + } + + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(networkNode.id) + + /* First we setup everything so as to listen to the response from the Task Manger */ + let eventSubscriptionIdOnStatus + let key = 'Task Client - ' + node.id + eventsServerClient.listenToEvent(key, 'Task Status', undefined, node.id, onResponse, onStatus) + + function onResponse(message) { + eventSubscriptionIdOnStatus = message.eventSubscriptionId + } + + function onStatus(message) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnStatus, node.id) + if (message.event.status === 'Task Process Running' ) { + node.payload.uiObject.menu.internalClick('Run Task') + } + } + + /* Second we ask the Task Manager if this Task has a process Running. */ + let event = { + taskId: node.id + } + + eventsServerClient.raiseEvent('Task Manager', 'Task Status', event) + } + + function runTask(node, functionLibraryProtocolNode, isDebugging, callBackFunction) { + + let networkNode = validations(node) + if (networkNode === undefined) { + /* This means that the validations failed. */ + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(networkNode.id) + + for (let i = 0; i < node.bot.processes.length; i++) { + let process = node.bot.processes[i] + process.payload.uiObject.run(eventsServerClient) + } + + let taskLightingPath = '->Task->' + + 'Sensor Bot Instance->' + + 'Indicator Bot Instance->Time Frames Filter->' + + 'Trading Bot Instance->' + + 'Sensor Process Instance->Indicator Process Instance->Trading Process Instance->' + + 'Execution Started Event->' + + 'Key Reference->Exchange Account Key->' + + 'Task Manager->' + + 'Data Mine Tasks->Trading Mine Tasks->' + + 'Market Data Tasks->Market Trading Tasks->' + + 'Exchange Data Tasks->Exchange Trading Tasks->' + + 'Market->Exchange Markets->Crypto Exchange->' + + 'Market Base Asset->Market Quoted Asset->Asset->' + + 'Backtesting Session->Live Trading Session->Paper Trading Session->Forward Testing Session->' + + 'Process Definition->' + + 'Process Output->' + + 'Output Dataset Folder->Output Dataset Folder->Output Dataset Folder->Output Dataset Folder->Output Dataset Folder->' + + 'Output Dataset->Dataset Definition->Product Definition->' + + 'Process Dependencies->' + + 'Status Dependency->Status Report->Process Definition->' + + 'Data Mine Data Dependencies->Bot Data Dependencies->' + + 'Data Dependency Folder->Data Dependency Folder->Data Dependency Folder->Data Dependency Folder->Data Dependency Folder->' + + 'Data Dependency->Dataset Definition->Product Definition->' + + 'Record Definition->Record Property->Formula->' + + 'Data Building Procedure->' + + 'Procedure Initialization->Javascript Code->' + + 'Procedure Loop->Javascript Code->' + + 'Calculations Procedure->' + + 'Procedure Initialization->Javascript Code->' + + 'Procedure Loop->Javascript Code->' + + 'Status Report->' + + 'Execution Finished Event->' + + 'Execution Started Event->Execution Finished Event->Process Definition->' + + 'Sensor Bot->' + + 'Product Definition Folder->Product Definition Folder->Product Definition Folder->Product Definition Folder->Product Definition Folder->' + + 'Indicator Bot->' + + 'Trading Bot->' + + 'Data Mine->Trading Mine->' + + let taskDefinition = functionLibraryProtocolNode.getProtocolNode(node, false, true, true, false, false, taskLightingPath) + + let networkLightingPath = '->Network->Network Node->' + + 'Data Storage->Data Mines Data->Exchange Data Products->' + + 'Market Data Products->' + + 'Data Mine Products->Bot Products->' + + 'Data Product Folder->Data Product Folder->Data Product Folder->Data Product Folder->Data Product Folder->' + + 'Data Product->Product Definition->' + + 'Data Mining->Testing Environment->Production Environment->' + + 'Exchange Data Tasks->Exchange Trading Tasks->Crypto Exchange->' + + 'Market Data Tasks->Market Trading Tasks->Market->' + + 'Data Mine Tasks->Trading Mine Tasks->' + + 'Task Manager->Task->' + + 'Indicator Bot Instance->Sensor Bot Instance->Trading Bot Instance->' + + 'Indicator Process Instance->Sensor Process Instance->Trading Process Instance->' + + 'Paper Trading Session->Forward Testing Session->Backtesting Session->Live Trading Session->' + + 'Market->' + + 'Process Definition->' + + let networkDefinition = functionLibraryProtocolNode.getProtocolNode(networkNode.payload.parentNode, false, true, true, false, false, networkLightingPath) + + let event = { + appSchema: JSON.stringify(SCHEMAS_BY_PROJECT.get('Superalgos').array.appSchema), + taskId: node.id, + taskName: node.name, + taskDefinition: JSON.stringify(taskDefinition), + networkDefinition: JSON.stringify(networkDefinition) + } + + if (isDebugging === true) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) + eventsServerClient.raiseEvent('Task Server', 'Debug Task Started', event) + return + } + + node.payload.uiObject.run(eventsServerClient, callBackFunction) + eventsServerClient.raiseEvent('Task Manager', 'Run Task', event) + } + + function stopTask(node, functionLibraryProtocolNode, callBackFunction) { + let networkNode = validations(node) + if (networkNode === undefined) { + /* This means that the validations failed. */ + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(networkNode.id) + + let event = { + taskId: node.id, + taskName: node.name + } + + node.payload.uiObject.stop(callBackFunction) + eventsServerClient.raiseEvent('Task Manager', 'Stop Task', event) + + if (node.bot === undefined) { return } + if (node.bot.processes.length === 0) { return } + + for (let i = 0; i < node.bot.processes.length; i++) { + let process = node.bot.processes[i] + process.payload.uiObject.stop() + } + } + + function validations(node) { + if (node.bot === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to have a Bot Instance.') + return + } + if (node.bot.processes.length === 0) { + node.payload.uiObject.setErrorMessage('Task Bot Instance needs to have al least once Process Instance.') + return + } + + if (node.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to be inside a Task Manager.') + return + } + + let taskManager = node.payload.parentNode + + if (taskManager.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to be inside Mine Tasks.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to be inside Market Tasks.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to be inside Exchange Tasks.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to be inside a Data Mining, Testing or Production Environment.') + return + } + + if (taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { + node.payload.uiObject.setErrorMessage('Task needs to be inside a Network Node.') + return + } + + let networkNode = taskManager.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode.payload.parentNode + if (UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(networkNode.payload, 'host') === undefined) { + node.payload.uiObject.setErrorMessage('Network Node needs to have a valid Host property at its config.') + return + } + + if (UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(networkNode.payload, 'webPort') === undefined) { + node.payload.uiObject.setErrorMessage('Network Node needs to have a valid webPort property at its config.') + return + } + + if (UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(networkNode.payload, 'webSocketsPort') === undefined) { + node.payload.uiObject.setErrorMessage('Network Node needs to have a valid webSocketsPort property at its config.') + return + } + + return networkNode + } + + function runAllTasks(taskManager, functionLibraryProtocolNode) { + for (let i = 0; i < taskManager.tasks.length; i++) { + let node = taskManager.tasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run Task') + } + } + + function stopAllTasks(taskManager, functionLibraryProtocolNode) { + for (let i = 0; i < taskManager.tasks.length; i++) { + let node = taskManager.tasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop Task') + } + } + + function runAllTaskManagers(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.taskManagers.length; i++) { + let node = parent.taskManagers[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Tasks') + menu.internalClick('Run All Tasks') + } + } + + function stopAllTaskManagers(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.taskManagers.length; i++) { + let node = parent.taskManagers[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Tasks') + menu.internalClick('Stop All Tasks') + } + } + + function runAllExchangeDataTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.exchangeDataTasks.length; i++) { + let node = parent.exchangeDataTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Market Data Tasks') + menu.internalClick('Run All Market Data Tasks') + } + } + + function stopAllExchangeDataTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.exchangeDataTasks.length; i++) { + let node = parent.exchangeDataTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Market Data Tasks') + menu.internalClick('Stop All Market Data Tasks') + } + } + + function runAllExchangeTradingTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.exchangeTradingTasks.length; i++) { + let node = parent.exchangeTradingTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Market Trading Tasks') + menu.internalClick('Run All Market Trading Tasks') + } + } + + function stopAllExchangeTradingTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.exchangeTradingTasks.length; i++) { + let node = parent.exchangeTradingTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Market Trading Tasks') + menu.internalClick('Stop All Market Trading Tasks') + } + } + + function runAllMarketDataTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.marketDataTasks.length; i++) { + let node = parent.marketDataTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Data Mine Tasks') + menu.internalClick('Run All Data Mine Tasks') + } + } + + function stopAllMarketDataTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.marketDataTasks.length; i++) { + let node = parent.marketDataTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Data Mine Tasks') + menu.internalClick('Stop All Data Mine Tasks') + } + } + + function runAllMarketTradingTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.marketTradingTasks.length; i++) { + let node = parent.marketTradingTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Trading Mine Tasks') + menu.internalClick('Run All Trading Mine Tasks') + } + } + + function stopAllMarketTradingTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.marketTradingTasks.length; i++) { + let node = parent.marketTradingTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Trading Mine Tasks') + menu.internalClick('Stop All Trading Mine Tasks') + } + } + + function runAllDataMineTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.dataMineTasks.length; i++) { + let node = parent.dataMineTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Task Managers') + menu.internalClick('Run All Task Managers') + } + } + + function stopAllDataMineTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.dataMineTasks.length; i++) { + let node = parent.dataMineTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Task Managers') + menu.internalClick('Stop All Task Managers') + } + } + + function runAllTradingMineTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.tradingMineTasks.length; i++) { + let node = parent.tradingMineTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Run All Task Managers') + menu.internalClick('Run All Task Managers') + } + } + + function stopAllTradingMineTasks(parent, functionLibraryProtocolNode) { + for (let i = 0; i < parent.tradingMineTasks.length; i++) { + let node = parent.tradingMineTasks[i] + let menu = node.payload.uiObject.menu + + menu.internalClick('Stop All Task Managers') + menu.internalClick('Stop All Task Managers') + } + } + + function addMissingExchangeDataTasks(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingExchangeTasks(node, rootNodes, 'Exchange Data Tasks', functionLibraryUiObjectsFromNodes) + } + + function addMissingExchangeTradingTasks(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingExchangeTasks(node, rootNodes, 'Exchange Trading Tasks', functionLibraryUiObjectsFromNodes) + } + + function addMissingExchangeTasks(node, rootNodes, newNodeType, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Crypto Ecosystem') { + let cryptoEcosystem = rootNode + for (let j = 0; j < cryptoEcosystem.cryptoExchanges.length; j++) { + let cryptoExchanges = cryptoEcosystem.cryptoExchanges[j] + for (let k = 0; k < cryptoExchanges.exchanges.length; k++) { + let cryptoExchange = cryptoExchanges.exchanges[k] + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, cryptoExchange, true) === true) { + let exchangeTasks = functionLibraryUiObjectsFromNodes.addUIObject(node, newNodeType) + exchangeTasks.payload.referenceParent = cryptoExchange + } + } + } + } + } + } + + function addMissingMarketDataTasks(node, functionLibraryUiObjectsFromNodes) { + addMissingMarketTasks(node, 'Market Data Tasks', functionLibraryUiObjectsFromNodes) + } + + function addMissingMarketTradingTasks(node, functionLibraryUiObjectsFromNodes) { + addMissingMarketTasks(node, 'Market Trading Tasks', functionLibraryUiObjectsFromNodes) + } + + function addMissingMarketTasks(node, newNodeType, functionLibraryUiObjectsFromNodes) { + if (node.payload === undefined) { return } + if (node.payload.referenceParent === undefined) { return } + if (node.payload.referenceParent.exchangeMarkets === undefined) { return } + + let markets = node.payload.referenceParent.exchangeMarkets.markets + + for (let i = 0; i < markets.length; i++) { + let market = markets[i] + + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, market, true) === true) { + let marketDataTasks = functionLibraryUiObjectsFromNodes.addUIObject(node, newNodeType) + marketDataTasks.payload.referenceParent = market + } + } + } + + function addMissingDataMineTasks(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingMineTasks(node, rootNodes, 'Data Mine', 'Data Mine Tasks', functionLibraryUiObjectsFromNodes) + } + + function addMissingTradingMineTasks(node, rootNodes, functionLibraryUiObjectsFromNodes) { + addMissingMineTasks(node, rootNodes, 'Trading Mine', 'Trading Mine Tasks', functionLibraryUiObjectsFromNodes) + } + + function addMissingMineTasks(node, rootNodes, rootNodeType, newNodeType, functionLibraryUiObjectsFromNodes) { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === rootNodeType) { + let dataMine = rootNode + + if (UI.projects.superalgos.utilities.children.isMissingChildren(node, dataMine, true) === true) { + let dataMineTasks = functionLibraryUiObjectsFromNodes.addUIObject(node, newNodeType) + dataMineTasks.payload.referenceParent = dataMine + } + } + } + } + + function addAllTasks(node, rootNodes, functionLibraryUiObjectsFromNodes) { + if (node.payload === undefined) { return } + if (node.payload.referenceParent === undefined) { return } + + let taskManager = functionLibraryUiObjectsFromNodes.addUIObject(node, 'Task Manager') + taskManager.name = node.payload.referenceParent.name + taskManager.payload.floatingObject.collapseToggle() + + switch (node.type) { + case 'Data Mine Tasks': { + addDataTasks() + break + } + case 'Trading Mine Tasks': { + addTradingTasks() + break + } + } + + function addDataTasks() { + addTaskForTradinSystem() + } + + function addTradingTasks() { + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode.type === 'Trading System') { + addTaskForTradinSystem(rootNode) + } + } + } + + function addTaskForTradinSystem(tradingSystem) { + let mine = node.payload.referenceParent + + addTasksForBotArray(mine.sensorBots) + addTasksForBotArray(mine.indicatorBots) + addTasksForBotArray(mine.tradingBots) + + function addTasksForBotArray(botsArray) { + if (botsArray === undefined) { return } + + for (let i = 0; i < botsArray.length; i++) { + let bot = botsArray[i] + + let task = functionLibraryUiObjectsFromNodes.addUIObject(taskManager, 'Task') + + if (tradingSystem !== undefined) { + task.name = tradingSystem.name + } else { + task.name = bot.name + } + + let botInstance + switch (bot.type) { + case 'Sensor Bot': { + botInstance = functionLibraryUiObjectsFromNodes.addUIObject(task, 'Sensor Bot Instance') + botInstance.name = bot.name + break + } + case 'Indicator Bot': { + botInstance = functionLibraryUiObjectsFromNodes.addUIObject(task, 'Indicator Bot Instance') + botInstance.name = bot.name + break + } + case 'Trading Bot': { + botInstance = functionLibraryUiObjectsFromNodes.addUIObject(task, 'Trading Bot Instance') + botInstance.name = bot.name + break + } + } + + for (let j = 0; j < bot.processes.length; j++) { + let process = bot.processes[j] + let processInstance + switch (bot.type) { + case 'Sensor Bot': { + processInstance = functionLibraryUiObjectsFromNodes.addUIObject(botInstance, 'Sensor Process Instance') + processInstance.payload.referenceParent = process + break + } + case 'Indicator Bot': { + processInstance = functionLibraryUiObjectsFromNodes.addUIObject(botInstance, 'Indicator Process Instance') + processInstance.payload.referenceParent = process + break + } + case 'Trading Bot': { + processInstance = functionLibraryUiObjectsFromNodes.addUIObject(botInstance, 'Trading Process Instance') + processInstance.payload.referenceParent = process + + if (node.payload.parentNode === undefined) { return } + if (node.payload.parentNode.payload === undefined) { return } + if (node.payload.parentNode.payload.parentNode === undefined) { return } + if (node.payload.parentNode.payload.parentNode.payload === undefined) { return } + if (node.payload.parentNode.payload.parentNode.payload.parentNode === undefined) { return } + + let environment = node.payload.parentNode.payload.parentNode.payload.parentNode + let session + + switch (environment.type) { + case 'Testing Environment': { + addSession('Backtesting Session') + break + } + case 'Production Environment': { + addSession('Live Trading Session') + break + } + } + break + + function addSession(sessionType) { + session = functionLibraryUiObjectsFromNodes.addUIObject(processInstance, sessionType) + session.name = task.name + let config = JSON.parse(session.config) + config.folderName = session.name.split(" ").join("-") + session.config = JSON.stringify(config) + + for (let m = 0; m < rootNodes.length; m++) { + let rootNode = rootNodes[m] + if (rootNode.type === 'Trading Engine' && rootNode.isPlugin === true) { + let tradingEngine = rootNode + session.tradingEngineReference.payload.referenceParent = tradingEngine + session.tradingSystemReference.payload.referenceParent = tradingSystem + } + } + } + } + } + processInstance.payload.floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_90 + } + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/UiObjectsFromNodes.js b/Integrations/Projects/Superalgos/Function-Libraries/UiObjectsFromNodes.js new file mode 100644 index 000000000..c4a06715f --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/UiObjectsFromNodes.js @@ -0,0 +1,656 @@ +function newUiObjectsFromNodes() { + thisObject = { + syncronizeTasksFoundAtWorkspaceWithBackEnd: syncronizeTasksFoundAtWorkspaceWithBackEnd, + syncronizeSessionsFoundAtWorkspaceWithBackEnd: syncronizeSessionsFoundAtWorkspaceWithBackEnd, + playTutorials: playTutorials, + recreateWorkspace: recreateWorkspace, + getNodeById: getNodeById, + tryToConnectChildrenWithReferenceParents: tryToConnectChildrenWithReferenceParents, + createUiObjectFromNode: createUiObjectFromNode, + addUIObject: addUIObject, + addMissingChildren: addMissingChildren + } + + let mapOfReferenceChildren = new Map() + let mapOfNodes + let tasksFoundAtWorkspace + let sessionsFoundAtWorkspace + let tutorialsToPlay + + return thisObject + + function getNodeById(nodeId) { + return mapOfNodes.get(nodeId) + } + + function recreateWorkspace(node, callBackFunction) { + mapOfNodes = new Map() + tasksFoundAtWorkspace = [] + sessionsFoundAtWorkspace = [] + tutorialsToPlay = [] + + removeNullRootNodes() + + function removeNullRootNodes() { + for (let i = 0; i < node.rootNodes.length; i++) { + let rootNode = node.rootNodes[i] + if (rootNode === null) { + node.rootNodes.splice(i, 1) + removeNullRootNodes() + return + } + } + } + + addPluginNodes() + + function addPluginNodes(pluginNames) { + let blobService = newFileStorage() + let totalPlugin = 0 + let totalRead = 0 + + for (let i = 0; i < node.rootNodes.length; i++) { + let rootNode = node.rootNodes[i] + if (rootNode.type === 'Plugins') { + let plugins = rootNode + + if (plugins.pluginProjects === undefined) { continue } + for (let j = 0; j < plugins.pluginProjects.length; j++) { + let project = plugins.pluginProjects[j] + if (project.pluginDataMines !== undefined) { + totalPlugin = totalPlugin + project.pluginDataMines.pluginFiles.length + pluginAllTheseFiles(project.pluginDataMines.pluginFiles, 'Data-Mines') + } + if (project.pluginTradingMines !== undefined) { + totalPlugin = totalPlugin + project.pluginTradingMines.pluginFiles.length + pluginAllTheseFiles(project.pluginTradingMines.pluginFiles, 'Trading-Mines') + } + if (project.pluginTradingSystems !== undefined) { + totalPlugin = totalPlugin + project.pluginTradingSystems.pluginFiles.length + pluginAllTheseFiles(project.pluginTradingSystems.pluginFiles, 'Trading-Systems') + } + if (project.pluginTradingEngines !== undefined) { + totalPlugin = totalPlugin + project.pluginTradingEngines.pluginFiles.length + pluginAllTheseFiles(project.pluginTradingEngines.pluginFiles, 'Trading-Engines') + } + if (project.pluginSuperScripts !== undefined) { + totalPlugin = totalPlugin + project.pluginSuperScripts.pluginFiles.length + pluginAllTheseFiles(project.pluginSuperScripts.pluginFiles, 'Super-Scripts') + } + if (project.pluginTutorials !== undefined) { + totalPlugin = totalPlugin + project.pluginTutorials.pluginFiles.length + pluginAllTheseFiles(project.pluginTutorials.pluginFiles, 'Tutorials') + } + } + } + } + + /* + If there is no Plugins node at the workspace, we load it anyways here. + */ + if (totalPlugin === 0) { + addUserDefinedNodes() + } + + function pluginAllTheseFiles(pluginFiles, pluginType) { + for (let i = 0; i < pluginFiles.length; i++) { + let pluginFile = pluginFiles[i] + let name = pluginFile.name + callWebServer(undefined, 'LoadPlugin' + '/' + 'Superalgos' + '/' + pluginType + '/' + name + '.json', onFileReceived) + + function onFileReceived(err, text, response) { + + if (err && err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + console.log('[WARN] Cannot load plugin ' + pluginType + ' ' + name + '. The Workspace will be loaded with this plugin file missing.') + } else { + let receivedNode = JSON.parse(text) + /* + If the workspace already contains a root node with the id of the head of the hirierchy + we are loading, we remove it because the plugin file has precedende. + */ + for (let i = 0; i < node.rootNodes.length; i++) { + let rootNode = node.rootNodes[i] + if (rootNode.id === receivedNode.id) { + node.rootNodes.splice(i, 1) + break + } + } + receivedNode.isPlugin = true + + if (pluginFile.pluginFilePosition !== undefined) { + let positionOffset = { + x: pluginFile.pluginFilePosition.savedPayload.position.x - receivedNode.savedPayload.position.x, + y: pluginFile.pluginFilePosition.savedPayload.position.y - receivedNode.savedPayload.position.y + } + receivedNode.positionOffset = positionOffset + } + + node.rootNodes.unshift(receivedNode) + } + + totalRead++ + if (totalPlugin === totalRead) { + addUserDefinedNodes() + } + } + } + } + } + + function addUserDefinedNodes() { + /* Create the workspace UI OBject and then continue with the root nodes. */ + createUiObject(false, 'Workspace', node.name, node, undefined, undefined, 'Workspace') + if (node.rootNodes !== undefined) { + for (let i = 0; i < node.rootNodes.length; i++) { + let rootNode = node.rootNodes[i] + createUiObjectFromNode(rootNode, undefined, undefined) + } + } + + tryToConnectChildrenWithReferenceParents() + + if (callBackFunction !== undefined) { + callBackFunction() // The recreation of the workspace is complete + } + } + } + + function syncronizeTasksFoundAtWorkspaceWithBackEnd(functionLibraryTaskFunctions) { + for (let i = 0; i < tasksFoundAtWorkspace.length; i++) { + let node = tasksFoundAtWorkspace[i] + functionLibraryTaskFunctions.syncronizeTaskWithBackEnd(node) + } + tasksFoundAtWorkspace = undefined + } + + function syncronizeSessionsFoundAtWorkspaceWithBackEnd(functionLibrarySessionFunctions) { + for (let i = 0; i < sessionsFoundAtWorkspace.length; i++) { + let node = sessionsFoundAtWorkspace[i] + functionLibrarySessionFunctions.syncronizeSessionWithBackEnd(node) + } + sessionsFoundAtWorkspace = undefined + } + + function playTutorials() { + for (let i = 0; i < tutorialsToPlay.length; i++) { + let node = tutorialsToPlay[i] + node.payload.uiObject.menu.internalClick('Play Tutorial') + } + tutorialsToPlay = undefined + } + + function tryToConnectChildrenWithReferenceParents() { + /* We reconstruct here the reference relationships. */ + for (const [key, node] of mapOfNodes) { + if (node.id === "54854f90-d121-4365-8f40-9a26e7af1097") { + console.log('ACA') + } + if (node.payload === undefined) { continue } + if (node.payload.referenceParent !== undefined) { + if (node.payload.referenceParent.cleaned === true) { + node.payload.referenceParent = mapOfNodes.get(node.payload.referenceParent.id) + if (node.payload.referenceParent === undefined) { + //console.log('[WARN]' + node.type + ' ' + node.name + ' reference parent lost during re-binding phase.') + } + continue // We were referencing a deleted node, so we replace it potentially with a newly created one. + } else { + continue // In this case the reference is already good. + } + } + if (node.savedPayload !== undefined) { + if (node.savedPayload.referenceParent !== undefined) { // these are children recreated + node.payload.referenceParent = mapOfNodes.get(node.savedPayload.referenceParent.id) + if (node.payload.referenceParent === undefined) { + //console.log('[WARN]' + node.type + ' ' + node.name + ' reference parent lost during re-binding phase.') + } + } + } + } + } + + function migrateCodeToConfig(node, nodeDefinition) { + /* Code needed to Migrante from Beta 5 to Beta a Workspace */ + if (nodeDefinition.editors !== undefined) { + if (nodeDefinition.editors.config === true) { + if (node.code !== undefined) { + node.config = node.code + node.code = undefined + } + } + } + } + + function createUiObjectFromNode(node, parentNode, chainParent, positionOffset) { + /* Migration code from beta 6 to beta 7 */ + if (node.project === undefined) { + node.project = 'Superalgos' + } + + /* + This function can be called with a positionOffset to change the node + saved position and all of its descendants positions as well with an + offset. It can also happen that the positionOffset is defined as a property + of the node, in which case it will produce exactly the same effect. + */ + if (node.positionOffset !== undefined) { + positionOffset = node.positionOffset + } + + + /* Get node definition */ + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition !== undefined) { + migrateCodeToConfig(node, nodeDefinition) + + /* Resolve Initial Values */ + if (nodeDefinition.initialValues !== undefined) { + if (nodeDefinition.initialValues.code !== undefined) { + if (node.code === undefined) { + node.code = nodeDefinition.initialValues.code + } + } + if (nodeDefinition.initialValues.config !== undefined) { + if (node.config === undefined) { + node.config = nodeDefinition.initialValues.config + } + } + } + + /* For the cases where an node is not chained to its parent but to the one at the parent before it at its collection */ + if (nodeDefinition.chainedToSameType === true) { + if (parentNode !== undefined) { + let parentNodeDefinition = getNodeDefinition(parentNode) + if (parentNodeDefinition !== undefined) { + if (parentNodeDefinition.properties !== undefined) { + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (property.childType === node.type) { + if (property.type === 'array') { + if (parentNode[property.name] !== undefined) { + if (parentNode[property.name].length > 1) { + let nodeChildren = parentNode[property.name] + for (let j = 0; j < nodeChildren.length; j++) { + if (node.id === nodeChildren[j].id) { + if (j > 0) { + chainParent = nodeChildren[j - 1] + } + } + } + } + } + } + } + } + } + } + } + } + /* Create Self */ + createUiObject(false, node.type, node.name, node, parentNode, chainParent, node.type, positionOffset) + + /* Create Children */ + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + if (node[property.name] !== undefined) { + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + createUiObjectFromNode(node[property.name], node, node, positionOffset) + previousPropertyName = property.name + } + } + break + case 'array': { + let nodePropertyArray = node[property.name] + for (let m = 0; m < nodePropertyArray.length; m++) { + let arrayItem = nodePropertyArray[m] + createUiObjectFromNode(arrayItem, node, node, positionOffset) + } + } + break + } + } else { + if (property.type === 'array') { + node[property.name] = [] + } + } + } + } + } + } + + function addUIObject(parentNode, type, rootNodes) { + let object = { + name: 'New ' + type, + type: type, + project: parentNode.project + } + + let parentNodeDefinition = getNodeDefinition(parentNode) + if (parentNodeDefinition === undefined) { + console.log('Cannot addUIOBject from parent of ' + type + ' because that type it is not defined at the APP_SCHEMA.') + } + /* Resolve Initial Values */ + let nodeDefinition = getNodeDefinition(object) + + if (nodeDefinition === undefined) { + console.log('Cannot addUIOBject of ' + type + ' because that type it is not defined at the APP_SCHEMA.') + return + } + + if (nodeDefinition.initialValues !== undefined) { + if (nodeDefinition.initialValues.code !== undefined) { + object.code = nodeDefinition.initialValues.code + } + } + if (nodeDefinition.initialValues !== undefined) { + if (nodeDefinition.initialValues.config !== undefined) { + object.config = nodeDefinition.initialValues.config + } + } + + if (nodeDefinition.isHierarchyHead === true) { + rootNodes.push(object) + createUiObject(true, object.type, object.name, object, parentNode, undefined) + return object + } + + /* For the cases where an node is not chained to its parent but to the one at the parent before it at its collection */ + let chainParent = parentNode + if (nodeDefinition.chainedToSameType === true) { + if (parentNodeDefinition.properties !== undefined) { + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (property.childType === type) { + if (property.type === 'array') { + if (parentNode[property.name] !== undefined) { + if (parentNode[property.name].length > 0) { + let nodeChildren = parentNode[property.name] + chainParent = nodeChildren[nodeChildren.length - 1] + } + } + } + } + } + } + } + + /* Create Empty Arrays for properties of type Array */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + if (property.type === 'array') { + object[property.name] = [] + } + } + } + + if (nodeDefinition.initialValues !== undefined) { + if (nodeDefinition.initialValues.code !== undefined) { + object.code = nodeDefinition.initialValues.code + } + if (nodeDefinition.initialValues.config !== undefined) { + object.config = nodeDefinition.initialValues.config + } + } + + /* Connect to Parent */ + if (parentNodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < parentNodeDefinition.properties.length; i++) { + let property = parentNodeDefinition.properties[i] + if (property.childType === type) { + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + parentNode[property.name] = object + previousPropertyName = property.name + } + } + break + case 'array': { + if (parentNode[property.name] === undefined) { + parentNode[property.name] = [] + } + if (property.maxItems !== undefined) { + if (parentNode[property.name].length < property.maxItems) { + parentNode[property.name].push(object) + } else { + return // Object can not be created. + } + } else { + parentNode[property.name].push(object) + } + } + break + } + } + } + } + + createUiObject(true, object.type, object.name, object, parentNode, chainParent) + + /* Auto Add more Children */ + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (property.autoAdd === true) { + addUIObject(object, property.childType) + previousPropertyName = property.name + } + } + } + break + case 'array': { + if (property.autoAdd === true) { + if (object[property.name] === undefined) { + object[property.name] = [] + } + addUIObject(object, property.childType) + } + } + break + } + } + } + + return object + } + + function addMissingChildren(node) { + let nodeDefinition = getNodeDefinition(node) + + /* Connect to Parent */ + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + if (property.type === 'node') { + if (property.name !== previousPropertyName) { + if (node[property.name] === undefined) { + addUIObject(node, property.childType) + previousPropertyName = property.name + } + } + } + } + } + } + + function createUiObject(userAddingNew, uiObjectType, name, node, parentNode, chainParent, title, positionOffset) { + let payload = {} + + /* Mechanism related to keeping references when cloning */ + if (node.id === undefined) { + node.id = newUniqueId() + } else { + /* + We need to avoid nodes having the same id. If we add nodes to a workspace that already exist with the same Id, + the nodes being added will get a new id at this moment. + */ + let testNode = mapOfNodes.get(node.id) + if (testNode !== undefined && testNode.cleaned !== true) { + console.log(testNode.name + ' already have the id of the node ' + node.name + ' being added.') + node.id = newUniqueId() + } + } + + /* Default Naming */ + if (name === '' || name === undefined) { name = 'My ' + uiObjectType } + + /* If we are creating this object as the result of restoring a backup, share or clone, we will bring some of the saved properties to the new running instance. */ + if (userAddingNew === false && uiObjectType !== 'Workspace') { + /* This following info can not be missing. */ + if (node.savedPayload === undefined) { + console.log('Node ' + node.type + ' without savedPayload.', node) + return + } + if (node.savedPayload.position === undefined) { + console.log('Node ' + node.type + ' without savedPayload.position.', node) + return + } + if (node.savedPayload.targetPosition === undefined) { + console.log('Node ' + node.type + ' without savedPayload.targetPosition.', node) + return + } + + /* If there is not a position offset, which happens when we are dropping a node into the designer, we create a cero vector then. */ + if (positionOffset === undefined) { + positionOffset = { + x: 0, + y: 0 + } + } + + /* Adding the offset to the saved positions. */ + node.savedPayload.targetPosition.x = node.savedPayload.targetPosition.x + positionOffset.x + node.savedPayload.targetPosition.y = node.savedPayload.targetPosition.y + positionOffset.y + node.savedPayload.position.x = node.savedPayload.position.x + positionOffset.x + node.savedPayload.position.y = node.savedPayload.position.y + positionOffset.y + + /* Transferring the saved payload properties into the running instance being created. */ + payload.targetPosition = { + x: node.savedPayload.targetPosition.x, + y: node.savedPayload.targetPosition.y + } + node.savedPayload.targetPosition = undefined + + /* Reference children connection to parents */ + if (node.savedPayload.referenceParent !== undefined) { + payload.referenceParent = mapOfNodes.get(node.savedPayload.referenceParent.id) + + if (payload.referenceParent !== undefined) { + mapOfReferenceChildren.set(node.id, node) + } + } + } + + /* If we are adding a new object, then we set the initial values for position and targetPosition */ + if (userAddingNew === true || uiObjectType === 'Workspace') { + /* Workspace allways to the spawn position */ + if (uiObjectType === 'Workspace') { + payload.position = { + x: spawnPosition.x, + y: spawnPosition.y + } + } + + /* Chain parent pointing to the position of the chain parent if defined. */ + if (chainParent === undefined) { + payload.targetPosition = { + x: spawnPosition.x, + y: spawnPosition.y + } + } else { + payload.targetPosition = { + x: chainParent.payload.position.x, + y: chainParent.payload.position.y + } + } + } + + /* Setting the title */ + if (title !== undefined) { + payload.subTitle = title + } else { + payload.subTitle = uiObjectType + } + + payload.visible = true + payload.title = name + payload.node = node + payload.parentNode = parentNode + payload.chainParent = chainParent + payload.executeAction = UI.projects.superalgos.spaces.designSpace.workspace.executeAction + + node.payload = payload + node.type = uiObjectType + + /* + Now we copy the possible frame saved to the payload where it can be used. A saved frame stores the position of a a chartingSpace object related to a node. + This will prevent that if those charting space objects never get activated, that the frame information is lost when the workspace is saved. + */ + if (node.savedPayload !== undefined) { + if (node.savedPayload.frame !== undefined) { + let frame = { + position: { + x: 0, + y: 0 + } + } + frame.position.x = node.savedPayload.frame.position.x + frame.position.y = node.savedPayload.frame.position.y + frame.width = node.savedPayload.frame.width + frame.height = node.savedPayload.frame.height + frame.radius = node.savedPayload.frame.radius + UI.projects.superalgos.utilities.loadSaveFrame.saveFrame(payload, frame) + } + if (node.savedPayload.tutorial !== undefined) { + let tutorial = { + status: node.savedPayload.tutorial.status + } + UI.projects.superalgos.utilities.tutorial.saveTutorial(payload, tutorial) + } + } + + /* Now we mount the floating object where the UIOBject will be laying on top of */ + UI.projects.superalgos.spaces.floatingSpace.uiObjectConstructor.createUiObject(userAddingNew, payload) + + /* This is the point where we build a map with all nodes present at the workspace */ + mapOfNodes.set(node.id, node) + + /* We will collect all tasks at the workspace in order to later syncronize them with the backend */ + if (userAddingNew === false && uiObjectType === 'Task' && node.savedPayload !== undefined) { + if (tasksFoundAtWorkspace !== undefined) { // it might be undefined when you are spawning a task that was backed up + tasksFoundAtWorkspace.push(node) + } + } + + /* Check if there are sessions to run */ + if (userAddingNew === false && node.savedPayload !== undefined) { + if (uiObjectType === 'Live Trading Session' || uiObjectType === 'Forward Testing Session' || uiObjectType === 'Backtesting Session' || uiObjectType === 'Paper Trading Session') { + if (sessionsFoundAtWorkspace !== undefined) { // it might be undefined when you are spawning a session that was running while backed up + sessionsFoundAtWorkspace.push(node) + } + } + } + + /* Check if there are tutorials to play */ + if (userAddingNew === false && node.savedPayload !== undefined) { + if (uiObjectType === 'Tutorial') { + if (node.savedPayload.uiObject.isPlaying === true) { + if (tutorialsToPlay !== undefined) { // it might be undefined when you are spawning a tutorial that was playing while backed up + tutorialsToPlay.push(node) + } + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Function-Libraries/WebhookFunctions.js b/Integrations/Projects/Superalgos/Function-Libraries/WebhookFunctions.js new file mode 100644 index 000000000..daa51483c --- /dev/null +++ b/Integrations/Projects/Superalgos/Function-Libraries/WebhookFunctions.js @@ -0,0 +1,22 @@ +function newWebhookFunctions() { + thisObject = { + sendTestMessage: sendTestMessage + } + + return thisObject + + function sendTestMessage(node, callBackFunction) { + let testMessage = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(node.payload, 'testMessage') + let letURL = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(node.payload, 'webhookURL') + + callWebServer(testMessage, letURL, onResponse) + + function onResponse(err) { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) + } else { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Globals/Docs.js b/Integrations/Projects/Superalgos/Globals/Docs.js new file mode 100644 index 000000000..43c7ed371 --- /dev/null +++ b/Integrations/Projects/Superalgos/Globals/Docs.js @@ -0,0 +1,7 @@ +function newSuperalgosGlobalsDocs() { + thisObject = { + DOCUMENTATION_URL_PREFIX: 'https://docs.superalgos.org/', + DOCUMENTATION_URL_DEFAULT: 'https://docs.superalgos.org/suite-about-this-documentation.html' + } + return thisObject +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Globals/Zoom.js b/Integrations/Projects/Superalgos/Globals/Zoom.js new file mode 100644 index 000000000..040b4952f --- /dev/null +++ b/Integrations/Projects/Superalgos/Globals/Zoom.js @@ -0,0 +1,15 @@ +function newSuperalgosGlobalsZoom() { + thisObject = { + DOUBLE_CLICK_ZOOM_OUT_LEVEL: 0, + DOUBLE_CLICK_ZOOM_IN_LEVEL: 6, + DOUBLE_CLICK_ZOOM_IN_IN_LEVEL: 7, + ZOOM_OUT_THRESHOLD_FOR_NOT_HIDDING_PANELS: 12, + ZOOM_OUT_THRESHOLD_FOR_HIDDING_PANELS: 7, + ZOOM_OUT_THRESHOLD_FOR_CHANGING_TIME_FRAME: 3, + ZOOM_OUT_THRESHOLD_FOR_PACKING_OBJECTS_AT_THE_BOTTOM_OR_TOP_OF_VIEWPORT: 2, + ZOOM_OUT_THRESHOLD_FOR_PLOTTING_IN_LOW_RESOLUTION: 3, + ZOOM_OUT_THRESHOLD_FOR_DISPLAYING_TIME_MACHINES_ICONIZED: 5, + ZOOM_OUT_THRESHOLD_FOR_DISPLAYING_SCALES: 3 + } + return thisObject +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/ChartUtilities.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/ChartUtilities.js new file mode 100644 index 000000000..ca778df72 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/ChartUtilities.js @@ -0,0 +1,139 @@ + +function getSideDays(timeFrame) { + let daysOnSides + + if (timeFrame < _1_HOUR_IN_MILISECONDS) { + daysOnSides = 1 + } + + if (timeFrame === _1_HOUR_IN_MILISECONDS) { + daysOnSides = 9 + } + + if (timeFrame === _2_HOURS_IN_MILISECONDS) { + daysOnSides = 10 + } + + if (timeFrame === _3_HOURS_IN_MILISECONDS) { + daysOnSides = 12 + } + + if (timeFrame === _4_HOURS_IN_MILISECONDS) { + daysOnSides = 20 + } + + if (timeFrame === _6_HOURS_IN_MILISECONDS) { + daysOnSides = 35 + } + + if (timeFrame === _8_HOURS_IN_MILISECONDS) { + daysOnSides = 44 + } + + if (timeFrame === _12_HOURS_IN_MILISECONDS) { + daysOnSides = 56 + } + + if (timeFrame === ONE_DAY_IN_MILISECONDS) { + daysOnSides = 154 + } + + return daysOnSides +} + +function getCondenseFactor(timeFrame) { + switch (timeFrame) { + case _1_MINUTE_IN_MILISECONDS: return 1 + case _5_MINUTES_IN_MILISECONDS: return 5 + case _10_MINUTES_IN_MILISECONDS: return 10 + case _30_MINUTES_IN_MILISECONDS: return 50 + case _1_HOUR_IN_MILISECONDS: return 100 + } +} + +function getTransparenceFactor(timeFrame) { + switch (timeFrame) { + case _1_MINUTE_IN_MILISECONDS: return '0.12' + case _5_MINUTES_IN_MILISECONDS: return '0.10' + case _10_MINUTES_IN_MILISECONDS: return '0.08' + case _30_MINUTES_IN_MILISECONDS: return '0.06' + case _1_HOUR_IN_MILISECONDS: return '0.04' + } +} + +function recalculatePeriod(zoomLevel) { + // console.log('NEW ZOOM LEVEL', zoomLevel) + if (zoomLevel > 400) { + return _1_MINUTE_IN_MILISECONDS + } + + if (zoomLevel > 360) { + return _2_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 330) { + return _3_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 300) { + return _4_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 270) { + return _5_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 250) { + return _10_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 230) { + return _15_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 210) { + return _20_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 190) { + return _30_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 170) { + return _40_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 150) { + return _45_MINUTES_IN_MILISECONDS + } + + if (zoomLevel > 130) { + return _1_HOUR_IN_MILISECONDS + } + + if (zoomLevel > 110) { + return _2_HOURS_IN_MILISECONDS + } + + if (zoomLevel > 80) { + return _3_HOURS_IN_MILISECONDS + } + + if (zoomLevel > 50) { + return _4_HOURS_IN_MILISECONDS + } + + if (zoomLevel > 35) { + return _6_HOURS_IN_MILISECONDS + } + + if (zoomLevel > 20) { + return _8_HOURS_IN_MILISECONDS + } + + if (zoomLevel > 10) { + return _12_HOURS_IN_MILISECONDS + } + + return ONE_DAY_IN_MILISECONDS +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/ChartingSpace.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/ChartingSpace.js new file mode 100644 index 000000000..5beccc07d --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/ChartingSpace.js @@ -0,0 +1,462 @@ +/* + +The chart space is where all the charts live. What this space contains is a type of object we call Time Machine. A Time Machine is a cointainer of +charts objects. The idea is simple, all the chart objects are ploted according to the time of the Time Machine object. When the time of the Time Machine +changes, then all charts in it are replotted with the corresponging data. + +*/ + +function newSuperalgosChartingSpace() { + const MODULE_NAME = 'Charting Space' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + visible: false, + container: undefined, + inViewport: undefined, + timeMachines: undefined, + viewport: undefined, + payload: undefined, + findTimeMachine: findTimeMachine, + onKeyPressed: onKeyPressed, + reset: reset, + oneScreenUp: oneScreenUp, + oneScreenDown: oneScreenDown, + oneScreenLeft: oneScreenLeft, + oneScreenRight: oneScreenRight, + fitFunction: fitFunction, + isThisPointVisible: isThisPointVisible, + physics: physics, + draw: draw, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + let canvasBrowserResizedEventSubscriptionId + let timeMachinesMap + let syncWithDesignerLoop + + const PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT = 25 + + initialSetup() + + let isInitialized = false + + return thisObject + + function reset() { + finalize() + initialSetup() + isInitialized = false + initialize() + } + + function initialSetup() { + thisObject.timeMachines = [] + thisObject.visible = true + timeMachinesMap = new Map() + syncWithDesignerLoop = 0 + thisObject.inViewport = new Map() + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = false + } + + function finalize() { + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[i] + timeMachine.finalize() + } + + thisObject.inViewport = undefined + thisObject.timeMachines = undefined + timeMachinesMap = undefined + + thisObject.container.eventHandler.stopListening(canvasBrowserResizedEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + + if (thisObject.viewport !== undefined) { + thisObject.viewport.finalize() + thisObject.viewport = undefined + } + + thisObject.payload = undefined + } + + function initialize() { + if (isInitialized === true) { return } + if (UI.projects.superalgos.spaces.designSpace === undefined) { return } + if (UI.projects.superalgos.spaces.designSpace.workspace === undefined) { return } + + let rootNodes = UI.projects.superalgos.spaces.designSpace.workspace.workspaceNode.rootNodes + for (let i = 0; i < rootNodes.length; i++) { + let rootNode = rootNodes[i] + if (rootNode !== null) { + if (rootNode.type === 'Charting Space') { + thisObject.payload = rootNode.payload + } + } + } + + if (thisObject.payload === undefined) { + // if (ERROR_LOG === true) { logger.write('[WARN] initialize -> There must exist a Charting Space at the Design Space in order to enable Charts. PLease create one and refreash your browser. ') } + return + } + + thisObject.viewport = newViewport() + if (thisObject.payload.node.viewport === undefined) { + if (ERROR_LOG === true) { logger.write('[WARN] initialize -> There must exist a Viewport node attached to the Charting Space node in order for the Viewport save its properties. ') } + return + } else { + thisObject.viewport.payload = thisObject.payload.node.viewport.payload + } + + thisObject.viewport.initialize() + + canvasBrowserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize) + + isInitialized = true + } + + function getContainer(point, purpose) { + if (thisObject.visible !== true) { return } + if (thisObject.viewport === undefined) { return } + if (thisObject.viewport.isThisPointInViewport(point) === false) { return } + + let container + + /* This first step is not actually to get the container but to allow things to be turned off in case there is a switch of focus from one time machine to another */ + for (let i = 0; i < thisObject.timeMachines.length; i++) { + container = thisObject.timeMachines[i].container + if (purpose === GET_CONTAINER_PURPOSE.MOUSE_OVER) { + container.eventHandler.raiseEvent('onMouseNotOver') + } + } + + /* + Now we see which is the inner most container that has it. + In this case we will ask all the time machines even if one of them already returned a matching container. + This will help the others know they are not on ofcus + */ + let containerFound + for (let i = 0; i < thisObject.timeMachines.length; i++) { + container = thisObject.timeMachines[i].getContainer(point, purpose) + if (container !== undefined) { + if (purpose !== undefined) { + if (containerFound === undefined) { + containerFound = container + } + } else { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + if (containerFound === undefined) { + containerFound = container + } + } + } + } + } + if (containerFound !== undefined) { + containerFound.space = 'Charting Space' + if (purpose === GET_CONTAINER_PURPOSE.MOUSE_WHEEL && containerFound.isWheelable !== true) { + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + return + } + return containerFound + } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + thisObject.container.space = 'Charting Space' + if (purpose === GET_CONTAINER_PURPOSE.MOUSE_WHEEL && thisObject.container.isWheelable !== true) { + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + return + } + return thisObject.container + } else { + return undefined + } + } + + function resize() { + thisObject.viewport.resize() + thisObject.container.frame.width = browserCanvas.width + thisObject.container.frame.height = COCKPIT_SPACE_POSITION + } + + function oneScreenUp() { + if (thisObject.visible === false) { return } + let displaceVector = { + x: 0, + y: browserCanvas.height * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100 + } + + UI.projects.superalgos.spaces.chartingSpace.viewport.displace(displaceVector) + } + + function oneScreenDown() { + if (thisObject.visible === false) { return } + let displaceVector = { + x: 0, + y: -browserCanvas.height * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100 + } + + UI.projects.superalgos.spaces.chartingSpace.viewport.displace(displaceVector) + } + + function oneScreenLeft() { + if (thisObject.visible === false) { return } + let displaceVector = { + x: browserCanvas.width * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100, + y: 0 + } + + UI.projects.superalgos.spaces.chartingSpace.viewport.displace(displaceVector, true) + } + + function oneScreenRight() { + if (thisObject.visible === false) { return } + let displaceVector = { + x: -browserCanvas.width * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100, + y: 0 + } + + UI.projects.superalgos.spaces.chartingSpace.viewport.displace(displaceVector, true) + } + + function fitFunction(point, fullVisible) { + /* Here we check the boundaries of the resulting points, so they dont go out of the visible area. */ + + let returnPoint = { + x: point.x, + y: point.y + } + + if (point.x > browserCanvas.width) { + returnPoint.x = browserCanvas.width + } + + if (point.x < 0) { + returnPoint.x = 0 + } + + if (fullVisible === true) { + if (point.y > COCKPIT_SPACE_POSITION) { + returnPoint.y = COCKPIT_SPACE_POSITION + } + } else { + if (point.y > COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT / 2) { + returnPoint.y = COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT / 2 + } + } + + if (point.y < TOP_SPACE_HEIGHT) { + returnPoint.y = TOP_SPACE_HEIGHT + } + + return returnPoint + } + + function isThisPointVisible(point) { + if (point.x > browserCanvas.width) { + return false + } + + if (point.x < 0) { + return false + } + + if (point.y > COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT / 2) { + return false + } + + if (point.y < 0) { + return false + } + + return true + } + + function physics() { + initialize() + thisObjectPhysics() + if (thisObject.visible !== true) { return } + if (thisObject.viewport !== undefined) { + thisObject.viewport.physics() + } + childrenPhysics() + syncWithDesigner() + } + + function onKeyPressed(event) { + if (thisObject.visible !== true) { return } + for (let j = 0; j < thisObject.timeMachines.length; j++) { + let timeMachine = thisObject.timeMachines[j] + if (timeMachine.onFocus === true) { + timeMachine.onKeyPressed(event) + } + } + } + + function findTimeMachine(node) { + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[i] + if (timeMachine.payload.node.id === node.id) { + return timeMachine + } + } + } + + function syncWithDesigner() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.node === undefined) { return } + syncWithDesignerLoop = syncWithDesignerLoop + 0.00000000001 + + if (thisObject.payload.node.dashboards !== undefined) { + for (let i = 0; i < thisObject.payload.node.dashboards.length; i++) { + let dashboard = thisObject.payload.node.dashboards[i] + if (dashboard.timeMachines !== undefined) { + for (let j = 0; j < dashboard.timeMachines.length; j++) { + let node = dashboard.timeMachines[j] + let timeMachine = timeMachinesMap.get(node.id) + if (timeMachine === undefined) { + /* The time machine node is new, thus we need to initialize a new timeMachine */ + initializeTimeMachine(node, syncWithDesignerLoop) + } else { + /* The time machine already exists, we tag it as existing at the current loop. */ + timeMachine.syncWithDesignerLoop = syncWithDesignerLoop + } + } + } + } + } + + /* We check all the timeMachines we have to see if we need to remove any of them */ + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[i] + if (timeMachine.syncWithDesignerLoop < syncWithDesignerLoop) { + /* Must be removed */ + timeMachine.finalize() + timeMachinesMap.delete(timeMachine.nodeId) + thisObject.timeMachines.splice(i, 1) + /* We remove one at the time */ + return + } + } + + function initializeTimeMachine(node, syncWithDesignerLoop) { + if (node.payload === undefined) { return } // not ready to consider this Time Machine + if (node.payload.uiObject === undefined) { return } + + let timeMachine = newTimeMachine() + timeMachine.syncWithDesignerLoop = syncWithDesignerLoop + timeMachine.payload = node.payload + timeMachine.nodeId = node.id + + timeMachinesMap.set(node.id, timeMachine) + timeMachine.payload.uiObject.setValue('Loading...') + + /* Setting up the new time machine. */ + timeMachine.initialize(onTimeMachineInitialized) + + function onTimeMachineInitialized() { + thisObject.timeMachines.push(timeMachine) + timeMachine.payload.uiObject.setValue('') + if (UI.projects.superalgos.spaces.chartingSpace.viewport !== undefined) { + UI.projects.superalgos.spaces.chartingSpace.viewport.raiseEvents() // These events will impacts on objects just initialized. + } + } + } + } + + function thisObjectPhysics() { + thisObject.container.frame.height = COCKPIT_SPACE_POSITION + + if (thisObject.container.frame.height <= TOP_SPACE_HEIGHT) { + thisObject.visible = false + if (thisObject.viewport !== undefined) { + thisObject.viewport.visible = false + } + } else { + thisObject.visible = true + if (thisObject.viewport !== undefined) { + thisObject.viewport.visible = true + } + } + + if (UI.projects.superalgos.spaces.chartingSpace.viewport !== undefined) { + UI.projects.superalgos.spaces.chartingSpace.viewport.resize() + } + } + + function childrenPhysics() { + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[i] + if (timeMachine.payload.node === undefined) { + continue + } + + if (timeMachine.container.frame.isInViewPort()) { + thisObject.inViewport.set(timeMachine.payload.node.id, timeMachine) + } else { + thisObject.inViewport.delete(timeMachine.payload.node.id) + } + timeMachine.physics() + } + } + + function drawBackground() { + if (thisObject.visible === false) { return } + drawSpaceBackground() + + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[thisObject.timeMachines.length - i - 1] + timeMachine.drawBackground() + } + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + if (thisObject.visible === false) { return } + drawBackground() + + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[thisObject.timeMachines.length - i - 1] + timeMachine.draw() + } + + drawForeground() + } + + function drawForeground() { + for (let i = 0; i < thisObject.timeMachines.length; i++) { + let timeMachine = thisObject.timeMachines[thisObject.timeMachines.length - i - 1] + timeMachine.drawForeground() + } + } + + function drawSpaceBackground() { + let opacity = '1' + let fromPoint = { + x: 0, + y: 0 + } + + let toPoint = { + x: browserCanvas.width, + y: COCKPIT_SPACE_POSITION + } + + browserCanvasContext.beginPath() + browserCanvasContext.rect(fromPoint.x, fromPoint.y, toPoint.x - fromPoint.x, toPoint.y - fromPoint.y) + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + opacity + ')' + browserCanvasContext.closePath() + browserCanvasContext.fill() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/CoordinateSystem.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/CoordinateSystem.js new file mode 100644 index 000000000..1e5271bd4 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/CoordinateSystem.js @@ -0,0 +1,375 @@ +/* + +This Object object is responsible for defining the coordinate system in a TimeLine. + +The scale on the X axis depends on the time. +The scale on the Y axis depends on the data that wants to be plotted. Usually we need the Max amount on that data to generate a proper scale. + +*/ + +function newCoordinateSystem() { + let thisObject = { + min: undefined, + max: undefined, + parentMin: undefined, + parentMax: undefined, + childrenMin: undefined, + childrenMax: undefined, + maxHeight: undefined, + maxWidth: undefined, + scale: undefined, + eventHandler: undefined, + autoMinXScale: true, + autoMaxXScale: true, + autoMinYScale: true, + autoMaxYScale: true, + HORIZONTAL_MARGIN_FACTOR: 1, + VERTICAL_MARGIN_FACTOR: 1, + HORIZONTAL_MARGIN_INVERSE_FACTOR: 0, + VERTICAL_MARGIN_INVERSE_FACTOR: 0, + physics: physics, + reportParentXValue: reportParentXValue, + reportParentYValue: reportParentYValue, + reportXValue: reportXValue, + reportYValue: reportYValue, + zoomX: zoomX, + zoomY: zoomY, + recalculateScale: recalculateScale, + transformThisPoint: transformThisPoint, + transformThisPoint2: transformThisPoint2, + unInverseTransform: unInverseTransform, + inverseTransformUncappedY: inverseTransformUncappedY, + initializeX: initializeX, + initializeY: initializeY, + initialize: initialize, + finalize: finalize + } + + thisObject.min = { + x: 0, + y: 0 + } + thisObject.max = { + x: 0, + y: 0 + } + thisObject.parentMin = { + x: 0, + y: 0 + } + thisObject.parentMax = { + x: 0, + y: 0 + } + thisObject.childrenMin = { + x: 0, + y: 0 + } + thisObject.childrenMax = { + x: 0, + y: 0 + } + + thisObject.scale = { + x: 0, + y: 0 + } + + thisObject.eventHandler = newEventHandler() + thisObject.eventHandler.name = 'Coordinate System' + + let newXMax = -VERY_LARGE_NUMBER + let newXMin = VERY_LARGE_NUMBER + + let newYMax = -VERY_LARGE_NUMBER + let newYMin = VERY_LARGE_NUMBER + + let parentMinChanged = false + let parentMaxChanged = false + + return thisObject + + function finalize() { + thisObject.eventHandler.finalize() + thisObject.eventHandler = undefined + } + + function initialize(minValue, maxValue, maxWidth, maxHeight) { + /* Defines the min and max value of rate that we are going to transport to the available screen at the center position. */ + thisObject.min.x = minValue.x + thisObject.max.x = maxValue.x + + thisObject.min.y = minValue.y + thisObject.max.y = maxValue.y + + thisObject.maxWidth = maxWidth + thisObject.maxHeight = maxHeight + + recalculateScale() + } + + function reportXValue(value) { + if (thisObject.autoMinXScale === true) { + if (value < newXMin && value !== 0) { + newXMin = value + } + } + if (thisObject.autoMaxXScale === true) { + if (value > newXMax && value !== 0) { + newXMax = value + } + } + } + + function reportYValue(value) { + /* We will try to create a margin on the y axis so that in auto mode the charts dont get into the scales */ + const TOP_MARGIN_FACTOR = 1.01 + const BOTTOM_MARGIN_FACTOR = 0.99 + + if (thisObject.autoMinYScale === true) { + if (value * BOTTOM_MARGIN_FACTOR < newYMin) { + newYMin = value * BOTTOM_MARGIN_FACTOR + } + } + if (thisObject.autoMaxYScale === true) { + if (value * TOP_MARGIN_FACTOR> newYMax) { + newYMax = value * TOP_MARGIN_FACTOR + } + } + } + + function reportParentXValue(min, max) { + if (thisObject.autoMinXScale === true) { + if (thisObject.parentMin.x !== min) { + thisObject.parentMin.x = min + } + } + if (thisObject.autoMaxXScale === true) { + if (thisObject.parentMax.x !== max) { + thisObject.parentMax.x = max + } + } + } + + function reportParentYValue(min, max) { + if (thisObject.autoMinYScale === true) { + if (thisObject.parentMin.y !== min) { + thisObject.parentMin.y = min + } + } + if (thisObject.autoMaxYScale === true) { + if (thisObject.parentMax.y !== max) { + thisObject.parentMax.y = max + } + } + } + + function physics() { + let mustRecalculate = false + + if ((thisObject.autoMinXScale === true || thisObject.autoMaxXScale === true)) { + if (thisObject.autoMaxXScale === true && thisObject.childrenMax.x !== newXMax && newXMax !== -VERY_LARGE_NUMBER) { + thisObject.childrenMax.x = newXMax + } + + if (thisObject.autoMaxXScale === true) { + newXMax = -VERY_LARGE_NUMBER + } + + if (thisObject.autoMinXScale === true && thisObject.childrenMin.x !== newXMin && newXMin !== VERY_LARGE_NUMBER) { + thisObject.childrenMin.x = newXMin + newXMin = VERY_LARGE_NUMBER + } + + if (thisObject.autoMinXScale === true) { + newXMin = VERY_LARGE_NUMBER + } + } + + if ((thisObject.autoMinYScale === true || thisObject.autoMaxYScale === true)) { + if (thisObject.autoMaxYScale === true && thisObject.childrenMax.y !== newYMax && newYMax !== -VERY_LARGE_NUMBER) { + thisObject.childrenMax.y = newYMax + } + + if (thisObject.autoMaxYScale === true) { + newYMax = -VERY_LARGE_NUMBER + } + + if (thisObject.autoMinYScale === true && thisObject.childrenMin.y !== newYMin && newYMin !== VERY_LARGE_NUMBER) { + thisObject.childrenMin.y = newYMin + newYMin = VERY_LARGE_NUMBER + } + + if (thisObject.autoMinYScale === true) { + newYMin = VERY_LARGE_NUMBER + } + } + + /* Here we will see if any of the children or parent values modify the current status quo */ + /* Min */ + let minX = Math.min(thisObject.childrenMin.x, thisObject.parentMin.x) + let minY = Math.min(thisObject.childrenMin.y, thisObject.parentMin.y) + let maxX = Math.max(thisObject.childrenMax.x, thisObject.parentMax.x) + let maxY = Math.max(thisObject.childrenMax.y, thisObject.parentMax.y) + + if (thisObject.parentMin.x === 0) { + minX = thisObject.childrenMin.x + } + + if (thisObject.parentMin.y === 0) { + minY = thisObject.childrenMin.y + } + + if (thisObject.parentMax.x === 0) { + maxX = thisObject.childrenMax.x + } + + if (thisObject.parentMax.y === 0) { + maxY = thisObject.childrenMax.y + } + + if (minX !== 0 && thisObject.min.x !== minX && thisObject.autoMinXScale === true) { + thisObject.min.x = minX + mustRecalculate = true + } + + if (minY !== 0 && thisObject.min.y !== minY && thisObject.autoMinYScale === true) { + thisObject.min.y = minY + mustRecalculate = true + } + + if (maxX !== 0 && thisObject.max.x !== maxX && thisObject.autoMaxXScale === true) { + thisObject.max.x = maxX + mustRecalculate = true + } + + if (maxY !== 0 && thisObject.max.y !== maxY && thisObject.autoMaxYScale === true) { + thisObject.max.y = maxY + mustRecalculate = true + } + + if (mustRecalculate === true) { + recalculateScale() + } + } + + function recalculateScale(event) { + thisObject.scale.x = thisObject.maxWidth * thisObject.HORIZONTAL_MARGIN_FACTOR / (thisObject.max.x - thisObject.min.x) + thisObject.scale.y = thisObject.maxHeight * thisObject.VERTICAL_MARGIN_FACTOR / (thisObject.max.y - thisObject.min.y) + + thisObject.eventHandler.raiseEvent('Scale Changed', event) + } + + function zoomX(factor, mousePosition, container) { + let mouseAtCointainer = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(mousePosition, container) + let leftDistance = mouseAtCointainer.x + let rightDistance = container.frame.width - mouseAtCointainer.x + let diff = thisObject.max.x - thisObject.min.x + let min = thisObject.min.x + diff * factor * leftDistance / (container.frame.width / 2) + let max = thisObject.max.x - diff * factor * rightDistance / (container.frame.width / 2) + + if (min < max) { + let mustRecalculate = false + if (thisObject.autoMinXScale === false) { + thisObject.min.x = min + mustRecalculate = true + } + if (thisObject.autoMaxXScale === false) { + thisObject.max.x = max + mustRecalculate = true + } + if (mustRecalculate === true) { + thisObject.recalculateScale() + } + } + } + + function zoomY(factor, mousePosition, container) { + let mouseAtCointainer = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(mousePosition, container) + let topDistance = mouseAtCointainer.y + let bottomDistance = container.frame.height - mouseAtCointainer.y + let diff = thisObject.max.y - thisObject.min.y + let min = thisObject.min.y + diff * factor * bottomDistance / (container.frame.height / 2) + let max = thisObject.max.y - diff * factor * topDistance / (container.frame.height / 2) + + if (min < max) { + let mustRecalculate = false + if (thisObject.autoMinYScale === false) { + thisObject.min.y = min + mustRecalculate = true + } + if (thisObject.autoMaxYScale === false) { + thisObject.max.y = max + mustRecalculate = true + } + if (mustRecalculate === true) { + thisObject.recalculateScale() + } + } + } + + function initializeX(minValue, maxValue, maxWidth) { + thisObject.min.x = minValue.x // * 0.999; // 0.1% less + thisObject.max.x = maxValue.x // * 1.001; // 0.1% more + + thisObject.scale.x = thisObject.maxWidth / (thisObject.max.x - thisObject.min.x) + } + + function initializeY(minValue, maxValue, pMaxHeight) { + thisObject.min.y = minValue.y // * 0.999; // 0.1% less + thisObject.max.y = maxValue.y // * 1.001; // 0.1% more + + thisObject.scale.y = pMaxHeight / (thisObject.max.y - thisObject.min.y) + + thisObject.maxHeight = pMaxHeight + } + + function transformThisPoint(point) { + /* + + This is the straigh fordward transformation this object provides. The input is a point on the data set that wants to be plotted in a timeline. + The x value of the point must be a datetime.valueOf() and is going to be transformed into an x value on the coordinate system of the timeline. + The y value should be a value in the dataset at that moment in time. Depending on how this coordinate system was initialized then are the + possible values y can have. For sure it must be in the range between the min and max y declared at initialization. The y value has a special + treatment since the browser canvas object used, has a zero y value at the top, while the timeline we use has a zero y value at the botton, + for that reason y is flipped. + + Besides this, what we do is to multiply by the scale, which in turn is calculated at initialization time depending on the values provided. + + */ + + point = { + x: (point.x - thisObject.min.x) * thisObject.scale.x + thisObject.maxWidth * thisObject.HORIZONTAL_MARGIN_INVERSE_FACTOR / 2, + y: thisObject.maxHeight - (point.y - thisObject.min.y) * thisObject.scale.y - thisObject.maxHeight * thisObject.VERTICAL_MARGIN_INVERSE_FACTOR / 2 + } + + return point + } + + function transformThisPoint2(point) { + point = { + x: (point.x - thisObject.min.x) * thisObject.scale.x + thisObject.maxWidth * thisObject.HORIZONTAL_MARGIN_INVERSE_FACTOR / 2, + y: (thisObject.maxHeight - point.y - thisObject.min.y) * thisObject.scale.y - thisObject.maxHeight * thisObject.VERTICAL_MARGIN_INVERSE_FACTOR / 2 + } + + return point + } + + function unInverseTransform(point, inverseY) { + point = { + x: ((point.x - thisObject.maxWidth * thisObject.HORIZONTAL_MARGIN_INVERSE_FACTOR / 2) / thisObject.scale.x) + thisObject.min.x, + y: (inverseY * thisObject.VERTICAL_MARGIN_FACTOR - point.y + thisObject.maxHeight * thisObject.VERTICAL_MARGIN_INVERSE_FACTOR / 2) / thisObject.scale.y + thisObject.min.y + } + + return point + } + + function inverseTransformUncappedY(point, inverseY) { + point = { + x: (point.x - thisObject.min.x) * thisObject.scale.x + thisObject.maxWidth * thisObject.HORIZONTAL_MARGIN_INVERSE_FACTOR / 2, + y: (inverseY * thisObject.VERTICAL_MARGIN_FACTOR - point.y) * thisObject.scale.y - thisObject.maxHeight * thisObject.VERTICAL_MARGIN_INVERSE_FACTOR / 2 + } + + return point + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/EdgeEditor.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/EdgeEditor.js new file mode 100644 index 000000000..4324f5933 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/EdgeEditor.js @@ -0,0 +1,729 @@ +function newEdgeEditor() { + const MODULE_NAME = 'Edge Editor' + + let thisObject = { + container: undefined, + fitFunction: undefined, + isVisible: true, + isMouseOver: undefined, + resetAspectRatio: resetAspectRatio, + onKeyPressed: onKeyPressed, + physics: physics, + drawForeground: drawForeground, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + const EDGE_SIZE = 6 + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = true + thisObject.container.isClickeable = false + thisObject.container.isWheelable = false + thisObject.container.detectMouseOver = true + + let whatHappened + let whereIsMouseOver = 'outside' + + let coordinateSystem + let coordinateSystemWhenDragStarted + + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + let onDragStartedEventSubscriptionId + + let mouse = { + position: { + x: 0, + y: 0 + } + } + + mouseWhenDragStarted = { + position: { + x: 0, + y: 0 + } + } + + parentFrameWhenDragStarted = { + position: { + x: 0, + y: 0 + }, + width: 0, + height: 0 + } + + let doubleClickCounter = 0 + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onDragStartedEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + mouse = undefined + mouseWhenDragStarted = undefined + parentFrameWhenDragStarted = undefined + + coordinateSystem = undefined + + if (coordinateSystemWhenDragStarted !== undefined) { + coordinateSystemWhenDragStarted.finalize() + } + + coordinateSystemWhenDragStarted = undefined + } + + function initialize(pCoordinateSystem) { + coordinateSystem = pCoordinateSystem + + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + onDragStartedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onDragStarted', onDragStarted) + } + + function onMouseOver(event) { + thisObject.isMouseOver = true + mouse = { + position: { + x: event.x, + y: event.y + } + } + } + + function onMouseNotOver() { + thisObject.isMouseOver = false + } + + function resetAspectRatio() { + /* Resize the Time Machine to match the current screen aspect ration */ + + thisObject.container.parentContainer.frame.width = browserCanvas.width / TIME_MACHINE_WIDTH + thisObject.container.parentContainer.frame.height = (COCKPIT_SPACE_POSITION - TOP_SPACE_HEIGHT) / TIME_MACHINE_HEIGHT + coordinateSystem.maxHeight = thisObject.container.parentContainer.frame.height + coordinateSystem.maxWidth = thisObject.container.parentContainer.frame.width + coordinateSystem.recalculateScale() + thisObject.container.parentContainer.eventHandler.raiseEvent('Dimmensions Changed', event) + } + + function onDoubleClick(event) { + doubleClickCounter = 0 + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomLevel === UI.projects.superalgos.globals.zoom.DOUBLE_CLICK_ZOOM_OUT_LEVEL) { + UI.projects.superalgos.spaces.chartingSpace.viewport.displaceToContainer(thisObject.container.parentContainer) + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomAtCenter(UI.projects.superalgos.globals.zoom.DOUBLE_CLICK_ZOOM_IN_LEVEL) + return + } + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomLevel === UI.projects.superalgos.globals.zoom.DOUBLE_CLICK_ZOOM_IN_LEVEL) { + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomAtCenter(UI.projects.superalgos.globals.zoom.DOUBLE_CLICK_ZOOM_IN_IN_LEVEL) + return + } + } + + function onDragStarted(event) { + if (event.buttons === 1) { + if (doubleClickCounter > 0) { + onDoubleClick(event) + return + } else { + doubleClickCounter = 50 + } + } + + mouseWhenDragStarted = { + position: { + x: event.x, + y: event.y + } + } + + parentFrameWhenDragStarted = { + position: { + x: thisObject.container.parentContainer.frame.position.x, + y: thisObject.container.parentContainer.frame.position.y + }, + width: thisObject.container.parentContainer.frame.width, + height: thisObject.container.parentContainer.frame.height + } + + coordinateSystemWhenDragStarted = newCoordinateSystem() + + let minValue = { + x: coordinateSystem.min.x, + y: coordinateSystem.min.y + } + + let maxValue = { + x: coordinateSystem.max.x, + y: coordinateSystem.max.y + } + + coordinateSystemWhenDragStarted.initialize(minValue, maxValue, coordinateSystem.maxWidth, coordinateSystem.maxHeight) + + switch (event.buttons) { + case 1: { + whatHappened = 'left mouse button' + if (event.shiftKey === true) { + whatHappened = 'left mouse button + SHIFT' + } + break + } + case 2: { + whatHappened = 'right mouse button' // this will never happen as of today + break + } + } + } + + function onKeyPressed(event, forceExecution) { + if (forceExecution !== true) { + if (whereIsMouseOver !== 'center' ) {return} + } + + const STEP = 10 + + if (event.shiftKey === false && event.ctrlKey === false && event.code === 'ArrowLeft') { + onDragStarted(event) + thisObject.container.frame.position.x = thisObject.container.frame.position.x + STEP + whatHappened = 'left or right arrow key pressed' + return + } + + if (event.shiftKey === false && event.ctrlKey === false && event.code === 'ArrowRight') { + if (ARE_WE_RECORDING_A_MARKET_PANORAMA === true) { + if (PANORAMA_WAS_PANNED === false) { + PANORAMA_WAS_PANNED = true + onDragStarted(event) + thisObject.container.frame.position.x = thisObject.container.frame.position.x - STEP * 2 + whatHappened = 'left or right arrow key pressed' + } + } else { + onDragStarted(event) + thisObject.container.frame.position.x = thisObject.container.frame.position.x - STEP + whatHappened = 'left or right arrow key pressed' + } + return + } + + if (event.shiftKey === false && event.ctrlKey === false && event.code === 'ArrowUp') { + onDragStarted(event) + thisObject.container.frame.position.y = thisObject.container.frame.position.y + STEP + whatHappened = 'up or down arrow key pressed' + return + } + + if (event.shiftKey === false && event.ctrlKey === false && event.code === 'ArrowDown') { + onDragStarted(event) + thisObject.container.frame.position.y = thisObject.container.frame.position.y - STEP + whatHappened = 'up or down arrow key pressed' + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && (event.key === 'A' || event.key === 'a')) { + resetAspectRatio() + return + } + } + + function getContainer(event, purpose) { + if (thisObject.container.frame.isThisPointHere(event, undefined, undefined, -EDGE_SIZE) === true && event.buttons !== 2) { + let point = { + x: event.x, + y: event.y + } + point = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(point, thisObject.container) + + if (point.x <= 0) { + whereIsMouseOver = 'left' + return thisObject.container + } + + if (point.x >= thisObject.container.frame.width) { + whereIsMouseOver = 'right' + return thisObject.container + } + + if (point.y <= 0) { + whereIsMouseOver = 'top' + return thisObject.container + } + + if (point.y >= thisObject.container.frame.height) { + whereIsMouseOver = 'bottom' + return thisObject.container + } + + whereIsMouseOver = 'center' + switch (purpose) { + case GET_CONTAINER_PURPOSE.MOUSE_OVER: + return // We will let the Time Machine to handle this situation. + break + case GET_CONTAINER_PURPOSE.DRAGGING: + return thisObject.container + break + } + } + } + + function physics() { + draggingPhysics() + doubleClickPhysics() + } + + function doubleClickPhysics() { + doubleClickCounter-- + if (doubleClickCounter < 0) { + doubleClickCounter = 0 + } + } + + function draggingPhysics() { + if (coordinateSystemWhenDragStarted === undefined) { return } + if (thisObject.container.frame.position.x === 0 && thisObject.container.frame.position.y === 0) { return } + + let mouseNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(mouse.position) + let mouseWhenDragStartedNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(mouseWhenDragStarted.position) + + let dragVectorWhenDragStarted = { + x: mouseNoZoom.x - mouseWhenDragStartedNoZoom.x, + y: mouseNoZoom.y - mouseWhenDragStartedNoZoom.y + } + + let dragVector = { + x: thisObject.container.frame.position.x, + y: thisObject.container.frame.position.y + } + + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + const MIN_WIDTH = 200 + const MIN_HEIGHT = 100 + + switch (whereIsMouseOver) { + case 'center': { + switch (whatHappened) { + case 'left mouse button + SHIFT': { + /* This is equivalent to drag the whole Time Machine, so we will apply the translation received onto the Time Machine container. */ + thisObject.container.parentContainer.frame.position.x = parentFrameWhenDragStarted.position.x + dragVectorWhenDragStarted.x + thisObject.container.parentContainer.frame.position.y = parentFrameWhenDragStarted.position.y + dragVectorWhenDragStarted.y + snapPointToGrid(thisObject.container.parentContainer.frame.position) + thisObject.container.parentContainer.eventHandler.raiseEvent('onDisplace') + break + } + case 'left mouse button': { + let point = { + x: (-dragVectorWhenDragStarted.x), + y: (-dragVectorWhenDragStarted.y) + } + + let newMinDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtContainer(point, thisObject.container.parentContainer, coordinateSystemWhenDragStarted) + let newMaxRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer(point, thisObject.container.parentContainer, coordinateSystemWhenDragStarted) + + let xDifferenceMaxMin = coordinateSystemWhenDragStarted.max.x - coordinateSystemWhenDragStarted.min.x + let yDifferenceMaxMin = coordinateSystemWhenDragStarted.max.y - coordinateSystemWhenDragStarted.min.y + coordinateSystem.min.x = newMinDate.valueOf() + coordinateSystem.max.x = newMinDate.valueOf() + xDifferenceMaxMin + coordinateSystem.min.y = newMaxRate - yDifferenceMaxMin + coordinateSystem.max.y = newMaxRate + let event = { + type: 'center dragged', + dragVector: dragVector + } + coordinateSystem.recalculateScale(event) + } + break + case 'left or right arrow key pressed': { + let point = { + x: -dragVector.x + thisObject.container.frame.width * coordinateSystem.HORIZONTAL_MARGIN_INVERSE_FACTOR / 2, + y: 0 + } + + let newMinDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtContainer(point, thisObject.container.parentContainer, coordinateSystem) + let xDifferenceMaxMin = coordinateSystem.max.x - coordinateSystem.min.x + coordinateSystem.min.x = newMinDate.valueOf() + coordinateSystem.max.x = newMinDate.valueOf() + xDifferenceMaxMin + let event = { + type: 'center dragged', + dragVector: dragVector + } + coordinateSystem.recalculateScale(event) + } + break + case 'up or down arrow key pressed': { + let point = { + x: 0, + y: -dragVector.y + thisObject.container.frame.height * coordinateSystem.VERTICAL_MARGIN_INVERSE_FACTOR / 2 + } + + let newMaxRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer(point, thisObject.container.parentContainer, coordinateSystem) + let yDifferenceMaxMin = coordinateSystem.max.y - coordinateSystem.min.y + coordinateSystem.min.y = newMaxRate - yDifferenceMaxMin + coordinateSystem.max.y = newMaxRate + let event = { + type: 'center dragged', + dragVector: dragVector + } + coordinateSystem.recalculateScale(event) + } + break + } + thisObject.container.parentContainer.eventHandler.raiseEvent('onMouseOver', mouse.position) + break + } + case 'top': { + dragVectorWhenDragStarted.x = 0 + + let newHight = parentFrameWhenDragStarted.height - dragVectorWhenDragStarted.y + if (newHight < MIN_HEIGHT && dragVectorWhenDragStarted.y > 0) { + dragVectorWhenDragStarted.y = parentFrameWhenDragStarted.height - MIN_HEIGHT + } + + thisObject.container.parentContainer.frame.position.y = parentFrameWhenDragStarted.position.y + dragVectorWhenDragStarted.y + snapPointToGrid(thisObject.container.parentContainer.frame.position) + thisObject.container.parentContainer.frame.height = parentFrameWhenDragStarted.height + (parentFrameWhenDragStarted.position.y - thisObject.container.parentContainer.frame.position.y) + + let point = { + x: 0, + y: (thisObject.container.parentContainer.frame.position.y - parentFrameWhenDragStarted.position.y) + } + + /* This is equivalent to UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer, but as we do not have a container anymore because we already change it, we do it like this. */ + point = coordinateSystemWhenDragStarted.unInverseTransform(point, parentFrameWhenDragStarted.height) + let newMaxRate = point.y + + switch (whatHappened) { + case 'left mouse button': { + coordinateSystem.max.y = newMaxRate + coordinateSystem.maxHeight = thisObject.container.parentContainer.frame.height + let event = { + type: 'top dragged', + dragVector: dragVector + } + coordinateSystem.recalculateScale(event) + break + } + case 'left mouse button + SHIFT': { + coordinateSystem.maxHeight = thisObject.container.parentContainer.frame.height + coordinateSystem.recalculateScale() + break + } + } + thisObject.container.parentContainer.eventHandler.raiseEvent('Dimmensions Changed', event) + break + } + case 'bottom': { + dragVectorWhenDragStarted.x = 0 + + let newHight = dragVectorWhenDragStarted.y + parentFrameWhenDragStarted.height + if (newHight < MIN_HEIGHT && dragVectorWhenDragStarted.y < 0) { + dragVectorWhenDragStarted.y = MIN_HEIGHT - parentFrameWhenDragStarted.height + } + + let point = { + x: 0, + y: dragVectorWhenDragStarted.y + parentFrameWhenDragStarted.height + parentFrameWhenDragStarted.position.y + } + snapPointToGrid(point) + point.y = point.y - parentFrameWhenDragStarted.position.y + + thisObject.container.parentContainer.frame.height = point.y + + /* This is equivalent to UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer, but as we do not have a container anymore because we already change it, we do it like this. */ + point = coordinateSystemWhenDragStarted.unInverseTransform(point, parentFrameWhenDragStarted.height) + let newMinRate = point.y + + switch (whatHappened) { + case 'left mouse button': { + coordinateSystem.min.y = newMinRate + coordinateSystem.maxHeight = thisObject.container.parentContainer.frame.height + let event = { + type: 'bottom dragged', + dragVector: dragVector + } + coordinateSystem.recalculateScale(event) + break + } + case 'left mouse button + SHIFT': { + coordinateSystem.maxHeight = thisObject.container.parentContainer.frame.height + coordinateSystem.recalculateScale() + break + } + } + thisObject.container.parentContainer.eventHandler.raiseEvent('Dimmensions Changed', event) + break + } + case 'left': { + dragVectorWhenDragStarted.y = 0 + + let newWidth = parentFrameWhenDragStarted.width - dragVectorWhenDragStarted.x + if (newWidth < MIN_WIDTH && dragVectorWhenDragStarted.x > 0) { + dragVectorWhenDragStarted.x = parentFrameWhenDragStarted.width - MIN_WIDTH + } + + thisObject.container.parentContainer.frame.position.x = parentFrameWhenDragStarted.position.x + dragVectorWhenDragStarted.x + snapPointToGrid(thisObject.container.parentContainer.frame.position) + thisObject.container.parentContainer.frame.width = parentFrameWhenDragStarted.width + (parentFrameWhenDragStarted.position.x - thisObject.container.parentContainer.frame.position.x) + + let point = { + x: (thisObject.container.parentContainer.frame.position.x - parentFrameWhenDragStarted.position.x), + y: 0 + } + + /* This is equivalent to UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtContainer, but as we do not have a container anymore because we already change it, we do it like this. */ + point = coordinateSystemWhenDragStarted.unInverseTransform(point, parentFrameWhenDragStarted.width) + let newMinDate = point.x + + switch (whatHappened) { + case 'left mouse button': { + coordinateSystem.min.x = newMinDate.valueOf() + coordinateSystem.maxWidth = thisObject.container.parentContainer.frame.width + coordinateSystem.recalculateScale() + break + } + case 'left mouse button + SHIFT': { + coordinateSystem.maxWidth = thisObject.container.parentContainer.frame.width + coordinateSystem.recalculateScale() + break + } + } + thisObject.container.parentContainer.eventHandler.raiseEvent('Dimmensions Changed', event) + break + } + case 'right': { + dragVectorWhenDragStarted.y = 0 + + let newWidth = dragVectorWhenDragStarted.x + parentFrameWhenDragStarted.width + if (newWidth < MIN_WIDTH && dragVectorWhenDragStarted.x < 0) { + dragVectorWhenDragStarted.x = MIN_WIDTH - parentFrameWhenDragStarted.width + } + + let point = { + x: dragVectorWhenDragStarted.x + parentFrameWhenDragStarted.width + parentFrameWhenDragStarted.position.x, + y: 0 + } + snapPointToGrid(point) + point.x = point.x - parentFrameWhenDragStarted.position.x + + thisObject.container.parentContainer.frame.width = point.x + + /* This is equivalent to UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer, but as we do not have a container anymore because we already change it, we do it like this. */ + point = coordinateSystemWhenDragStarted.unInverseTransform(point, parentFrameWhenDragStarted.width) + let newMaxDate = point.x + + switch (whatHappened) { + case 'left mouse button': { + coordinateSystem.max.x = newMaxDate.valueOf() + coordinateSystem.maxWidth = thisObject.container.parentContainer.frame.width + coordinateSystem.recalculateScale() + break + } + case 'left mouse button + SHIFT': { + coordinateSystem.maxWidth = thisObject.container.parentContainer.frame.width + coordinateSystem.recalculateScale() + break + } + } + thisObject.container.parentContainer.eventHandler.raiseEvent('Dimmensions Changed', event) + break + } + } + + function snapPointToGrid(point) { + point.x = snapScalarToGrid(point.x) + point.y = snapScalarToGrid(point.y) + } + + function snapScalarToGrid(scalar) { + let sign + if (scalar < 0) { + sign = -1 + } else { + sign = 1 + } + const GRID_SIZE = 10 + return Math.trunc(scalar / GRID_SIZE) * GRID_SIZE + GRID_SIZE / 2 * sign + } + } + + function drawForeground() { + let MARGIN = 1 + let edgeSize + let lineWidth + + if (whereIsMouseOver === 'outside' || thisObject.isMouseOver === false) { + edgeSize = 2 + lineWidth = 0.1 + } else { + edgeSize = EDGE_SIZE + lineWidth = 0.5 + } + + const OPACITY = 1 + + pointA1 = { + x: 0, + y: 0 + } + + pointA2 = { + x: thisObject.container.frame.width, + y: 0 + } + + pointA3 = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + } + + pointA4 = { + x: 0, + y: thisObject.container.frame.height + } + + pointB1 = { + x: 0, + y: 0 + } + + pointB2 = { + x: thisObject.container.frame.width, + y: 0 + } + + pointB3 = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + } + + pointB4 = { + x: 0, + y: thisObject.container.frame.height + } + + pointA1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointA1, thisObject.container) + pointA2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointA2, thisObject.container) + pointA3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointA3, thisObject.container) + pointA4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointA4, thisObject.container) + + pointB1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointB1, thisObject.container) + pointB2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointB2, thisObject.container) + pointB3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointB3, thisObject.container) + pointB4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(pointB4, thisObject.container) + + pointA1 = { + x: pointA1.x - MARGIN, + y: pointA1.y - MARGIN + } + + pointA2 = { + x: pointA2.x + MARGIN, + y: pointA2.y - MARGIN + } + + pointA3 = { + x: pointA3.x + MARGIN, + y: pointA3.y + MARGIN + } + + pointA4 = { + x: pointA4.x - MARGIN, + y: pointA4.y + MARGIN + } + + pointB1 = { + x: pointB1.x - edgeSize, + y: pointB1.y - edgeSize + } + + pointB2 = { + x: pointB2.x + edgeSize, + y: pointB2.y - edgeSize + } + + pointB3 = { + x: pointB3.x + edgeSize, + y: pointB3.y + edgeSize + } + + pointB4 = { + x: pointB4.x - edgeSize, + y: pointB4.y + edgeSize + } + + pointA1 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointA1) + pointA2 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointA2) + pointA3 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointA3) + pointA4 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointA4) + + pointB1 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointB1) + pointB2 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointB2) + pointB3 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointB3) + pointB4 = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoViewport(pointB4) + + browserCanvasContext.setLineDash([0, 0]) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(pointA1.x, pointA1.y) + browserCanvasContext.lineTo(pointA2.x, pointA2.y) + browserCanvasContext.lineTo(pointB2.x, pointB2.y) + browserCanvasContext.lineTo(pointB1.x, pointB1.y) + browserCanvasContext.lineTo(pointA1.x, pointA1.y) + browserCanvasContext.closePath() + + drawEdge('top') + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(pointA2.x, pointA2.y) + browserCanvasContext.lineTo(pointA3.x, pointA3.y) + browserCanvasContext.lineTo(pointB3.x, pointB3.y) + browserCanvasContext.lineTo(pointB2.x, pointB2.y) + browserCanvasContext.lineTo(pointA2.x, pointA2.y) + browserCanvasContext.closePath() + + drawEdge('right') + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(pointA3.x, pointA3.y) + browserCanvasContext.lineTo(pointA4.x, pointA4.y) + browserCanvasContext.lineTo(pointB4.x, pointB4.y) + browserCanvasContext.lineTo(pointB3.x, pointB3.y) + browserCanvasContext.lineTo(pointA3.x, pointA3.y) + browserCanvasContext.closePath() + + drawEdge('bottom') + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(pointA4.x, pointA4.y) + browserCanvasContext.lineTo(pointA1.x, pointA1.y) + browserCanvasContext.lineTo(pointB1.x, pointB1.y) + browserCanvasContext.lineTo(pointB4.x, pointB4.y) + browserCanvasContext.lineTo(pointA4.x, pointA4.y) + browserCanvasContext.closePath() + + drawEdge('left') + + function drawEdge(edgeType) { + if (whereIsMouseOver === edgeType && thisObject.isMouseOver === true) { + browserCanvasContext.lineWidth = lineWidth + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + OPACITY + ')' + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + } else { + browserCanvasContext.lineWidth = lineWidth + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + OPACITY + ')' + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + OPACITY + ')' + } + browserCanvasContext.fill() + browserCanvasContext.stroke() + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/PlottersManager.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/PlottersManager.js new file mode 100644 index 000000000..540934334 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/PlottersManager.js @@ -0,0 +1,489 @@ +function newPlottersManager() { + const MODULE_NAME = 'Plotters Manager' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let timeFrame = INITIAL_TIME_PERIOD + let datetime = NEW_SESSION_INITIAL_DATE + + let thisObject = { + fitFunction: undefined, + container: undefined, + payload: undefined, + connectors: [], + setDatetime: setDatetime, + setTimeFrame: setTimeFrame, + setCoordinateSystem: setCoordinateSystem, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.connectors = [] + + let initializationReady = false + let layersPanel + let onLayerStatusChangedEventSuscriptionId + let coordinateSystem + + setupContainer() + return thisObject + + function setupContainer() { + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + } + + function finalize() { + layersPanel.container.eventHandler.stopListening(onLayerStatusChangedEventSuscriptionId) + layersPanel = undefined + + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[i] + /* Then the panels. */ + for (let j = 0; j < connector.panels.length; j++) { + UI.projects.superalgos.spaces.panelSpace.destroyPanel(connector.panels[j]) + connector.panels[j] = undefined + } + connector.panels = undefined + + /* Finalize the plotters */ + if (connector.plotter.finalize !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.plotter.onRecordChangeEventsSubscriptionId) + connector.plotter.finalize() + } + connector.plotter = undefined + + /* Finally the Storage Objects */ + finalizeStorage(connector.storage) + connector.storage = undefined + + connector = undefined + } + thisObject.connectors = [] + coordinateSystem = undefined + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + } + + function finalizeStorage(storage) { + storage.eventHandler.stopListening(storage.onMarketFileLoadedLayerEventsSubscriptionId) + storage.eventHandler.stopListening(storage.onDailyFileLoadedLayerEventsSubscriptionId) + storage.eventHandler.stopListening(storage.onSingleFileLoadedLayerEventsSubscriptionId) + storage.eventHandler.stopListening(storage.onFileSequenceLoadedLayerEventsSubscriptionId) + storage.eventHandler.stopListening(storage.onDailyFileLoadedPlotterEventsSubscriptionId) + storage.finalize() + } + + function initialize(pLayersPanel) { + /* Remember this */ + layersPanel = pLayersPanel + + /* Listen to the event of change of status */ + onLayerStatusChangedEventSuscriptionId = layersPanel.container.eventHandler.listenToEvent('Layer Status Changed', onLayerStatusChanged) + } + + function initializePlotter(layer) { + try { + /* Before Initializing a Plotter, we need the Storage it will use, loaded with the files it will initially need. */ + let storage = newProductStorage(layer.payload.node.id) + + /* + Before Initializing the Storage, we will put the Layer to listen to the events the storage will raise every time a file is loaded, + so that the UI can somehow show this. There are different types of events. + */ + for (let i = 0; i < layer.definition.referenceParent.referenceParent.datasets.length; i++) { + let dataset = layer.definition.referenceParent.referenceParent.datasets[i] + + switch (dataset.config.type) { + case 'Market Files': { + storage.onMarketFileLoadedLayerEventsSubscriptionId = storage.eventHandler.listenToEvent('Market File Loaded', layer.onMarketFileLoaded) + } + break + case 'Daily Files': { + storage.onDailyFileLoadedLayerEventsSubscriptionId = storage.eventHandler.listenToEvent('Daily File Loaded', layer.onDailyFileLoaded) + } + break + case 'Single File': { + storage.onSingleFileLoadedLayerEventsSubscriptionId = storage.eventHandler.listenToEvent('Single File Loaded', layer.onSingleFileLoaded) + } + break + case 'File Sequence': { + storage.onFileSequenceLoadedLayerEventsSubscriptionId = storage.eventHandler.listenToEvent('File Sequence Loaded', layer.onFileSequenceLoaded) + } + break + default: { + if (ERROR_LOG === true) { + logger.write('[ERROR] initializePlotter -> Dataset Type Incorrect or Undefined -> dataset.type = ' + dataset.type) + } + return + } + } + } + + let baseAsset = layer.baseAsset.config.codeName + let quotedAsset = layer.quotedAsset.config.codeName + let market = { + baseAsset: baseAsset, + quotedAsset: quotedAsset + } + let productDefinition = layer.productDefinition + let bot = layer.bot + let dataMine = layer.dataMine + let exchange = layer.exchange + let plotterModule = layer.plotterModule + let session + let tradingSystem + let tradingEngine + + /* + A layer can be referencing a Data Product in two different branches of the Network hiriatchy. + In one of those braches we can get the Session node, at the other we Cannot. + */ + let sessionReference = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(layer.definition, 'Session Reference', undefined, false, true, true, true) + + if (sessionReference !== undefined) { + session = sessionReference.referenceParent + if (session === undefined) { + logger.write('[ERROR] initializePlotter -> Sessioin is Undefined at Session Reference -> Plotter will not be loaded. ') + return + } + /* From the session we might be able to reach the Trading System or the Trading Engine */ + if (session.tradingSystemReference !== undefined) { + tradingSystem = session.tradingSystemReference.referenceParent + } + if (session.tradingEngineReference !== undefined) { + tradingEngine = session.tradingEngineReference.referenceParent + } + } + + let host = layer.networkNode.config.host + let webPort = layer.networkNode.config.webPort + if (host === undefined) { host = window.location.hostname } + if (webPort === undefined) { webPort = window.location.port } + + let eventsServerClient = UI.projects.superalgos.spaces.designSpace.workspace.eventsServerClients.get(layer.networkNode.id) + + storage.initialize( + dataMine, + bot, + session, + productDefinition, + exchange, + market, + datetime, + timeFrame, + host, + webPort, + eventsServerClient, + onProductStorageInitialized + ) + + function onProductStorageInitialized(err) { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + /* Now we have all the initial data loaded and ready to be delivered to the new instance of the plotter. */ + let plotter + + if (plotterModule.config.isLegacy !== true) { + plotter = newPlotter() + } else { + plotter = getNewPlotter(dataMine.config.codeName, plotterModule.parentNode.config.codeName, plotterModule.config.codeName) + } + + plotter.container.connectToParent(thisObject.container, true, true, false, true, true, true, false, false, true) + plotter.container.frame.position.x = thisObject.container.frame.width / 2 - plotter.container.frame.width / 2 + plotter.container.frame.position.y = thisObject.container.frame.height / 2 - plotter.container.frame.height / 2 + plotter.fitFunction = thisObject.fitFunction + plotter.initialize(storage, datetime, timeFrame, coordinateSystem, onPlotterInizialized, productDefinition) + + function onPlotterInizialized() { + let connector = { + layer: layer, + plotter: plotter, + storage: storage + } + /* Let the Plotter listen to the event of Cursor Files loaded, so that it can react recalculating if needed. */ + plotter.onDailyFileLoadedPlotterEventsSubscriptionId = storage.eventHandler.listenToEvent('Daily File Loaded', plotter.onDailyFileLoaded) + /* Lets load now this plotter panels. */ + connector.panels = [] + + if (productDefinition !== undefined) { + /* Here we setup the panels associated with this plotter */ + for (let i = 0; i < plotterModule.panels.length; i++) { + let panel = plotterModule.panels[i] + + let parameters = { + dataMine: dataMine.config.codeName, + plotterCodeName: plotterModule.parentNode.config.codeName, + moduleCodeName: plotterModule.config.codeName, + panelNode: panel + } + + let owner = thisObject.payload.node.payload.parentNode.id // Panels are owned by the time machine. + let plotterPanelHandle = UI.projects.superalgos.spaces.panelSpace.createNewPanel('Plotter Panel', parameters, owner, layer.session) + let plotterPanel = UI.projects.superalgos.spaces.panelSpace.getPanel(plotterPanelHandle) + + /* Connect Panel to the Plotter via an Event. */ + connector.plotter.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', plotterPanel.onRecordChange) + connector.panels.push(plotterPanelHandle) + } + connector.layer.panels = connector.panels + + /* + Next we will check the different types of Plotting related to connecting to an existing node branch. + */ + if (plotterModule.nodesHighlights !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesHighlights = newNodesHighlights() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesHighlights.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesHighlights.initialize(tradingEngine) + break + } + connector.nodesHighlights.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesHighlights.onRecordChange) + } + if (plotterModule.nodesValues !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesValues = newNodesValues() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesValues.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesValues.initialize(tradingEngine) + break + } + connector.nodesValues.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesValues.onRecordChange) + } + if (plotterModule.nodesErrors !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesErrors = newNodesErrors() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesErrors.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesErrors.initialize(tradingEngine) + break + } + connector.nodesErrors.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesErrors.onRecordChange) + } + if (plotterModule.nodesWarnings !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesWarnings = newNodesWarnings() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesWarnings.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesWarnings.initialize(tradingEngine) + break + } + connector.nodesWarnings.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesWarnings.onRecordChange) + } + if (plotterModule.nodesInfos !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesInfos = newNodesInfos() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesInfos.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesInfos.initialize(tradingEngine) + break + } + connector.nodesInfos.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesInfos.onRecordChange) + } + if (plotterModule.nodesRunning !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesRunning = newNodesRunning() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesRunning.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesRunning.initialize(tradingEngine) + break + } + connector.nodesRunning.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesRunning.onRecordChange) + } + if (plotterModule.nodesStatus !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesStatus = newNodesStatus() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesStatus.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesStatus.initialize(tradingEngine) + break + } + connector.nodesStatus.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesStatus.onRecordChange) + } + if (plotterModule.nodesProgress !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesProgress = newNodesProgress() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesProgress.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesProgress.initialize(tradingEngine) + break + } + connector.nodesProgress.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesProgress.onRecordChange) + } + if (plotterModule.nodesAnnouncements !== undefined && plotterModule.config.connectTo !== undefined) { + connector.nodesAnnounements = newNodesAnnouncements() + switch (plotterModule.config.connectTo) { + case 'Trading System': + connector.nodesAnnounements.initialize(tradingSystem) + break + case 'Trading Engine': + connector.nodesAnnounements.initialize(tradingEngine) + break + } + connector.nodesAnnounements.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.nodesAnnounements.onRecordChange) + } + /* + Another type of plotting is with Record Values. This method uses the record definition to find the target node + and from there is looks into its children all based on the Record Definition configuration. + */ + if (plotterModule.recordValues !== undefined) { + connector.recordValues = newRecordValues() + connector.recordValues.initialize(tradingSystem, tradingEngine, productDefinition) + connector.recordValues.onRecordChangeEventsSubscriptionId = connector.plotter.container.eventHandler.listenToEvent('Current Record Changed', connector.recordValues.onRecordChange) + } + } + thisObject.connectors.push(connector) + } + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initializePlotter -> err = ' + err.stack) } + } + } + + function onLayerStatusChanged(layer) { + if (layer.status === LAYER_STATUS.LOADING) { + /* Lets see if we can find the Plotter of this card on our Active Plotters list, other wise we will initialize it */ + let found = false + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[i] + if (connector.layer.payload.node.id === layer.payload.node.id) { + found = true + } + } + if (found === false) { + initializePlotter(layer) + } + } + if (layer.status === LAYER_STATUS.OFF) { + /* If the plotter of this card is not on our Active Plotters list, then we remove it. */ + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[i] + if (connector.layer.payload.node.id === layer.payload.node.id) { + /* Then the panels. */ + for (let j = 0; j < connector.panels.length; j++) { + UI.projects.superalgos.spaces.panelSpace.destroyPanel(connector.panels[j]) + } + /* Finally the Storage Objects */ + finalizeNodesPlotting(connector) + if (connector.plotter.finalize !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.plotter.onRecordChangeEventsSubscriptionId) + connector.plotter.container.finalize() + connector.plotter.finalize() + } + finalizeStorage(connector.storage) + thisObject.connectors.splice(i, 1) // Delete item from array. + return // We already found the product woth changes and processed it. + } + } + } + } + + function finalizeNodesPlotting(connector) { + if (connector.nodesHighlights !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesHighlights.onRecordChangeEventsSubscriptionId) + connector.nodesHighlights.finalize() + } + if (connector.nodesValues !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesValues.onRecordChangeEventsSubscriptionId) + connector.nodesValues.finalize() + } + if (connector.nodesErrors !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesErrors.onRecordChangeEventsSubscriptionId) + connector.nodesErrors.finalize() + } + if (connector.nodesWarnings !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesWarnings.onRecordChangeEventsSubscriptionId) + connector.nodesWarnings.finalize() + } + if (connector.nodesInfos !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesInfos.onRecordChangeEventsSubscriptionId) + connector.nodesInfos.finalize() + } + if (connector.nodesStatus !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesStatus.onRecordChangeEventsSubscriptionId) + connector.nodesStatus.finalize() + } + if (connector.nodesProgress !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.Progress.onRecordChangeEventsSubscriptionId) + connector.Progress.finalize() + } + if (connector.nodesRunning !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.nodesRunning.onRecordChangeEventsSubscriptionId) + connector.nodesRunning.finalize() + } + if (connector.recordValues !== undefined) { + connector.plotter.container.eventHandler.stopListening(connector.recordValues.onRecordChangeEventsSubscriptionId) + connector.recordValues.finalize() + } + } + + function setTimeFrame(pTimeFrame) { + timeFrame = pTimeFrame + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[i] + connector.layer.setTimeFrame(timeFrame) + connector.storage.setTimeFrame(timeFrame) + connector.plotter.setTimeFrame(timeFrame) + } + } + + function setDatetime(pDatetime) { + datetime = pDatetime + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[i] + connector.layer.setDatetime(pDatetime) + connector.storage.setDatetime(pDatetime) + connector.plotter.setDatetime(pDatetime) + } + } + + function setCoordinateSystem(pCoordinateSystem) { + coordinateSystem = pCoordinateSystem + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[i] + connector.plotter.setCoordinateSystem(coordinateSystem) + } + } + + function getContainer(point) { + } + + function draw() { + if (thisObject.connectors === undefined) { return } // We need to wait + /* First the Product Plotters. */ + let maxElementsPlotted = 0 + for (let i = 0; i < thisObject.connectors.length; i++) { + let connector = thisObject.connectors[thisObject.connectors.length - i - 1] + let elementsPlotted = connector.plotter.draw() + if (elementsPlotted !== undefined) { + if (elementsPlotted > maxElementsPlotted) { + maxElementsPlotted = elementsPlotted + } + } + } + return maxElementsPlotted + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesAnnouncements.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesAnnouncements.js new file mode 100644 index 000000000..b248a90da --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesAnnouncements.js @@ -0,0 +1,46 @@ +function newNodesAnnouncements() { + const MODULE_NAME = 'Nodes Announcements' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.announcements + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setValue(value, 20) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesErrors.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesErrors.js new file mode 100644 index 000000000..92302a351 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesErrors.js @@ -0,0 +1,47 @@ +function newNodesErrors() { + const MODULE_NAME = 'Nodes Errors' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.errors + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === '') { + node.payload.uiObject.resetErrorMessage() + } else { + node.payload.uiObject.setErrorMessage(value) + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesHighlights.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesHighlights.js new file mode 100644 index 000000000..c0af60ae9 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesHighlights.js @@ -0,0 +1,42 @@ +function newNodesHighlights() { + const MODULE_NAME = 'Nodes Highlights' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.highlights + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let nodeId = array[i] + highlight(nodeId) + } + } + + function highlight(nodeId) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + node.payload.uiObject.highlight(10) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesInfos.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesInfos.js new file mode 100644 index 000000000..177b3c338 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesInfos.js @@ -0,0 +1,47 @@ +function newNodesInfos() { + const MODULE_NAME = 'Nodes Infos' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.infos + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === '') { + node.payload.uiObject.resetInfoMessage() + } else { + node.payload.uiObject.setInfoMessage(value) + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesProgress.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesProgress.js new file mode 100644 index 000000000..dc9b28ccd --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesProgress.js @@ -0,0 +1,47 @@ +function newNodesProgress() { + const MODULE_NAME = 'Nodes Progress' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.progress + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setPercentage(value, 30) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesRunning.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesRunning.js new file mode 100644 index 000000000..421129566 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesRunning.js @@ -0,0 +1,47 @@ +function newNodesRunning() { + const MODULE_NAME = 'Nodes Running' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.running + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.runningAtBackend() + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesStatus.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesStatus.js new file mode 100644 index 000000000..8cb51b1b2 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesStatus.js @@ -0,0 +1,47 @@ +function newNodesStatus() { + const MODULE_NAME = 'Nodes Status' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.status + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setStatus(value, 30) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesValues.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesValues.js new file mode 100644 index 000000000..1ea35832c --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesValues.js @@ -0,0 +1,48 @@ +function newNodesValues() { + const MODULE_NAME = 'Nodes Values' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.values + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.valueAtAngle = false + node.payload.uiObject.setValue(value, 1) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesWarnings.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesWarnings.js new file mode 100644 index 000000000..bdffad3a8 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/NodesWarnings.js @@ -0,0 +1,47 @@ +function newNodesWarnings() { + const MODULE_NAME = 'Nodes Warnings' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + let hiriarchyMap + return thisObject + + function finalize() { + hiriarchyMap = undefined + } + + function initialize(pRootNode) { + let rootNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pRootNode.id) + hiriarchyMap = UI.projects.superalgos.utilities.hierarchy.getHiriarchyMap(rootNode) + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + let array = currentRecord.warnings + if (array === undefined) { return } + for (let i = 0; i < array.length; i++) { + let arrayItem = array[i] + let nodeId = arrayItem[0] + let value = arrayItem[1] + applyValue(nodeId, value) + } + } + + function applyValue(nodeId, value) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + let node = hiriarchyMap.get(nodeId) + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === '') { + node.payload.uiObject.resetWarningMessage() + } else { + node.payload.uiObject.setWarningMessage(value) + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/Plotter.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/Plotter.js new file mode 100644 index 000000000..aa5ae072c --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/Plotter.js @@ -0,0 +1,918 @@ +function newPlotter() { + const MODULE_NAME = 'Plotter' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + currentRecord: undefined, + container: undefined, + fitFunction: undefined, + onDailyFileLoaded: onDailyFileLoaded, + initialize: initialize, + finalize: finalize, + getContainer: getContainer, + setTimeFrame: setTimeFrame, + setDatetime: setDatetime, + setCoordinateSystem: setCoordinateSystem, + draw: draw + } + + let container = newContainer() + container.initialize() + thisObject.container = container + + let coordinateSystem + let slotHeight = (UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y) / 10 // This is the amount of slots available + let mustRecalculateDataPoints = false + let atMousePositionFillStyles = new Map() + let atMousePositionStrokeStyles = new Map() + + let timeFrame // This will hold the current Time Frame the user is at. + let datetime // This will hold the current Datetime the user is at. + + let marketFile // This is the current Market File being plotted. + let fileCursor // This is the current File Cursor being used to retrieve Daily Files. + + let marketFiles // This object will provide the different Market Files at different Time Periods. + let dailyFiles // This object will provide the different File Cursors at different Time Periods. + + let productDefinition // Here we store a snapshot of the product definition which references this plotter. + + let records = [] // We will have the information to be plotted here. + let userPositionDate + let minUserPositionRate + let maxUserPositionRate + + let onMouseOverEventSuscriptionId + let zoomChangedEventSubscriptionId + let offsetChangedEventSubscriptionId + let dragFinishedEventSubscriptionId + let dimmensionsChangedEventSubscriptionId + let marketFilesUpdatedEventSubscriptionId + let dailyFilesUpdatedEventSubscriptionId + let onDisplaceEventSubscriptionId + let scaleChangedEventSubscriptionId + + let logged = false + return thisObject + + function finalize() { + try { + /* Stop listening to the necesary events. */ + thisObject.container.eventHandler.stopListening(onMouseOverEventSuscriptionId) + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(zoomChangedEventSubscriptionId) + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(offsetChangedEventSubscriptionId) + canvas.eventHandler.stopListening(dragFinishedEventSubscriptionId) + thisObject.container.eventHandler.stopListening(dimmensionsChangedEventSubscriptionId) + marketFiles.eventHandler.stopListening(marketFilesUpdatedEventSubscriptionId) + dailyFiles.eventHandler.stopListening(dailyFilesUpdatedEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onDisplaceEventSubscriptionId) + thisObject.fitFunction = undefined + + /* Clear References */ + marketFiles = undefined + dailyFiles = undefined + + datetime = undefined + timeFrame = undefined + + marketFile = undefined + fileCursor = undefined + + finalizeCoordinateSystem() + coordinateSystem = undefined + slotHeight = undefined + mustRecalculateDataPoints = undefined + atMousePositionFillStyles = undefined + atMousePositionStrokeStyles = undefined + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] finalize -> err = ' + err.stack.stack) } + } + } + + function initialize(pStorage, pDatetime, pTimeFrame, pCoordinateSystem, callBackFunction, pProductDefinition) { + try { + + /* Store the information received. */ + marketFiles = pStorage.marketFiles[0] + dailyFiles = pStorage.dailyFiles[0] + + datetime = pDatetime + timeFrame = pTimeFrame + coordinateSystem = pCoordinateSystem + initializeCoordinateSystem() + + productDefinition = pProductDefinition + + /* We need a Market File in order to calculate the Y scale, since this scale depends on actual data. */ + marketFile = marketFiles.getFile(ONE_DAY_IN_MILISECONDS) // This file is the one processed faster. + + /* Now we set the right files according to current Period. */ + marketFile = marketFiles.getFile(pTimeFrame) + fileCursor = dailyFiles.getFileCursor(pTimeFrame) + + /* Listen to the necesary events. */ + zoomChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent('Zoom Changed', onViewportZoomChanged) + offsetChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent('Position Changed', onViewportPositionChanged) + dragFinishedEventSubscriptionId = canvas.eventHandler.listenToEvent('Drag Finished', onDragFinished) + marketFilesUpdatedEventSubscriptionId = marketFiles.eventHandler.listenToEvent('Files Updated', onMarketFilesUpdated) + dailyFilesUpdatedEventSubscriptionId = dailyFiles.eventHandler.listenToEvent('Files Updated', onDailyFilesUpdated) + onDisplaceEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onDisplace', onDisplace) + onMouseOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + + /* Get ready for plotting. */ + recalculate() + dimmensionsChangedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('Dimmensions Changed', function () { + recalculate() + }) + + callBackFunction() + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = ' + err.stack) } + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + + function initializeCoordinateSystem() { + scaleChangedEventSubscriptionId = coordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + } + + function finalizeCoordinateSystem() { + coordinateSystem.eventHandler.stopListening(scaleChangedEventSubscriptionId) + } + + function onScaleChanged() { + mustRecalculateDataPoints = true + recalculate() + } + + function onMouseOver(event) { + let userPosition = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(event, thisObject.container, coordinateSystem) + userPositionDate = userPosition.valueOf() + + let minPositionPoint = { + x: event.x, + y: event.y + 4 + } + minUserPositionRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtBrowserCanvas(minPositionPoint, thisObject.container, coordinateSystem) + + let maxPositionPoint = { + x: event.x, + y: event.y - 4 + } + maxUserPositionRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtBrowserCanvas(maxPositionPoint, thisObject.container, coordinateSystem) + } + + function onMarketFilesUpdated() { + try { + let newMarketFile = marketFiles.getFile(timeFrame) + if (newMarketFile !== undefined) { + marketFile = newMarketFile + mustRecalculateDataPoints = true + recalculate() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onMarketFilesUpdated -> err = ' + err.stack.stack) } + } + } + + function onDailyFilesUpdated() { + try { + let newFileCursor = dailyFiles.getFileCursor(timeFrame) + if (newFileCursor !== undefined) { + fileCursor = newFileCursor + mustRecalculateDataPoints = true + recalculate() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onDailyFilesUpdated -> err = ' + err.stack.stack) } + } + } + + function getContainer(point) { + try { + let container + /* First we check if this point is inside this space. */ + if (thisObject.container.frame.isThisPointHere(point) === true) { + return thisObject.container + } else { + /* This point does not belong to this space. */ + return undefined + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] getContainer -> err = ' + err.stack) } + } + } + + function setTimeFrame(pTimeFrame) { + try { + if (timeFrame !== pTimeFrame) { + timeFrame = pTimeFrame + mustRecalculateDataPoints = true + if (timeFrame >= _1_HOUR_IN_MILISECONDS) { + marketFile = marketFiles.getFile(pTimeFrame) + } else { + fileCursor = dailyFiles.getFileCursor(pTimeFrame) + } + recalculate() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] setTimeFrame -> err = ' + err.stack) } + } + } + + function setDatetime(pDatetime) { + datetime = pDatetime + } + + function setCoordinateSystem(pCoordinateSystem) { + finalizeCoordinateSystem() + coordinateSystem = pCoordinateSystem + initializeCoordinateSystem() + } + + function onDailyFileLoaded(event) { + try { + if (event.currentValue === event.totalValue) { + /* This happens only when all of the files in the cursor have been loaded. */ + recalculate() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onDailyFileLoaded -> err = ' + err.stack) } + } + } + + function draw() { + try { + thisObject.container.frame.draw() + plot() + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] draw -> err = ' + err.stack) } + } + } + + function recalculate() { + try { + if (timeFrame >= _1_HOUR_IN_MILISECONDS) { + recalculateUsingMarketFiles() + } else { + recalculateUsingDailyFiles() + } + thisObject.container.eventHandler.raiseEvent('CandleStairs Changed', records) // --> Check This + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] recalculate -> err = ' + err.stack) } + } + } + + function jsonifyDataFile(dataFile, recordDefinition, farLeftDate, farRightDate) { + /* + This function has as an input the raw data on files and creates with it an array of JSON objects + with not calculated properties for later being consumed by Formulas + */ + + let jsonifiedArray = [] + let previous + + for (let i = 0; i < dataFile.length; i++) { + let record = {} + for (let j = 0; j < recordDefinition.properties.length; j++) { + let property = recordDefinition.properties[j] + if (property.config.isCalculated !== true) { + record[property.config.codeName] = dataFile[i][j] + } + } + + if (productDefinition.config.nodePathType === 'array') { + /* + When we have an array configuration, we know that the last field of the record + contains the array index, which is needed to correctly plot this data. Since + this index is not part of the product definition, we will restore its value in + this way: + */ + record.index = dataFile[i][recordDefinition.properties.length] + } + + if (record.begin === undefined || record.end === undefined) { + console.log('Could not find the property begin or end which are needed for the plotter to work. productDefinition.config.codeName = ' + productDefinition.config.codeName) + continue + } + + if (record.end <= farLeftDate.valueOf()) { continue } + if (record.begin >= farRightDate.valueOf()) { continue } + + if (record.end < coordinateSystem.min.x) { continue } + if (record.begin > coordinateSystem.max.x) { continue } + + record.previous = previous + jsonifiedArray.push(record) + previous = record + } + + return jsonifiedArray + } + + function calculationsProcedure(jsonArray, recordDefinition, calculationsProcedure, timeFrame) { + /* + This function has as an input an array of JSON objects, and it adds calculated properties to + complete the set of properties that will be available. + */ + + let system = { // These are the available system variables to be used in User Code and Formulas + timeFrame: timeFrame, + ONE_DAY_IN_MILISECONDS: ONE_DAY_IN_MILISECONDS + } + let variable = {} // This is the structure where the user will define its own variables that will be shared across different config blocks and formulas. + let results = [] + + /* This is Initialization Code */ + if (calculationsProcedure.initialization !== undefined) { + if (calculationsProcedure.initialization.javascriptCode !== undefined) { + try { + eval(calculationsProcedure.initialization.javascriptCode.code) + } catch (err) { + logger.write('[ERROR] calculationsProcedure -> initialization -> Error executing User Code. Error = ' + err.stack.stack) + logger.write('[ERROR] calculationsProcedure -> initialization -> Error executing User Code. Code = ' + calculationsProcedure.initialization.javascriptCode.code) + throw ('Error Executing User Code.') + } + } + } + + /* This is Initialization Code */ + if (calculationsProcedure.loop !== undefined) { + if (calculationsProcedure.loop.javascriptCode !== undefined) { + for (let index = 0; index < jsonArray.length; index++) { + let product = jsonArray[index] + + /* This is Loop Code */ + try { + eval(calculationsProcedure.loop.javascriptCode.code) + } catch (err) { + logger.write('[ERROR] calculationsProcedure -> loop -> Error executing User Code. Error = ' + err.stack.stack) + logger.write('[ERROR] calculationsProcedure -> loop -> Error executing User Code. product = ' + JSON.stringify(product)) + logger.write('[ERROR] calculationsProcedure -> loop -> Error executing User Code. Code = ' + calculationsProcedure.loop.javascriptCode.code) + throw ('Error Executing User Code.') + } + + /* For each calculated property we apply its formula */ + for (let j = 0; j < recordDefinition.properties.length; j++) { + let property = recordDefinition.properties[j] + if (property.config.isCalculated === true) { + if (property.formula !== undefined) { + if (property.formula.code !== undefined) { + try { + let newValue = eval(property.formula.code) + let currentRecord = product + currentRecord[property.config.codeName] = newValue + } catch (err) { + logger.write('[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Error = ' + err.stack.stack) + logger.write('[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. product = ' + JSON.stringify(product)) + logger.write('[ERROR] calculationsProcedure -> loop -> formula -> Error executing User Code. Code = ' + property.formula.code) + throw ('Error Executing User Code.') + } + } + } + } + } + + /* Adding the new element to the resulting array */ + results.push(product) + } + } + } + return results + } + + function recalculateUsingDailyFiles() { + try { + if (fileCursor === undefined) { + records = [] + return + } // We need to wait until there is a fileCursor available or maybe this indicator does not produce data for the current Time Frame + if (fileCursor.files.size === 0) { return } // We need to wait until there are files in the cursor + + let daysOnSides = getSideDays(timeFrame) + + let leftDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem) + let rightDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight, thisObject.container, coordinateSystem) + + let dateDiff = rightDate.valueOf() - leftDate.valueOf() + + let farLeftDate = new Date(leftDate.valueOf() - dateDiff * 1.5) + let farRightDate = new Date(rightDate.valueOf() + dateDiff * 1.5) + + let currentDate = new Date(farLeftDate.valueOf()) + + records = [] + + while (currentDate.valueOf() <= farRightDate.valueOf() + ONE_DAY_IN_MILISECONDS) { + let stringDate = currentDate.getFullYear() + '-' + pad(currentDate.getMonth() + 1, 2) + '-' + pad(currentDate.getDate(), 2) + + let dailyFile = fileCursor.files.get(stringDate) + + if (dailyFile !== undefined) { + /* Transform the current file content into an array of JSON objects */ + let jsonData = jsonifyDataFile(dailyFile, productDefinition.record, farLeftDate, farRightDate) + + /* Add the calculated properties */ + if (productDefinition.calculations !== undefined) { + let calculationsResult = calculationsProcedure(jsonData, productDefinition.record, productDefinition.calculations, timeFrame) + records.push(...calculationsResult) // This adds records to the current array. + } else { + records.push(...jsonData)// This adds records to the current array. + } + } + + currentDate = new Date(currentDate.valueOf() + ONE_DAY_IN_MILISECONDS) + } + + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] recalculateUsingDailyFiles -> err = ' + err.stack) } + } + } + + function recalculateUsingMarketFiles() { + try { + if (marketFile === undefined) { + records = [] + return + } // Initialization not complete yet or this indicator does not produce data for the current time frame + + let daysOnSides = getSideDays(timeFrame) + + let leftDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft, thisObject.container, coordinateSystem) + let rightDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight, thisObject.container, coordinateSystem) + + let dateDiff = rightDate.valueOf() - leftDate.valueOf() + + leftDate = new Date(leftDate.valueOf() - dateDiff * 1.5) + rightDate = new Date(rightDate.valueOf() + dateDiff * 1.5) + + for (let i = 0; i < records.length; i++) { + let record = records[i] + record.fillStyle = undefined + record.strokeStyle = undefined + record.dataPoints = undefined + record.previous = undefined + records[i] = {} + } + records = [] + + /* Transform the current file content into an array of JSON objects */ + let jsonData = jsonifyDataFile(marketFile, productDefinition.record, leftDate, rightDate) + + /* Add the calculated properties */ + if (productDefinition.calculations !== undefined) { + let calculationsResult = calculationsProcedure(jsonData, productDefinition.record, productDefinition.calculations, timeFrame) + records.push(...calculationsResult) // This adds records to the current array. + } else { + records.push(...jsonData)// This adds records to the current array. + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] recalculateUsingMarketFiles -> err = ' + err.stack) } + } + } + + function plot() { + try { + /* Clean the pannel at places where there is no record. */ + thisObject.container.eventHandler.raiseEvent('Current Record Changed', undefined) + + for (let i = 0; i < records.length; i++) { + let record = records[i] + + /* + In the formulas to create plotters, we allos users to reference the previous record. + To enable that we need to link all records to the previous one in this way. + */ + if (i == 0) { + record.previous = {} // this way it wont be undefined + } else { + record.previous = records[i - 1] + } + + let beginPoint = { + x: record.begin, + y: 0 + } + + let endPoint = { + x: record.end, + y: 0 + } + + beginPoint = coordinateSystem.transformThisPoint(beginPoint) + endPoint = coordinateSystem.transformThisPoint(endPoint) + + beginPoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(beginPoint, thisObject.container) + endPoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(endPoint, thisObject.container) + + beginPoint = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(beginPoint) + endPoint = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(endPoint) + + beginPoint = thisObject.fitFunction(beginPoint) + endPoint = thisObject.fitFunction(endPoint) + + if (endPoint.x < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.x || beginPoint.x > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.x) { + continue + } + + let atMousePosition = false + if (userPositionDate >= record.begin && userPositionDate <= record.end) { + if (record.rate !== undefined && productDefinition.referenceParent.config.useRateAtMousePosition === true) { + /* Current Record depends also on rate */ + if (record.rate >= minUserPositionRate && record.rate <= maxUserPositionRate) { + atMousePosition = true + thisObject.container.eventHandler.raiseEvent('Current Record Changed', record) + } + } else { + /* Current Record depends only on begin and end. */ + atMousePosition = true + thisObject.container.eventHandler.raiseEvent('Current Record Changed', record) + } + } + + /* If there is code we execute it now. */ + if (productDefinition.referenceParent.javascriptCode !== undefined) { + eval(productDefinition.referenceParent.javascriptCode.code) + } + + if (productDefinition.referenceParent.shapes === undefined) { continue } + if (productDefinition.referenceParent.shapes.chartPoints === undefined) { continue } + let dataPoints = new Map() + if (record.dataPoints !== undefined && mustRecalculateDataPoints === false) { + /* We use the datapoints already calculated. */ + dataPoints = record.dataPoints + } else { + if (logged === false) { + logged = true + } + /* It seems we need to calculate the data points this time. */ + for (let k = 0; k < productDefinition.referenceParent.shapes.chartPoints.length; k++) { + let chartPoints = productDefinition.referenceParent.shapes.chartPoints[k] + for (let j = 0; j < chartPoints.points.length; j++) { + let point = chartPoints.points[j] + if (point.pointFormula !== undefined) { + let x = 0 + let y = 0 + eval(point.pointFormula.code) + let rawPoint = { + x: x, + y: y + } + + /* + The information we store in files is independent from the charing system and its coordinate systems. + That means that the first thing we allways need to do is to trasform these points to the coordinate system of the timeline. + */ + let dataPoint + dataPoint = coordinateSystem.transformThisPoint(rawPoint) + dataPoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(dataPoint, thisObject.container) + let testPoint = { + x: dataPoint.x, + y: dataPoint.y + } + dataPoint = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(dataPoint) + dataPoint = thisObject.fitFunction(dataPoint) + + if (testPoint.x === dataPoint.x) { + dataPoint.isInViewPort = true + } else { + dataPoint.isInViewPort = false + } + /* Store the data point at the local map */ + dataPoint.rawPoint = rawPoint + dataPoints.set(point.id, dataPoint) + } + } + } + + /* Remember this datapoints, so that we do not need to calculate them again. */ + record.dataPoints = dataPoints + } + + /* Here we are going to plot all the polygons. */ + for (let j = 0; j < productDefinition.referenceParent.shapes.polygons.length; j++) { + let polygon = productDefinition.referenceParent.shapes.polygons[j] + /* We will check if we need to plot this Polygon or not. */ + if (polygon.polygonCondition !== undefined) { + let mustPlot = eval(polygon.polygonCondition.code) + if (mustPlot !== true) { continue } + } + + let calculatedStyle + + /* Finding out the fill style */ + let fillStyle = { // default values + opacity: 1, + paletteColor: UI_COLOR.BLACK + } + if (polygon.polygonBody !== undefined) { + if (record.fillStyle === undefined) { + record.fillStyle = new Map() + } + calculatedStyle = record.fillStyle.get(polygon.id) + if (calculatedStyle !== undefined) { + /* We use the already calculated style */ + fillStyle = calculatedStyle + } else { + /* Get the default style if exists */ + if (polygon.polygonBody.style !== undefined) { + if (polygon.polygonBody.style.config !== undefined) { + if (polygon.polygonBody.style.config.default !== undefined) { + let bodyStyle = polygon.polygonBody.style.config.default + if (bodyStyle.opacity !== undefined) { fillStyle.opacity = bodyStyle.opacity } + if (bodyStyle.paletteColor !== undefined) { fillStyle.paletteColor = eval(bodyStyle.paletteColor) } + } + } + } + /* Apply conditional styles */ + if (polygon.polygonBody.styleConditions !== undefined) { + for (let k = 0; k < polygon.polygonBody.styleConditions.length; k++) { + let condition = polygon.polygonBody.styleConditions[k] + let value = eval(condition.code) + if (value === true) { + if (condition.style !== undefined) { + let bodyStyle = condition.style.config + if (bodyStyle.opacity !== undefined) { fillStyle.opacity = bodyStyle.opacity } + if (bodyStyle.paletteColor !== undefined) { fillStyle.paletteColor = eval(bodyStyle.paletteColor) } + } + } + } + } + /* Get the atMousePosition style if exists */ + if (polygon.polygonBody.style !== undefined) { + if (polygon.polygonBody.style.config.atMousePosition !== undefined) { + let atMousePositionStyleDefinition = polygon.polygonBody.style.config.atMousePosition + let atMousePositionStyle = {} + if (atMousePositionStyleDefinition.opacity !== undefined) { atMousePositionStyle.opacity = atMousePositionStyleDefinition.opacity } + if (atMousePositionStyleDefinition.paletteColor !== undefined) { atMousePositionStyle.paletteColor = eval(atMousePositionStyleDefinition.paletteColor) } + atMousePositionFillStyles.set(polygon.id, atMousePositionStyle) + } + } + + record.fillStyle.set(polygon.id, fillStyle) + } + } + + /* Finding out the stroke style */ + let strokeStyle = { // default values + opacity: 1, + paletteColor: UI_COLOR.BLACK, + lineWidth: 1, + lineDash: [0, 0] + } + if (polygon.polygonBorder !== undefined) { + if (record.strokeStyle === undefined) { + record.strokeStyle = new Map() + } + calculatedStyle = record.strokeStyle.get(polygon.id) + if (calculatedStyle !== undefined) { + /* We use the already calculated style */ + strokeStyle = calculatedStyle + } else { + /* Get the default style if exists */ + if (polygon.polygonBorder.style !== undefined) { + if (polygon.polygonBorder.style.config !== undefined) { + if (polygon.polygonBorder.style.config.default !== undefined) { + let borderStyle = polygon.polygonBorder.style.config.default + if (borderStyle.opacity !== undefined) { strokeStyle.opacity = borderStyle.opacity } + if (borderStyle.paletteColor !== undefined) { strokeStyle.paletteColor = eval(borderStyle.paletteColor) } + if (borderStyle.lineWidth !== undefined) { strokeStyle.lineWidth = borderStyle.lineWidth } + if (borderStyle.lineDash !== undefined) { strokeStyle.lineDash = borderStyle.lineDash } + } + } + } + /* Apply conditional styles */ + if (polygon.polygonBorder.styleConditions !== undefined) { + for (let k = 0; k < polygon.polygonBorder.styleConditions.length; k++) { + let condition = polygon.polygonBorder.styleConditions[k] + let value = eval(condition.code) + if (value === true) { + if (condition.style !== undefined) { + let borderStyle = condition.style.config + if (borderStyle.opacity !== undefined) { strokeStyle.opacity = borderStyle.opacity } + if (borderStyle.paletteColor !== undefined) { strokeStyle.paletteColor = eval(borderStyle.paletteColor) } + if (borderStyle.lineWidth !== undefined) { strokeStyle.lineWidth = borderStyle.lineWidth } + if (borderStyle.lineDash !== undefined) { strokeStyle.lineDash = borderStyle.lineDash } + } + } + } + } + /* Get the atMousePosition style if exists */ + if (polygon.polygonBorder.style !== undefined) { + if (polygon.polygonBorder.style.config.atMousePosition !== undefined) { + let atMousePositionStyleDefinition = polygon.polygonBorder.style.config.atMousePosition + let atMousePositionStyle = {} + if (atMousePositionStyleDefinition.opacity !== undefined) { atMousePositionStyle.opacity = atMousePositionStyleDefinition.opacity } + if (atMousePositionStyleDefinition.paletteColor !== undefined) { atMousePositionStyle.paletteColor = eval(atMousePositionStyleDefinition.paletteColor) } + if (atMousePositionStyleDefinition.lineWidth !== undefined) { atMousePositionStyle.lineWidth = atMousePositionStyleDefinition.lineWidth } + if (atMousePositionStyleDefinition.lineDash !== undefined) { atMousePositionStyle.lineDash = atMousePositionStyleDefinition.lineDash } + atMousePositionStrokeStyles.set(polygon.id, atMousePositionStyle) + } + } + + record.strokeStyle.set(polygon.id, strokeStyle) + } + } + + /* Draw the Polygon */ + browserCanvasContext.beginPath() + for (let k = 0; k < polygon.polygonVertexes.length; k++) { + let polygonVertex = polygon.polygonVertexes[k] + if (polygonVertex.referenceParent !== undefined) { + let dataPointObject = dataPoints.get(polygonVertex.referenceParent.id) + if (dataPointObject === undefined) { + polygonVertex.payload.uiObject.setErrorMessage('Vertex not referencing any Point') + console.log('[WARN] You have a Polygon Vertex not referencing any Point.') + continue + } + + let dataPoint = { + x: dataPointObject.x, + y: dataPointObject.y + } + /* We make sure the points do not fall outside the viewport visible area. This step allways need to be done. */ + dataPoint = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(dataPoint) + dataPoint = thisObject.fitFunction(dataPoint) + /* + Contributing to Auto-Scale: A point will contribute to the y coordinate only if the x coordinate is plotted in the screen. + It can happen that objects that span through a long period of time could have its begin point inside the visible + viewport but not its end point. In those cases, the end point should not be reported. + */ + + if (dataPointObject.rawPoint.y > 1 && dataPointObject.rawPoint.y !== undefined && isNaN(dataPointObject.rawPoint.y) === false) { + let rawPoint = coordinateSystem.transformThisPoint(dataPointObject.rawPoint) + if (dataPointObject.isInViewPort === true) { + coordinateSystem.reportYValue(dataPointObject.rawPoint.y) + } + } + + if (k === 0) { + browserCanvasContext.moveTo(dataPoint.x, dataPoint.y) + } else { + browserCanvasContext.lineTo(dataPoint.x, dataPoint.y) + } + } + } + browserCanvasContext.closePath() + + /* Apply the fill style to the canvas object */ + if (polygon.polygonBody !== undefined) { + /* Replace the fill style if we are at mouse position */ + if (atMousePosition === true) { + let atMousePositionFillStyle = atMousePositionFillStyles.get(polygon.id) + if (atMousePositionFillStyle !== undefined) { + fillStyle = atMousePositionFillStyle + } + } + + browserCanvasContext.fillStyle = 'rgba(' + fillStyle.paletteColor + ', ' + fillStyle.opacity + ')' + browserCanvasContext.fill() + } + + /* Apply the stroke style to the canvas object */ + if (polygon.polygonBorder !== undefined) { + /* Replace the stroke style if we are at mouse position */ + if (atMousePosition === true) { + let atMousePositionStrokeStyle = atMousePositionStrokeStyles.get(polygon.id) + if (atMousePositionStrokeStyle !== undefined) { + strokeStyle = atMousePositionStrokeStyle + } + } + + browserCanvasContext.lineWidth = strokeStyle.lineWidth + browserCanvasContext.setLineDash(strokeStyle.lineDash) + browserCanvasContext.strokeStyle = 'rgba(' + strokeStyle.paletteColor + ', ' + strokeStyle.opacity + ')' + browserCanvasContext.stroke() + } + } + + /* Here we are going to plot images. */ + for (let j = 0; j < productDefinition.referenceParent.shapes.images.length; j++) { + let image = productDefinition.referenceParent.shapes.images[j] + if (image.imagePosition === undefined) { continue } + if (image.imagePosition.referenceParent === undefined) { continue } + if (image.imageCondition !== undefined) { + let mustPlot = eval(image.imageCondition.code) + if (mustPlot !== true) { continue } + } + let imageName = '' + let imageSize = 0 + let offsetX = 0 + let offsetY = 0 + let imagePosition = { x: 0, y: 0 } + if (image.config.codeName !== undefined) { imageName = image.config.codeName } + if (image.config.size !== undefined) { imageSize = image.config.size } + if (image.imagePosition.config.offsetX !== undefined) { offsetX = image.imagePosition.config.offsetX } + if (image.imagePosition.config.offsetY !== undefined) { offsetY = image.imagePosition.config.offsetY } + let dataPointObject = dataPoints.get(image.imagePosition.referenceParent.id) + if (dataPointObject === undefined) { continue } + let dataPoint = { + x: dataPointObject.x, + y: dataPointObject.y + } + dataPoint = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(dataPoint) + dataPoint = thisObject.fitFunction(dataPoint) + + imagePosition.x = dataPoint.x + imagePosition.y = dataPoint.y + let imageToDraw = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', imageName) + if (imageToDraw !== undefined) { + if (imageToDraw.canDrawIcon === true) { + browserCanvasContext.drawImage(imageToDraw, imagePosition.x - imageSize / 2 + offsetX, imagePosition.y - imageSize / 2 - offsetY, imageSize, imageSize) + } + } else { + console.log('Can not plot image named ' + imageName + ' of product ' + productDefinition.name + ' because it does not exist.') + } + } + + /* Here we are going to plot texts. */ + for (let j = 0; j < productDefinition.referenceParent.shapes.texts.length; j++) { + let text = productDefinition.referenceParent.shapes.texts[j] + if (text.textPosition === undefined) { continue } + if (text.textPosition.referenceParent === undefined) { continue } + if (text.textCondition !== undefined) { + let mustPlot = eval(text.textCondition.code) + if (mustPlot !== true) { continue } + } + if (text.textFormula === undefined) { continue } + if (text.textStyle === undefined) { continue } + let value = eval(text.textFormula.code) + let fontSize = 0 + let opacity = 0 + let paletteColor = UI_COLOR.GREY + let offsetX = 0 + let offsetY = 0 + let textPosition = { x: 0, y: 0 } + if (text.textStyle.config.fontSize !== undefined) { fontSize = text.textStyle.config.fontSize } + if (text.textStyle.config.opacity !== undefined) { opacity = text.textStyle.config.opacity } + if (text.textStyle.config.paletteColor !== undefined) { paletteColor = eval(text.textStyle.config.paletteColor) } + if (text.textPosition.config.offsetX !== undefined) { offsetX = text.textPosition.config.offsetX } + if (text.textPosition.config.offsetY !== undefined) { offsetY = text.textPosition.config.offsetY } + let dataPointObject = dataPoints.get(text.textPosition.referenceParent.id) + if (dataPointObject === undefined) { continue } + let dataPoint = { + x: dataPointObject.x, + y: dataPointObject.y + } + dataPoint = UI.projects.superalgos.spaces.chartingSpace.viewport.fitIntoVisibleArea(dataPoint) + dataPoint = thisObject.fitFunction(dataPoint) + + textPosition.x = dataPoint.x + textPosition.y = dataPoint.y + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + paletteColor + ', ' + opacity + ')' + browserCanvasContext.fillText(value, textPosition.x + offsetX, textPosition.y - offsetY) + } + } + + if (coordinateSystem.autoMinYScale === true || coordinateSystem.autoMaxYScale === true) { + mustRecalculateDataPoints = true + } else { + mustRecalculateDataPoints = false + } + + logged = false + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] plot -> err = ' + err.stack) } + } + } + + function onViewportZoomChanged(event) { + try { + mustRecalculateDataPoints = true + recalculate() + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onViewportZoomChanged -> err = ' + err.stack) } + } + } + + function onDragFinished() { + if (UI.projects.superalgos.spaces.chartingSpace.visible === true) { + recalculate() + } + } + + function onDisplace(event) { + recalculateAll(event) + } + + function onViewportPositionChanged(event) { + recalculateAll(event) + } + + function recalculateAll(event) { + mustRecalculateDataPoints = true + if (event !== undefined) { + if (event.recalculate === true) { + recalculate() + return + } + } + if (Math.random() * 100 > 95) { + recalculate() + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/RecordValues.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/RecordValues.js new file mode 100644 index 000000000..7633119b7 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Plotting/RecordValues.js @@ -0,0 +1,124 @@ +function newRecordValues() { + const MODULE_NAME = 'Record Values' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + onRecordChange: onRecordChange, + initialize: initialize, + finalize: finalize + } + + let tradingSystem + let tradingEngine + let productDefinition + let propertyTargetNodeMap = new Map() + return thisObject + + function finalize() { + tradingSystem = undefined + tradingEngine = undefined + productDefinition = undefined + propertyTargetNodeMap = undefined + } + + function initialize(pTradingSystem, pTradingEngine, pProductDefinition) { + tradingSystem = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pTradingSystem.id) + tradingEngine = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsById(pTradingEngine.id) + productDefinition = pProductDefinition + + /* + The product root can be a node or a node property of type array. + */ + let productRoot = eval(productDefinition.config.nodePath) + + if (productDefinition.config.nodePathType === 'array') { + /* + This means that the configured nodePath is not pointing to a node, but to a node property that is an array. + For that reason we will assume that each element of the array is a record to be outputed + */ + for (let index = 0; index < productRoot.length; index++) { + /* + The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values. + */ + let productRootNode = productRoot[index] + scanProperties(productDefinition, productRootNode, index) + } + } else { + /* + This means that the configured nodePath points to a single node, which is the one whose children constitutes + the record to be saved at the output file. + */ + /* + The Product Root Node is the root of the node hiriarchy from where we are going to extract the record values. + */ + let productRootNode = productRoot + scanProperties(productDefinition, productRootNode) + } + + function scanProperties(productDefinition, productRootNode, index) { + for (let i = 0; i < productDefinition.record.properties.length; i++) { + let property = productDefinition.record.properties[i] + let key = property.id + if (index !== undefined) { + key = property.id + '-' + index + } + if (property.config.nodePath !== undefined) { + let propertyRoot + try { + propertyRoot = eval(property.config.nodePath) + } catch(err) { + console.log('Definition Error at Product ' + productDefinition.name) + console.log('productDefinition.config = ' + JSON.stringify(productDefinition.config)) + console.log('Definition Error at Property ' + property.name) + console.log('property.config = ' + JSON.stringify(property.config)) + console.log('property.config.nodePath = ' + property.config.nodePath) + return + } + propertyTargetNodeMap.set(key, propertyRoot) + } else { + propertyTargetNodeMap.set(key, productRootNode) + } + } + } + } + + function onRecordChange(currentRecord) { + if (currentRecord === undefined) { return } + for (let i = 0; i < productDefinition.record.properties.length; i++) { + let property = productDefinition.record.properties[i] + let value = currentRecord[property.config.codeName] + let key = property.id + if (productDefinition.config.nodePathType === 'array') { + /* + When we have an array configuration, in this case we will use the index + property that was injected into the record in order to build the key to + get the node where we have to send the value. + */ + key = property.id + '-' + currentRecord.index + } + let targetNode = propertyTargetNodeMap.get(key) + /* + If the codeName of the Record Property can not match the name of the property at + the target node, the user can explicitly specify the property name at the configuration, + and in those cases we need to use that. This happens when there are many Record Properties + pointing to the same property at the Target Node. + */ + if (property.config.childProperty !== undefined) { + applyValue(targetNode[property.config.childProperty], value, property.config.decimals) + } else { + applyValue(targetNode[property.config.codeName], value, property.config.decimals) + } + } + } + + function applyValue(node, value, minDecimals) { + if (UI.projects.superalgos.spaces.chartingSpace.visible !== true) { return } + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.uiObject === undefined) { return } + if (value === true) { value = 'true' } + if (value === false) { value = 'false' } + node.payload.uiObject.setValue(value, 1, minDecimals) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/AutoScaleButton.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/AutoScaleButton.js new file mode 100644 index 000000000..9dc11eda2 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/AutoScaleButton.js @@ -0,0 +1,205 @@ +function newAutoScaleButton() { + let thisObject = { + container: undefined, + fitFunction: undefined, + parentContainer: undefined, + autoMinScale: undefined, + autoMaxScale: undefined, + setStatus: setStatus, + draw: draw, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + /* Cointainer stuff */ + thisObject.container = newContainer() + thisObject.container.name = 'Auto Scale Button' + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.frame.containerName = 'Auto Scale Button' + + const ICON_SIZE = 36 + let onMouseWheelEventSubscriptionId + let coordinateSystem + let axis + let currentStatus + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSubscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.parentContainer = undefined + thisObject.panels = undefined + + coordinateSystem = undefined + axis = undefined + } + + function initialize(pAxis, pCoordinateSystem) { + axis = pAxis + coordinateSystem = pCoordinateSystem + thisObject.autoMinScale = coordinateSystem.autoMinYScale + thisObject.autoMaxScale = coordinateSystem.autoMaxYScale + + switch (axis) { + case 'X': { + thisObject.container.frame.width = ICON_SIZE + thisObject.container.frame.height = ICON_SIZE + + let position = { + x: thisObject.container.parentContainer.frame.width * 7 / 8, + y: thisObject.container.parentContainer.frame.height / 2 + } + + thisObject.container.frame.position = position + break + } + case 'Y': { + thisObject.container.frame.width = ICON_SIZE + thisObject.container.frame.height = ICON_SIZE + + let position = { + x: thisObject.container.parentContainer.frame.width * 7 / 8, + y: thisObject.container.parentContainer.frame.height / 2 + } + + thisObject.container.frame.position = position + break + } + } + onMouseWheelEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + } + + function setStatus(autoMinScale, autoMaxScale) { + thisObject.autoMinScale = autoMinScale + thisObject.autoMaxScale = autoMaxScale + + if (thisObject.autoMinScale === false && thisObject.autoMaxScale === false) { + currentStatus = 1 + } + if (thisObject.autoMinScale === true && thisObject.autoMaxScale === false) { + currentStatus = 2 + } + if (thisObject.autoMinScale === false && thisObject.autoMaxScale === true) { + currentStatus = 4 + } + if (thisObject.autoMinScale === true && thisObject.autoMaxScale === true) { + currentStatus = 6 + } + } + function onMouseWheel(event) { + let delta = event.wheelDelta + if (delta < 0) { + delta = -1 + } else { + delta = 1 + } + currentStatus = currentStatus + delta + if (currentStatus === 7) { currentStatus = 1 } + if (currentStatus === 0) { currentStatus = 6 } + + switch (currentStatus) { + case 1: { + thisObject.autoMinScale = false + thisObject.autoMaxScale = false + update() + return + } + case 2: { + thisObject.autoMinScale = true + thisObject.autoMaxScale = false + update() + return + } + case 3: { + thisObject.autoMinScale = false + thisObject.autoMaxScale = false + update() + return + } + case 4: { + thisObject.autoMinScale = false + thisObject.autoMaxScale = true + update() + return + } + case 5: { + thisObject.autoMinScale = false + thisObject.autoMaxScale = false + update() + return + } + case 6: { + thisObject.autoMinScale = true + thisObject.autoMaxScale = true + update() + return + } + } + + function update() { + switch (axis) { + case 'X': { + coordinateSystem.autoMinXScale = thisObject.autoMinScale + coordinateSystem.autoMaxXScale = thisObject.autoMaxScale + break + } + case 'Y': { + coordinateSystem.autoMinYScale = thisObject.autoMinScale + coordinateSystem.autoMaxYScale = thisObject.autoMaxScale + break + } + } + + coordinateSystem.recalculateScale() + } + } + + function getContainer(point, purpose) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function draw() { + let icon + switch (axis) { + case 'X': { + if (thisObject.autoMinScale === false && thisObject.autoMaxScale === false) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-time-scale-manual') + } + if (thisObject.autoMinScale === true && thisObject.autoMaxScale === false) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-time-scale-auto-min') + } + if (thisObject.autoMinScale === false && thisObject.autoMaxScale === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-time-scale-auto-max') + } + if (thisObject.autoMinScale === true && thisObject.autoMaxScale === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-time-scale-auto-min-max') + } + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon, 0, 0, 0, 0, ICON_SIZE, thisObject.container) + break + } + case 'Y': { + if (thisObject.autoMinScale === false && thisObject.autoMaxScale === false) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-scale-manual') + } + if (thisObject.autoMinScale === true && thisObject.autoMaxScale === false) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-scale-auto-min') + } + if (thisObject.autoMinScale === false && thisObject.autoMaxScale === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-scale-auto-max') + } + if (thisObject.autoMinScale === true && thisObject.autoMaxScale === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-auto-scale-auto-min-max') + } + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon, 0, 0, 0, 0, ICON_SIZE, thisObject.container) + break + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/Commons.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/Commons.js new file mode 100644 index 000000000..22644f0e3 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/Commons.js @@ -0,0 +1,45 @@ + +function drawScaleDisplay(label1, label2, label3, xExtraOffSet1, xExtraOffSet2, xExtraOffSet3, icon1, icon2, container, backgroundColor) { + const RED_LINE_HIGHT = 4 + const OPACITY = 1 + + let params = { + cornerRadius: 5, + lineWidth: 1, + container: container, + borderColor: UI_COLOR.RUSTED_RED, + castShadow: false, + backgroundColor: backgroundColor, + opacity: OPACITY + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + + /* Place the Text */ + + label1 = label1.substring(0, 18) + label2 = label2.substring(0, 20) + label3 = label3.substring(0, 20) + + UI.projects.superalgos.utilities.drawPrint.drawLabel(label1, 1 / 2, 92 / 100, 0, 0, 9, container) + UI.projects.superalgos.utilities.drawPrint.drawLabel(label2, 1 / 2, 42 / 100, 0, 0, 17, container) + UI.projects.superalgos.utilities.drawPrint.drawLabel(label3, 1 / 2, 67 / 100, 0, 0, 9, container) + + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon1, 1 / 8, 1 / 2, 0, 0, 28, container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon2, 7 / 8, 1 / 2, 0, 0, 28, container) +} + +function drawScaleDisplayCover(container) { + let params = { + cornerRadius: 5, + lineWidth: 1, + container: container, + borderColor: UI_COLOR.WHITE, + castShadow: false, + backgroundColor: UI_COLOR.WHITE, + opacity: '0.25' + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/RateScale.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/RateScale.js new file mode 100644 index 000000000..d46769457 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/RateScale.js @@ -0,0 +1,571 @@ +function newRateScale() { + const MODULE_NAME = 'Right Scale' + + let thisObject = { + container: undefined, + rate: undefined, + fitFunction: undefined, + payload: undefined, + minValue: undefined, + maxValue: undefined, + isVisible: true, + layersOn: undefined, + onUpstreamScaleChanged: onUpstreamScaleChanged, + onMouseOverSomeTimeMachineContainer: onMouseOverSomeTimeMachineContainer, + physics: physics, + draw: draw, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isClickeable = false + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL + thisObject.container.frame.height = 40 + + let draggeableContainer = newContainer() + draggeableContainer.initialize(MODULE_NAME + ' Draggeable') + draggeableContainer.isDraggeable = true + + let visible = true + let isMouseOver + + let onMouseWheelEventSubscriptionId + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + let onScaleChangedEventSubscriptionId + let onDragStartedEventSubscriptionId + + let coordinateSystem + let coordinateSystemWhenDragStarted + + let limitingContainer + let rateCalculationsContainer + + mouseWhenDragStarted = { + position: { + x: 0, + y: 0 + } + } + + parentFrameWhenDragStarted = { + position: { + x: 0, + y: 0 + }, + width: 0, + height: 0 + } + + let scaleDisplayTimer = 0 + let autoScaleButton + + let wheelDeltaDirection + let wheelDeltaCounter = 0 + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + coordinateSystem.eventHandler.stopListening(onScaleChangedEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onDragStartedEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.payload = undefined + + if (coordinateSystemWhenDragStarted !== undefined) { + coordinateSystemWhenDragStarted.finalize() + } + + coordinateSystemWhenDragStarted = undefined + + coordinateSystem = undefined + limitingContainer = undefined + rateCalculationsContainer = undefined + + mouseWhenDragStarted = undefined + parentFrameWhenDragStarted = undefined + + autoScaleButton.finalize() + autoScaleButton = undefined + } + + function initialize(pCoordinateSystem, pLimitingContainer, pRateCalculationsContainer) { + coordinateSystem = pCoordinateSystem + limitingContainer = pLimitingContainer + rateCalculationsContainer = pRateCalculationsContainer + + thisObject.minValue = coordinateSystem.min.y + thisObject.maxValue = coordinateSystem.max.y + + onMouseWheelEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + onScaleChangedEventSubscriptionId = coordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + onDragStartedEventSubscriptionId = draggeableContainer.eventHandler.listenToEvent('onDragStarted', onDragStarted) + + autoScaleButton = newAutoScaleButton() + autoScaleButton.container.connectToParent(thisObject.container) + autoScaleButton.initialize('Y', coordinateSystem) + + readObjectState() + } + + function onMouseOverSomeTimeMachineContainer(event) { + if (event.containerId === undefined) { + /* This happens when the mouse over was not at the instance of a certain scale, but anywhere else. */ + visible = true + } else { + if (event.containerId === thisObject.container.id) { + visible = true + } else { + visible = false + turnOnCounter = 0 + } + } + } + + function onScaleChanged() { + saveObjectState() + } + + function onMouseOver(event) { + isMouseOver = true + event.containerId = thisObject.container.id + thisObject.container.eventHandler.raiseEvent('onMouseOverScale', event) + } + + function onMouseNotOver() { + isMouseOver = false + scaleDisplayTimer = 0 + } + + function onMouseWheel(event) { + if (IS_MAC) { + let sensitivity + if (event.wheelDelta < 0) { + if (event.shiftKey === true) { + sensitivity = 20 + } else { + sensitivity = 5 + } + if (wheelDeltaDirection === -1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = -1 + return + } + } else { + if (event.shiftKey === true) { + sensitivity = 20 + } else { + sensitivity = 5 + } + if (wheelDeltaDirection === 1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = 1 + return + } + } + } + + if (event.shiftKey === true) { + autoScaleButton.container.eventHandler.raiseEvent('onMouseWheel', event) + return + } + + let factor + let morePower = 10 + if (event.buttons === 4) { morePower = 1 } // Mouse wheel pressed. + + let delta = event.wheelDelta + if (delta < 0) { + factor = -0.01 * morePower + } else { + factor = 0.01 * morePower + } + + coordinateSystem.zoomY(factor, event, limitingContainer) + scaleDisplayTimer = 100 + } + + function onDragStarted(event) { + mouseWhenDragStarted = { + position: { + x: event.x, + y: event.y + } + } + + parentFrameWhenDragStarted = { + position: { + x: limitingContainer.frame.position.x, + y: limitingContainer.frame.position.y + }, + width: limitingContainer.frame.width, + height: limitingContainer.frame.height + } + + coordinateSystemWhenDragStarted = newCoordinateSystem() + + let minValue = { + x: coordinateSystem.min.x, + y: coordinateSystem.min.y + } + + let maxValue = { + x: coordinateSystem.max.x, + y: coordinateSystem.max.y + } + + coordinateSystemWhenDragStarted.initialize(minValue, maxValue, coordinateSystem.maxWidth, coordinateSystem.maxHeight) + } + + function getContainer(point, purpose) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + if (purpose === GET_CONTAINER_PURPOSE.DRAGGING) { + return draggeableContainer + } + + return thisObject.container + } + } + + function saveObjectState() { + try { + let config = JSON.parse(thisObject.payload.node.config) + config.minValue = coordinateSystem.min.y + config.maxValue = coordinateSystem.max.y + config.autoMinScale = coordinateSystem.autoMinYScale + config.autoMaxScale = coordinateSystem.autoMaxYScale + thisObject.payload.node.config = JSON.stringify(config, null, 4) + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function readObjectState() { + try { + let config = JSON.parse(thisObject.payload.node.config) + + if ( + (isNaN(config.minValue) || config.minValue === null || config.minValue === undefined) || + (isNaN(config.maxValue) || config.maxValue === null || config.maxValue === undefined) + ) { + // not using this value + } else { + if ( + thisObject.minValue !== config.minValue || + thisObject.maxValue !== config.maxValue || + coordinateSystem.autoMinYScale !== config.autoMinScale || + coordinateSystem.autoMaxYScale !== config.autoMaxScale + ) { + thisObject.minValue = config.minValue + thisObject.maxValue = config.maxValue + coordinateSystem.min.y = thisObject.minValue + coordinateSystem.max.y = thisObject.maxValue + + if (config.autoMinScale !== undefined && (config.autoMinScale === true || config.autoMinScale === false) && config.autoMaxScale !== undefined && (config.autoMaxScale === true || config.autoMaxScale === false)) { + coordinateSystem.autoMinYScale = config.autoMinScale + coordinateSystem.autoMaxYScale = config.autoMaxScale + autoScaleButton.setStatus(config.autoMinScale, config.autoMaxScale) + } + + coordinateSystem.recalculateScale() + } + } + saveObjectState() // this overrides any invalid value at the config. + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function physics() { + scaleDisplayTimer-- + readObjectState() + positioningPhysics() + draggingPhysics() + } + + function draggingPhysics() { + if (coordinateSystemWhenDragStarted === undefined) { return } + if (draggeableContainer.frame.position.y === 0) { return } + + let mouseNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(canvas.mouse.position) + let mouseWhenDragStartedNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(mouseWhenDragStarted.position) + + let dragVectorWhenDragStarted = { + x: 0, + y: mouseNoZoom.y - mouseWhenDragStartedNoZoom.y + } + + let dragVector = { + x: 0, + y: draggeableContainer.frame.position.y + } + + draggeableContainer.frame.position.y = 0 + + let point = { + x: 0, + y: (-dragVectorWhenDragStarted.y) + } + + let newMaxRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer(point, limitingContainer, coordinateSystemWhenDragStarted) + let yDifferenceMaxMin = coordinateSystemWhenDragStarted.max.y - coordinateSystemWhenDragStarted.min.y + + coordinateSystem.min.y = newMaxRate - yDifferenceMaxMin + coordinateSystem.max.y = newMaxRate + let event = { + type: 'center dragged', + dragVector: dragVector + } + coordinateSystem.recalculateScale() + } + + function onUpstreamScaleChanged(event) { + if (event === undefined) { return } + if (event.dragVector.y === 0) { return } + if (event.type === 'center dragged') { + let point = { + x: event.dragVector.x, + y: -event.dragVector.y + } + let newMaxRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer(point, rateCalculationsContainer, coordinateSystem) + let yDifferenceMaxMin = coordinateSystem.max.y - coordinateSystem.min.y + coordinateSystem.min.y = newMaxRate - yDifferenceMaxMin + coordinateSystem.max.y = newMaxRate + + coordinateSystem.recalculateScale() + } + if (event.type === 'top dragged') { + let point = { + x: event.dragVector.x, + y: event.dragVector.y + } + let newMaxRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer(point, rateCalculationsContainer, coordinateSystem) + + coordinateSystem.max.y = newMaxRate + coordinateSystem.maxHeight = rateCalculationsContainer.frame.height + coordinateSystem.recalculateScale() + } + if (event.type === 'bottom dragged') { + let point = { + x: event.dragVector.x, + y: event.dragVector.y + rateCalculationsContainer.frame.height + } + let newMinRate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtContainer(point, rateCalculationsContainer, coordinateSystem) + + coordinateSystem.min.y = newMinRate + coordinateSystem.maxHeight = rateCalculationsContainer.frame.height + coordinateSystem.recalculateScale() + } + } + + function positioningPhysics() { + /* Container Limits */ + + let upCorner = { + x: 0, + y: 0 + } + + let bottonCorner = { + x: limitingContainer.frame.width, + y: limitingContainer.frame.height + } + + upCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(upCorner, limitingContainer) + bottonCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(bottonCorner, limitingContainer) + + upCorner = limitingContainer.fitFunction(upCorner, true) + bottonCorner = limitingContainer.fitFunction(bottonCorner, true) + + /* We will check if we need to change the bottom because of being one of many scales of the same type */ + let displaceFactor = 0 + if (thisObject.payload.parentNode === undefined) { return } // This happens when in the process of deleting the scale, timeline chart or time machine. + if (thisObject.payload.parentNode.type === 'Timeline Chart') { + if (thisObject.payload.parentNode.payload.parentNode !== undefined) { + if (thisObject.payload.parentNode.payload.parentNode.rateScale !== undefined) { + if (thisObject.payload.parentNode.payload.parentNode.rateScale.payload.isVisible === true) { + displaceFactor++ + } + } + for (let i = 0; i < thisObject.payload.parentNode.payload.parentNode.timelineCharts.length; i++) { + let timelineChart = thisObject.payload.parentNode.payload.parentNode.timelineCharts[i] + if (timelineChart.rateScale !== undefined) { + if (thisObject.payload.node.id !== timelineChart.rateScale.id) { + if (timelineChart.rateScale.payload.isVisible === true) { + displaceFactor++ + } + } else { + break + } + } + } + } + } + bottonCorner.x = bottonCorner.x - thisObject.container.frame.width * displaceFactor + + /* Mouse Position Rate Calculation */ + let ratePoint = { + x: 0, + y: canvas.mouse.position.y + } + + thisObject.rate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtBrowserCanvas(ratePoint, rateCalculationsContainer, coordinateSystem) + + /* rateScale Positioning */ + ratePoint = { + x: limitingContainer.frame.width, + y: 0 + } + + ratePoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(ratePoint, limitingContainer.frame.container) + ratePoint.y = canvas.mouse.position.y - thisObject.container.frame.height / 2 + thisObject.container.frame.height + + /* Checking against the container limits. */ + if (ratePoint.x < upCorner.x) { ratePoint.x = upCorner.x } + if (ratePoint.x + thisObject.container.frame.width > bottonCorner.x) { ratePoint.x = bottonCorner.x } + if (ratePoint.y < upCorner.y + thisObject.container.frame.height) { ratePoint.y = upCorner.y + thisObject.container.frame.height } + if (ratePoint.y > bottonCorner.y) { ratePoint.y = bottonCorner.y } + + thisObject.container.frame.position.y = ratePoint.y - thisObject.container.frame.height + thisObject.container.frame.position.x = ratePoint.x - thisObject.container.frame.width + + thisObject.isVisible = true + thisObject.payload.isVisible = true + if (thisObject.container.frame.position.y + thisObject.container.frame.height * 2 > bottonCorner.y || + thisObject.container.frame.position.y - thisObject.container.frame.height * 1 < upCorner.y || + thisObject.container.frame.position.x < upCorner.x + ) { + thisObject.isVisible = false + thisObject.payload.isVisible = false + } + if (thisObject.layersOn === 0) { + thisObject.isVisible = false + thisObject.payload.isVisible = false + } + + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_DISPLAYING_SCALES) { + thisObject.isVisible = false + } + } + + function draw() { + drawScaleBox() + autoScaleButton.draw() + if (visible === false) { + drawScaleDisplayCover(thisObject.container) + } + } + + function drawForeground() { + if (isMouseOver === true) { + drawScaleBox() + autoScaleButton.draw() + } + } + + function drawBackground() { + drawScale() + } + + function drawScale() { + const SEPARATION = 50 + const NUMBER_OF_LABELS = Math.trunc(UI.projects.superalgos.spaces.chartingSpace.viewport.height / SEPARATION) + const FONT_SIZE = 15 + + for (let i = 0; i <= NUMBER_OF_LABELS; i++) { + let ratePoint1 = { + x: 0, + y: TOP_SPACE_HEIGHT + SEPARATION * (i + 1) + } + let ratePoint2 = { + x: 0, + y: TOP_SPACE_HEIGHT + SEPARATION * (i + 0) + } + + let rate = UI.projects.superalgos.utilities.dateRateTransformations.getRateFromPointAtBrowserCanvas(ratePoint1, rateCalculationsContainer, coordinateSystem) + let labels = scaleLabels(rate) + let decimalsDisplace = labels[1].length * FONT_SIZE * FONT_ASPECT_RATIO + + let fitPoint1 = thisObject.fitFunction(ratePoint1) + let fitPoint2 = thisObject.fitFunction(ratePoint2) + + if (fitPoint1.y === ratePoint1.y && fitPoint2.y === ratePoint2.y) { + UI.projects.superalgos.utilities.drawPrint.drawLabel(labels[1], 1 / 2, 0, 0, 0, FONT_SIZE, thisObject.container, UI_COLOR.GREY, undefined, ratePoint1.y) + UI.projects.superalgos.utilities.drawPrint.drawLabel(labels[2], 1 / 2, 0, decimalsDisplace, -5, 9, thisObject.container, UI_COLOR.GREY, undefined, ratePoint1.y) + } + } + } + + function drawScaleBox() { + if (thisObject.rate === undefined) { return } + if (thisObject.payload === undefined) { return } + if (thisObject.payload.node === undefined) { return } + + let icon1 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.payload.parentNode.project, thisObject.payload.node.payload.parentNode.type) + let icon2 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.payload.node.type) + + let backgroundColor = UI_COLOR.BLACK + let labels = scaleLabels(thisObject.rate) + + drawScaleDisplay(labels[0], labels[1], labels[2], 0, 0, 0, icon1, icon2, thisObject.container, backgroundColor) + } + + function scaleLabels(rate) { + if (rate < coordinateSystem.min.y) { + rate = coordinateSystem.min.y + } + if (rate > coordinateSystem.max.y) { + rate = coordinateSystem.max.y + } + + let label = dynamicDecimals(rate) + let labelArray = label.split('.') + let label1 = thisObject.payload.node.payload.parentNode.name + let label2 = (Math.trunc(rate)).toLocaleString() + let label3 = labelArray[1] + if (label3 === undefined) { label3 = '00' } + + if (rate < 1) { + label2 = label + label3 = '' + } + + if (scaleDisplayTimer > 0) { + label2 = (coordinateSystem.scale.y * 10000).toFixed(2) + label3 = 'SCALE' + } + + return [label1, label2, label3] + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/TimeFrameScale.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/TimeFrameScale.js new file mode 100644 index 000000000..9230541c5 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/TimeFrameScale.js @@ -0,0 +1,393 @@ +function newTimeFrameScale() { + const MODULE_NAME = 'Time Frame Scale' + + let thisObject = { + timeFrame: undefined, + container: undefined, + fitFunction: undefined, + payload: undefined, + isVisible: true, + layersOn: undefined, + adjustTimeFrame: adjustTimeFrame, + onMouseOverSomeTimeMachineContainer: onMouseOverSomeTimeMachineContainer, + draw: draw, + drawForeground: drawForeground, + physics: physics, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + const FILES_PERIOD_DEFAULT_VALUE = 0 + const TIME_PERIOD_DEFAULT_VALUE = 0 + + let displayingRealTimeFrame = true + let greyOutCoverLayer = false + let isMouseOver + + let filePeriodIndex = FILES_PERIOD_DEFAULT_VALUE + let timeFrameIndex = TIME_PERIOD_DEFAULT_VALUE + let timeFramesMasterArray = [marketFilesPeriods, dailyFilePeriods] + let timeFrameLabel = '24-hs' + + let onMouseWheelEventSubscriptionId + let onViewportZoomChangedEventSubscriptionId + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + + let limitingContainer + + setupContainer() + + let wheelDeltaDirection + let wheelDeltaCounter = 0 + + return thisObject + + function setupContainer() { + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isClickeable = false + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL + thisObject.container.frame.height = 40 + } + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSubscriptionId) + UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.stopListening(onViewportZoomChangedEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.payload = undefined + + limitingContainer = undefined + } + + function initialize(pLimitingContainer) { + limitingContainer = pLimitingContainer + + readObjectState() + newTimeFrame() + + onMouseWheelEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + onViewportZoomChangedEventSubscriptionId = UI.projects.superalgos.spaces.chartingSpace.viewport.eventHandler.listenToEvent('Zoom Changed', onViewportZoomChanged) + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + } + + function onMouseOverSomeTimeMachineContainer(event) { + if (event.containerId === undefined) { + /* This happens when the mouse over was not at the instance of a certain scale, but anywhere else. */ + greyOutCoverLayer = false + } else { + if (event.containerId === thisObject.container.id) { + greyOutCoverLayer = false + } else { + greyOutCoverLayer = true + turnOnCounter = 0 + } + } + } + + function onMouseOver(event) { + isMouseOver = true + event.containerId = thisObject.container.id + thisObject.container.eventHandler.raiseEvent('onMouseOverScale', event) + } + + function onMouseNotOver() { + isMouseOver = false + } + + function physics() { + readObjectState() + positioningPhysics() + } + + function adjustTimeFrame(elementsPlotted) { + const ELEMENTS_PLOTTED_THREASHOLD = 500 + if ( + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel >= UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_CHANGING_TIME_FRAME && + displayingRealTimeFrame === false + ) { + let event = {} + event.timeFrame = thisObject.timeFrame + thisObject.container.eventHandler.raiseEvent('Time Frame Value Changed', event) + displayingRealTimeFrame = true + } + if ( + elementsPlotted > ELEMENTS_PLOTTED_THREASHOLD && + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_CHANGING_TIME_FRAME && + displayingRealTimeFrame === true + ) { + let event = {} + event.timeFrame = ONE_DAY_IN_MILISECONDS + thisObject.container.eventHandler.raiseEvent('Time Frame Value Changed', event) + displayingRealTimeFrame = false + } + } + + function positioningPhysics() { + /* Container Limits */ + + let upCorner = { + x: 0, + y: 0 + } + + let bottonCorner = { + x: limitingContainer.frame.width, + y: limitingContainer.frame.height + } + + upCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(upCorner, limitingContainer) + bottonCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(bottonCorner, limitingContainer) + + upCorner = limitingContainer.fitFunction(upCorner, true) + bottonCorner = limitingContainer.fitFunction(bottonCorner, true) + + /* We will check if we need to change the bottom because of being one of many scales of the same type */ + let displaceFactor = 0 + if (thisObject.payload.parentNode === undefined) { return } // This happens when in the process of deleting the scale, timeline chart or time machine. + if (thisObject.payload.parentNode.type === 'Timeline Chart') { + if (thisObject.payload.parentNode.payload.parentNode !== undefined) { + if (thisObject.payload.parentNode.payload.parentNode.timeFrameScale !== undefined) { + if (thisObject.payload.parentNode.payload.parentNode.timeFrameScale.payload.isVisible === true) { + displaceFactor++ + } + } + for (let i = 0; i < thisObject.payload.parentNode.payload.parentNode.timelineCharts.length; i++) { + let timelineChart = thisObject.payload.parentNode.payload.parentNode.timelineCharts[i] + if (timelineChart.timeFrameScale !== undefined) { + if (thisObject.payload.node.id !== timelineChart.timeFrameScale.id) { + if (timelineChart.timeFrameScale.payload.isVisible === true) { + displaceFactor++ + } + } else { + break + } + } + } + } + } + bottonCorner.y = bottonCorner.y - thisObject.container.frame.height * displaceFactor + + /* Positioning */ + timePoint = { + x: 0, + y: limitingContainer.frame.height + } + + timePoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(timePoint, limitingContainer.frame.container) + timePoint.x = canvas.mouse.position.x - thisObject.container.frame.width / 2 + + /* Checking against the container limits. */ + if (timePoint.x < upCorner.x) { timePoint.x = upCorner.x } + if (timePoint.x + thisObject.container.frame.width > bottonCorner.x) { timePoint.x = bottonCorner.x - thisObject.container.frame.width } + if (timePoint.y < upCorner.y + thisObject.container.frame.height) { timePoint.y = upCorner.y + thisObject.container.frame.height } + if (timePoint.y > bottonCorner.y) { timePoint.y = bottonCorner.y } + + thisObject.container.frame.position.x = timePoint.x + thisObject.container.frame.position.y = timePoint.y - thisObject.container.frame.height + + thisObject.isVisible = true + thisObject.payload.isVisible = true + if (thisObject.container.frame.position.y + thisObject.container.frame.height > bottonCorner.y || + thisObject.container.frame.position.y - thisObject.container.frame.height * 3 < upCorner.y || + thisObject.container.frame.position.x < upCorner.x) { + thisObject.isVisible = false + thisObject.payload.isVisible = false + } + if (thisObject.layersOn === 0) { + thisObject.isVisible = false + thisObject.payload.isVisible = false + } + + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_DISPLAYING_SCALES) { + thisObject.isVisible = false + } + } + + function onViewportZoomChanged(event) { + if (event !== undefined) { // it is undefined when the level is just being animated. + if (event.shiftKey === false || event.shiftKey === undefined) { return } // with no shft key pressed, no automatic change in scale + let currentTimeFrame = thisObject.timeFrame + let timeFrame = recalculatePeriod(event.newLevel) + if (timeFrame !== currentTimeFrame) { + for (let i = 0; i < timeFramesMasterArray.length; i++) { + let timeFrameArray = timeFramesMasterArray[i] + for (let j = 0; j < timeFrameArray.length; j++) { + let record = timeFrameArray[j] + if (timeFrame === record[0]) { + filePeriodIndex = i + timeFrameIndex = j + } + } + } + newTimeFrame() + saveObjectState() + } + } + } + + function onMouseWheel(event) { + if (IS_MAC) { + let sensitivity + if (event.wheelDelta < 0) { + if (event.shiftKey === true) { + sensitivity = 20 + } else { + sensitivity = 5 + } + if (wheelDeltaDirection === -1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = -1 + return + } + } else { + if (event.shiftKey === true) { + sensitivity = 20 + } else { + sensitivity = 5 + } + if (wheelDeltaDirection === 1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = 1 + return + } + } + } + + let delta = event.wheelDelta + if (delta < 0) { + timeFrameIndex-- + if (timeFrameIndex < 0) { + filePeriodIndex-- + if (filePeriodIndex < 0) { + filePeriodIndex = 0 + timeFrameIndex = 0 + } else { + timeFrameIndex = timeFramesMasterArray[filePeriodIndex].length - 1 + } + } + } else { + timeFrameIndex++ + if (timeFrameIndex > timeFramesMasterArray[filePeriodIndex].length - 1) { + filePeriodIndex++ + if (filePeriodIndex > timeFramesMasterArray.length - 1) { + filePeriodIndex = timeFramesMasterArray.length - 1 + timeFrameIndex = timeFramesMasterArray[filePeriodIndex].length - 1 + } else { + timeFrameIndex = 0 + } + } + } + + newTimeFrame() + saveObjectState() + } + + function getContainer(point) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function saveObjectState() { + try { + let config = JSON.parse(thisObject.payload.node.config) + config.value = timeFrameLabel + thisObject.payload.node.config = JSON.stringify(config) + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function readObjectState() { + try { + let config = JSON.parse(thisObject.payload.node.config) + if (config.value !== timeFrameLabel) { + let found = false + for (let i = 0; i < timeFramesMasterArray.length; i++) { + let timeFrameArray = timeFramesMasterArray[i] + for (let j = 0; j < timeFrameArray.length; j++) { + let record = timeFrameArray[j] + if (config.value === record[1]) { + filePeriodIndex = i + timeFrameIndex = j + found = true + } + } + } + if (found === true) { + newTimeFrame() + } else { + saveObjectState() + } + } + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function newTimeFrame() { + let timeFrameArray = timeFramesMasterArray[filePeriodIndex] + thisObject.timeFrame = timeFrameArray[timeFrameIndex][0] + timeFrameLabel = timeFrameArray[timeFrameIndex][1] + // console.log('NEW TIME FRAME LABEL', timeFrameLabel) + let event = {} + event.timeFrame = thisObject.timeFrame + thisObject.container.eventHandler.raiseEvent('Time Frame Value Changed', event) + } + + function draw() { + drawScaleBox() + if (greyOutCoverLayer === true) { + drawScaleDisplayCover(thisObject.container) + } + } + + function drawForeground() { + if (isMouseOver === true) { + drawScaleBox() + } + } + + function drawScaleBox() { + if (timeFrameLabel === undefined) { return } + + let label = timeFrameLabel.split('-') + let label1 = thisObject.payload.node.payload.parentNode.name + let label2 = label[0] + let label3 = label[1].toUpperCase() + + let icon1 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.payload.parentNode.project, thisObject.payload.node.payload.parentNode.type) + let icon2 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.payload.node.type) + + let backgroundColor = UI_COLOR.BLACK + + drawScaleDisplay(label1, label2, label3, 0, 0, 0, icon1, icon2, thisObject.container, backgroundColor) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/TimeScale.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/TimeScale.js new file mode 100644 index 000000000..267470e55 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Scales/TimeScale.js @@ -0,0 +1,364 @@ +function newTimeScale() { + const MODULE_NAME = 'Time Scale' + + let thisObject = { + container: undefined, + date: undefined, + fitFunction: undefined, + payload: undefined, + isVisible: true, + fromDate: undefined, + toDate: undefined, + onMouseOverSomeTimeMachineContainer: onMouseOverSomeTimeMachineContainer, + physics: physics, + draw: draw, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isClickeable = false + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL + thisObject.container.frame.height = 40 + + let visible = true + let autoScaleButton + let isMouseOver + + let onMouseWheelEventSubscriptionId + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + let onScaleChangedEventSubscriptionId + + let coordinateSystem + let limitingContainer + + let wheelDeltaDirection + let wheelDeltaCounter = 0 + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + coordinateSystem.eventHandler.stopListening(onScaleChangedEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.payload = undefined + + coordinateSystem = undefined + limitingContainer = undefined + + autoScaleButton.finalize() + autoScaleButton = undefined + } + + function initialize(pCoordinateSystem, pLimitingContainer) { + coordinateSystem = pCoordinateSystem + limitingContainer = pLimitingContainer + + onMouseWheelEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + onScaleChangedEventSubscriptionId = coordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + + autoScaleButton = newAutoScaleButton() + autoScaleButton.container.connectToParent(thisObject.container) + autoScaleButton.initialize('X', coordinateSystem) + + readObjectState() + } + + function onMouseOverSomeTimeMachineContainer(event) { + if (event.containerId === undefined) { + /* This happens when the mouse over was not at the instance of a certain scale, but anywhere else. */ + visible = true + } else { + if (event.containerId === thisObject.container.id) { + visible = true + } else { + visible = false + turnOnCounter = 0 + } + } + } + + function onMouseOver(event) { + isMouseOver = true + event.containerId = thisObject.container.id + thisObject.container.eventHandler.raiseEvent('onMouseOverScale', event) + } + + function onMouseNotOver() { + isMouseOver = false + } + + function onMouseWheel(event) { + if (IS_MAC) { + let sensitivity + if (event.wheelDelta < 0) { + if (event.shiftKey === true) { + sensitivity = 20 + } else { + sensitivity = 5 + } + if (wheelDeltaDirection === -1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = -1 + return + } + } else { + if (event.shiftKey === true) { + sensitivity = 20 + } else { + sensitivity = 5 + } + if (wheelDeltaDirection === 1) { + wheelDeltaCounter++ + if (wheelDeltaCounter < sensitivity) { + return + } else { + wheelDeltaCounter = 0 + } + } else { + wheelDeltaCounter = 0 + wheelDeltaDirection = 1 + return + } + } + } + + if (event.shiftKey === true) { + autoScaleButton.container.eventHandler.raiseEvent('onMouseWheel', event) + return + } + let factor + let morePower = 10 + if (event.buttons === 4) { morePower = 1 } // Mouse wheel pressed. + + let delta = event.wheelDelta + if (delta < 0) { + factor = -0.01 * morePower + } else { + factor = 0.01 * morePower + } + + coordinateSystem.zoomX(factor, event, limitingContainer, MODULE_NAME) + } + + function onScaleChanged() { + saveObjectState() + } + + function getContainer(point, purpose) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function saveObjectState() { + try { + let config = JSON.parse(thisObject.payload.node.config) + config.fromDate = (new Date(coordinateSystem.min.x)).toISOString() + config.toDate = (new Date(coordinateSystem.max.x)).toISOString() + config.autoMinScale = coordinateSystem.autoMinXScale + config.autoMaxScale = coordinateSystem.autoMaxXScale + thisObject.payload.node.config = JSON.stringify(config, null, 4) + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function readObjectState() { + try { + let config = JSON.parse(thisObject.payload.node.config) + + if ( + (isNaN(Date.parse(config.fromDate)) || config.fromDate === null || config.fromDate === undefined) || + (isNaN(Date.parse(config.toDate)) || config.toDate === null || config.toDate === undefined) + ) { + // not using this value + } else { + if (thisObject.fromDate !== Date.parse(config.fromDate) || thisObject.toDate !== Date.parse(config.toDate)) { + thisObject.fromDate = Date.parse(config.fromDate) + thisObject.toDate = Date.parse(config.toDate) + coordinateSystem.min.x = thisObject.fromDate + coordinateSystem.max.x = thisObject.toDate + + if (config.autoMinScale !== undefined && (config.autoMinScale === true || config.autoMinScale === false) && config.autoMaxScale !== undefined && (config.autoMaxScale === true || config.autoMaxScale === false)) { + coordinateSystem.autoMinXScale = config.autoMinScale + coordinateSystem.autoMaxXScale = config.autoMaxScale + autoScaleButton.setStatus(config.autoMinScale, config.autoMaxScale) + } + coordinateSystem.recalculateScale() + } + } + saveObjectState() // this overrides any invalid value at the config. + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function physics() { + readObjectState() + positioningPhysics() + } + + function positioningPhysics() { + /* Container Limits */ + + let upCorner = { + x: 0, + y: 0 + } + + let bottonCorner = { + x: limitingContainer.frame.width, + y: limitingContainer.frame.height + } + + upCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(upCorner, limitingContainer) + bottonCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(bottonCorner, limitingContainer) + + upCorner = limitingContainer.fitFunction(upCorner, true) + bottonCorner = limitingContainer.fitFunction(bottonCorner, true) + + /* Mouse Position Date Calculation */ + let timePoint = { + x: canvas.mouse.position.x, + y: 0 + } + + let mouseDate = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(timePoint, limitingContainer, coordinateSystem) + + thisObject.date = new Date(mouseDate) + + /* timeScale Positioning */ + timePoint = { + x: 0, + y: 0 + } + + timePoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(timePoint, limitingContainer.frame.container) + timePoint.x = canvas.mouse.position.x - thisObject.container.frame.width / 2 + + /* Checking against the container limits. */ + if (timePoint.x < upCorner.x) { timePoint.x = upCorner.x } + if (timePoint.x + thisObject.container.frame.width > bottonCorner.x) { timePoint.x = bottonCorner.x - thisObject.container.frame.width } + if (timePoint.y < upCorner.y) { timePoint.y = upCorner.y } + if (timePoint.y + thisObject.container.frame.height > bottonCorner.y) { timePoint.y = bottonCorner.y - thisObject.container.frame.height } + + thisObject.container.frame.position.x = timePoint.x + thisObject.container.frame.position.y = timePoint.y + + thisObject.isVisible = true + if (thisObject.container.frame.position.y + thisObject.container.frame.height * 4 > bottonCorner.y || + thisObject.container.frame.position.y < upCorner.y || + thisObject.container.frame.position.x < upCorner.x) { + thisObject.isVisible = false + } + + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_DISPLAYING_SCALES) { + thisObject.isVisible = false + } + } + + function drawBackground() { + drawScale() + } + + function drawScale() { + const SEPARATION = 150 + const NUMBER_OF_LABELS = Math.trunc(UI.projects.superalgos.spaces.chartingSpace.viewport.width / SEPARATION) + const FONT_SIZE = 15 + + for (let i = 0; i <= NUMBER_OF_LABELS; i++) { + let timePoint = { + x: SEPARATION * (i + 1), + y: 0 + } + + let time = UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtBrowserCanvas(timePoint, limitingContainer, coordinateSystem) + let labels = scaleLabels(time, true) + let labelDisplace = labels[1].length * FONT_SIZE * FONT_ASPECT_RATIO + + let timePoint1 = { + x: SEPARATION * (i + 1) - labelDisplace, + y: 0 + } + let timePoint2 = { + x: SEPARATION * (i + 1) + labelDisplace, + y: 0 + } + + let fitPoint1 = thisObject.fitFunction(timePoint1) + let fitPoint2 = thisObject.fitFunction(timePoint2) + + if (fitPoint1.x === timePoint1.x && fitPoint2.x === timePoint2.x) { + UI.projects.superalgos.utilities.drawPrint.drawLabel(labels[1], 1 / 2, 0, 18, 17, FONT_SIZE, thisObject.container, UI_COLOR.GREY, timePoint1.x, undefined) + UI.projects.superalgos.utilities.drawPrint.drawLabel(labels[2], 1 / 2, 0, 18, 30, 12, thisObject.container, UI_COLOR.GREY, timePoint1.x, undefined) + } + } + } + + function draw() { + drawScaleBox() + autoScaleButton.draw() + if (visible === false) { + drawScaleDisplayCover(thisObject.container) + } + } + + function drawForeground() { + if (isMouseOver === true) { + drawScaleBox() + autoScaleButton.draw() + } + } + + function drawScaleBox() { + if (thisObject.date === undefined) { return } + + let icon1 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.payload.parentNode.project, thisObject.payload.node.payload.parentNode.type) + let icon2 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.payload.node.type) + + let backgroundColor = UI_COLOR.BLACK + let labels = scaleLabels(thisObject.date) + + drawScaleDisplay(labels[0], labels[1], labels[2], 0, 0, 0, icon1, icon2, thisObject.container, backgroundColor) + } + + function scaleLabels(date, excludeYear) { + let label = date.toUTCString() + let labelArray = label.split(' ') + let label1 = thisObject.payload.node.payload.parentNode.name + let label2 = labelArray[1] + ' ' + labelArray[2] + ' ' + labelArray[3] + let label3 = labelArray[4] + + if (excludeYear === true) { + label2 = labelArray[1] + ' ' + labelArray[2] + } + + return [label1, label2, label3] + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/TimeMachine.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/TimeMachine.js new file mode 100644 index 000000000..20208aa97 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/TimeMachine.js @@ -0,0 +1,835 @@ +/* + +Markets are a function of time. When watching them, end users must be positioned at one particular point in time. The system currently allows users +to position themselves at any time they like. + +In the future, it will be usefull to explore markets and compare them at different times simultaneously. Anticipating that future this module exists. +All the timelineCharts that depand on a datetime are children of this object Time Machine. In the future we will allow users to have more than one Time Machine, +each one with it own timelineCharts, and each one positioned at an especific point in titme. + +*/ + +function newTimeMachine() { + const MODULE_NAME = 'Time Machine' + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let timeFrame = INITIAL_TIME_PERIOD + + let thisObject = { + container: undefined, + timeScale: undefined, + rateScale: undefined, + payload: undefined, + edgeEditor: undefined, + onFocus: undefined, + onKeyPressed: onKeyPressed, + timelineCharts: [], + fitFunction: fitFunction, + physics: physics, + draw: draw, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + let timeMachineCoordinateSystem = newCoordinateSystem() + timeMachineCoordinateSystem.name = 'TIME MACHINE' + + let mouse = { + position: { + x: 0, + y: 0 + } + } + + let tabAnimation = 0 + let tabAnimationStatus = 'Close' + let tabAnimationCounter = 0 + let wasOnFocus = false + let drawScales = false + let syncWithDesignerLoop = 0 + let timelineChartsMap = new Map() + + let onMouseOverEventSuscriptionId + let onMouseNotOverEventSuscriptionId + let timeScaleMouseOverEventSuscriptionId + let rateScaleMouseOverEventSuscriptionId + let timeFrameScaleEventSuscriptionId + let timeFrameScaleMouseOverEventSuscriptionId + let onScaleChangedEventSubscriptionId + + setupContainer() + return thisObject + + function setupContainer() { + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.fitFunction = thisObject.fitFunction + thisObject.container.isDraggeable = false + thisObject.container.insideViewport = true + thisObject.container.detectMouseOver = true + + thisObject.container.frame.width = browserCanvas.width / TIME_MACHINE_WIDTH + thisObject.container.frame.height = (browserCanvas.height - TOP_SPACE_HEIGHT - COCKPIT_SPACE_HEIGHT) / TIME_MACHINE_HEIGHT + } + + function finalize() { + timeMachineCoordinateSystem.eventHandler.stopListening(onScaleChangedEventSubscriptionId) + + if (thisObject.timeScale !== undefined) { + finalizeTimeScale() + } + + if (thisObject.rateScale !== undefined) { + finalizeRateScale() + } + + if (thisObject.timeFrameScale !== undefined) { + finalizeTimeFrameScale() + } + + thisObject.container.eventHandler.stopListening(onMouseOverEventSuscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSuscriptionId) + + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[i] + timelineChart.container.eventHandler.stopListening(timelineChart.onChildrenMouseOverEventSuscriptionId) + timelineChart.finalize() + } + + thisObject.timelineCharts = undefined + thisObject.fitFunction = undefined + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.edgeEditor = undefined + + mouse = undefined + timelineChartsMap = undefined + + timeMachineCoordinateSystem.finalize() + timeMachineCoordinateSystem = undefined + } + + function finalizeTimeScale() { + if (thisObject.timeScale === undefined) { return } + + thisObject.timeScale.container.eventHandler.stopListening(timeScaleMouseOverEventSuscriptionId) + thisObject.timeScale.finalize() + thisObject.timeScale = undefined + } + + function finalizeRateScale() { + if (thisObject.rateScale === undefined) { return } + + thisObject.rateScale.container.eventHandler.stopListening(rateScaleMouseOverEventSuscriptionId) + thisObject.rateScale.finalize() + thisObject.rateScale = undefined + } + + function finalizeTimeFrameScale() { + if (thisObject.timeFrameScale === undefined) { return } + + thisObject.timeFrameScale.container.eventHandler.stopListening(timeFrameScaleEventSuscriptionId) + thisObject.timeFrameScale.container.eventHandler.stopListening(timeFrameScaleMouseOverEventSuscriptionId) + thisObject.timeFrameScale.finalize() + thisObject.timeFrameScale = undefined + } + + function initialize(callBackFunction) { + timeFrame = INITIAL_TIME_PERIOD + UI.projects.superalgos.utilities.loadSaveFrame.loadFrame(thisObject.payload, thisObject.container.frame) + setInitialPosition() + UI.projects.superalgos.utilities.loadSaveFrame.saveFrame(thisObject.payload, thisObject.container.frame) + + recalculateCoordinateSystem() + recalculateCurrentDatetime() + + onMouseOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + onScaleChangedEventSubscriptionId = timeMachineCoordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + + thisObject.edgeEditor = newEdgeEditor() + thisObject.edgeEditor.initialize(timeMachineCoordinateSystem) + thisObject.edgeEditor.container.connectToParent(thisObject.container, true, true, false, true, true, true) + + callBackFunction() + } + + function setInitialPosition() { + + if (thisObject.container.frame.position.x === 0 && thisObject.container.frame.position.y === 0) { + let timeMachineIndex = UI.projects.superalgos.utilities.children.findChildIndexAtParentNode(thisObject.payload.node) + let dashboardIndex = UI.projects.superalgos.utilities.children.findChildIndexAtParentNode(thisObject.payload.parentNode) + + thisObject.container.frame.position.x = thisObject.container.frame.width * timeMachineIndex * 2 + thisObject.container.frame.position.y = thisObject.container.frame.height * dashboardIndex * 2 + } + } + + function onKeyPressed(event) { + thisObject.edgeEditor.onKeyPressed(event) + } + + function onMouseOver(event) { + drawScales = true + + mouse.position.x = event.x + mouse.position.y = event.y + + if (thisObject.timeScale !== undefined) { + thisObject.timeScale.onMouseOverSomeTimeMachineContainer(event) + } + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.onMouseOverSomeTimeMachineContainer(event) + } + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.onMouseOverSomeTimeMachineContainer(event) + } + } + + function onMouseNotOver(event) { + drawScales = false + + if (thisObject.timeScale !== undefined) { + thisObject.timeScale.visible = false + } + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.visible = false + } + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.visible = false + } + } + + function initializeTimeScale() { + thisObject.timeScale = newTimeScale() + thisObject.timeScale.fitFunction = thisObject.fitFunction + thisObject.timeScale.payload = thisObject.payload.node.timeScale.payload + + timeScaleMouseOverEventSuscriptionId = thisObject.timeScale.container.eventHandler.listenToEvent('onMouseOverScale', timeScaleMouseOver) + thisObject.timeScale.initialize(timeMachineCoordinateSystem, thisObject.container) + + function timeScaleMouseOver(event) { + thisObject.container.eventHandler.raiseEvent('onMouseOver', event) + } + } + + function initializeRateScale() { + thisObject.rateScale = newRateScale() + thisObject.rateScale.fitFunction = thisObject.fitFunction + thisObject.rateScale.payload = thisObject.payload.node.rateScale.payload + + rateScaleMouseOverEventSuscriptionId = thisObject.rateScale.container.eventHandler.listenToEvent('onMouseOverScale', rateScaleMouseOver) + thisObject.rateScale.initialize(timeMachineCoordinateSystem, thisObject.container, thisObject.container) + + function rateScaleMouseOver(event) { + thisObject.container.eventHandler.raiseEvent('onMouseOver', event) + } + } + + function initializeTimeFrameScale() { + thisObject.timeFrameScale = newTimeFrameScale() + thisObject.timeFrameScale.fitFunction = thisObject.fitFunction + thisObject.timeFrameScale.payload = thisObject.payload.node.timeFrameScale.payload + + timeFrameScaleEventSuscriptionId = thisObject.timeFrameScale.container.eventHandler.listenToEvent('Time Frame Value Changed', timeFrameScaleValueChanged) + timeFrameScaleMouseOverEventSuscriptionId = thisObject.timeFrameScale.container.eventHandler.listenToEvent('onMouseOverScale', timeFrameScaleMouseOver) + thisObject.timeFrameScale.initialize(thisObject.container) + + function timeFrameScaleValueChanged(event) { + let currentTimeFrame = timeFrame + timeFrame = event.timeFrame + if (timeFrame !== currentTimeFrame) { + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[i] + timelineChart.upstreamTimeFrame = timeFrame + if (timelineChart.timeFrameScale === undefined) { + timelineChart.setTimeFrame(timeFrame) + } + } + } + } + function timeFrameScaleMouseOver(event) { + thisObject.container.eventHandler.raiseEvent('onMouseOver', event) + } + } + + function getContainer(point, purpose) { + let container + + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { + container = thisObject.rateScale.getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + return container + } else { + if (purpose === GET_CONTAINER_PURPOSE.DRAGGING && container.isClickeable === true) { + return container + } + } + } + } + + if (thisObject.timeScale !== undefined && thisObject.timeScale.isVisible === true) { + container = thisObject.timeScale.getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + return container + } else { + if (purpose === GET_CONTAINER_PURPOSE.DRAGGING && container.isClickeable === true) { + return container + } + } + } + } + + if (thisObject.timeFrameScale !== undefined && thisObject.timeFrameScale.isVisible === true) { + container = thisObject.timeFrameScale.getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + return container + } + } + } + + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + container = thisObject.timelineCharts[i].getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + if (thisObject.container.frame.isThisPointHere(point) === true) { + return container + } + } else { + if (purpose === GET_CONTAINER_PURPOSE.DRAGGING && container.isClickeable === true) { + if (thisObject.container.frame.isThisPointHere(point) === true) { + return container + } + } + } + } + } + + container = thisObject.edgeEditor.getContainer(point, purpose) + if (container !== undefined) { + return container + } else { + if (thisObject.container.isForThisPurpose(purpose)) { + if (thisObject.container.frame.isThisPointHere(point) === true) { + return thisObject.container + } + } + } + } + + function onScaleChanged() { + recalculateCurrentDatetime() + } + + function recalculateCurrentDatetime() { + let center = { + x: thisObject.container.frame.width / 2, + y: thisObject.container.frame.height / 2 + } + + datetime = new Date(UI.projects.superalgos.utilities.dateRateTransformations.getDateFromPointAtContainer(center, thisObject.container, timeMachineCoordinateSystem)) + + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[i] + timelineChart.setDatetime(datetime) + } + } + + function fitFunction(point, fullVisible, margin, topMargin, bottomMargin) { + /* We prevent a point to be out of the container AND out of the Charting Space in general */ + + let returnPoint = { + x: point.x, + y: point.y + } + + let upCorner = { + x: 0, + y: 0 + } + + let bottonCorner = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + } + + upCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(upCorner, thisObject.container) + bottonCorner = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(bottonCorner, thisObject.container) + + /* Checking against the container limits. */ + if (margin === undefined) { margin = 0 } + if (topMargin === undefined) { topMargin = 0 } + if (bottomMargin === undefined) { bottomMargin = 0 } + + if (returnPoint.x - margin < upCorner.x) { returnPoint.x = upCorner.x + margin } + if (returnPoint.x + margin > bottonCorner.x) { returnPoint.x = bottonCorner.x - margin } + if (returnPoint.y - margin - topMargin < upCorner.y) { returnPoint.y = upCorner.y + margin + topMargin } + if (returnPoint.y + margin + bottomMargin > bottonCorner.y) { returnPoint.y = bottonCorner.y - margin - bottomMargin } + + returnPoint = UI.projects.superalgos.spaces.chartingSpace.fitFunction(returnPoint, fullVisible) + + return returnPoint + } + + function physics() { + thisObject.edgeEditor.physics() + timeMachineCoordinateSystem.physics() + + UI.projects.superalgos.utilities.loadSaveFrame.saveFrame(thisObject.payload, thisObject.container.frame) + if (thisObject.container.frame.isInViewPort()) { + childrenPhysics() + syncWithDesigner() + } + panelPhysics() + onFocusPhysics() + tabAnimationPhysics() + } + + function tabAnimationPhysics() { + const MAX_COUNTER_VALUE = 40 + const STEP = 10 + + if (wasOnFocus === true && thisObject.onFocus === false) { + tabAnimationStatus = 'Closing' + } + if (wasOnFocus === false && thisObject.onFocus === true) { + tabAnimationStatus = 'Opening' + } + + if (tabAnimationStatus === 'Closing') { + tabAnimationCounter = tabAnimationCounter - STEP + if (tabAnimationCounter <= 0) { + tabAnimationCounter = 0 + tabAnimationStatus = 'Closed' + } + } + if (tabAnimationStatus === 'Opening') { + tabAnimationCounter = tabAnimationCounter + STEP + if (tabAnimationCounter >= MAX_COUNTER_VALUE) { + tabAnimationCounter = MAX_COUNTER_VALUE + tabAnimationStatus = 'Open' + } + } + } + + function onFocusPhysics() { + wasOnFocus = thisObject.onFocus + if (thisObject.edgeEditor.isMouseOver === true) { + thisObject.onFocus = true + } else { + thisObject.onFocus = false + } + } + + function panelPhysics() { + if ( + thisObject.container.frame.isInViewPort() && + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomLevel >= UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_NOT_HIDDING_PANELS) { + UI.projects.superalgos.spaces.panelSpace.unHide(thisObject.payload.node.id, 'Layers Panel') + UI.projects.superalgos.spaces.panelSpace.unHide(thisObject.payload.node.id, 'Plotter Panel') + return + } + + if ( + thisObject.container.frame.isCenterInViewPort() && + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomLevel >= UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_HIDDING_PANELS + ) { + UI.projects.superalgos.spaces.panelSpace.unHide(thisObject.payload.node.id, 'Layers Panel') + UI.projects.superalgos.spaces.panelSpace.unHide(thisObject.payload.node.id, 'Plotter Panel') + } else { + UI.projects.superalgos.spaces.panelSpace.hide(thisObject.payload.node.id, 'Layers Panel') + UI.projects.superalgos.spaces.panelSpace.hide(thisObject.payload.node.id, 'Plotter Panel') + } + } + + function syncWithDesigner() { + syncWithDesignerTimelineCharts() + syncWithDesignerScales() + } + + function syncWithDesignerScales() { + if (thisObject.payload.node === undefined) { + finalizeTimeScale() + finalizeRateScale() + finalizeTimeFrameScale() + return + } + if (thisObject.payload.node.timeScale === undefined && thisObject.timeScale !== undefined) { + finalizeTimeScale() + } + if (thisObject.payload.node.timeScale !== undefined && thisObject.timeScale === undefined) { + initializeTimeScale() + } + if (thisObject.payload.node.rateScale === undefined && thisObject.rateScale !== undefined) { + finalizeRateScale() + } + if (thisObject.payload.node.rateScale !== undefined && thisObject.rateScale === undefined) { + initializeRateScale() + } + if (thisObject.payload.node.timeFrameScale === undefined && thisObject.timeFrameScale !== undefined) { + finalizeTimeFrameScale() + } + if (thisObject.payload.node.timeFrameScale !== undefined && thisObject.timeFrameScale === undefined) { + initializeTimeFrameScale() + } + } + + function syncWithDesignerTimelineCharts() { + syncWithDesignerLoop = syncWithDesignerLoop + 0.00000000001 + + for (let j = 0; j < thisObject.payload.node.timelineCharts.length; j++) { + let node = thisObject.payload.node.timelineCharts[j] + let timelineChart = timelineChartsMap.get(node.id) + if (timelineChart === undefined) { + /* The timeline timelineChart node is new, thus we need to initialize a new timelineChart */ + initializeTimelineChart(node, syncWithDesignerLoop) + } else { + /* The time machine already exists, we tag it as existing at the current loop. */ + timelineChart.syncWithDesignerLoop = syncWithDesignerLoop + } + } + + /* We check all the timeMachines we have to see if we need to remove any of them */ + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[i] + if (timelineChart.syncWithDesignerLoop < syncWithDesignerLoop) { + /* Must be removed */ + timelineChart.container.eventHandler.stopListening(timelineChart.onChildrenMouseOverEventSuscriptionId) + timelineChart.finalize() + timelineChartsMap.delete(timelineChart.nodeId) + thisObject.timelineCharts.splice(i, 1) + /* We remove one at the time */ + return + } + } + + function initializeTimelineChart(node, syncWithDesignerLoop) { + let timelineChart = newTimelineChart() + timelineChart.syncWithDesignerLoop = syncWithDesignerLoop + timelineChart.payload = node.payload + timelineChart.nodeId = node.id + timelineChartsMap.set(node.id, timelineChart) + timelineChart.payload.uiObject.setValue('Loading...') + + /* Setting up the new timeline timelineChart. */ + timelineChart.container.connectToParent(thisObject.container, true, true, false, true, true, true, false, false, true) + timelineChart.container.fitFunction = thisObject.container.fitFunction + timelineChart.fitFunction = thisObject.fitFunction + timelineChart.container.frame.width = thisObject.container.frame.width + timelineChart.container.frame.height = thisObject.container.frame.height + timelineChart.container.frame.position.x = 0 + timelineChart.container.frame.position.y = 0 + timelineChart.initialize(timeMachineCoordinateSystem, timeFrame) + + /* we will store the event suscription id as a property of the timelineChart, to avoid keeping it an a separate array */ + timelineChart.onChildrenMouseOverEventSuscriptionId = timelineChart.container.eventHandler.listenToEvent('onChildrenMouseOver', onChildrenMouseOver) + + thisObject.timelineCharts.push(timelineChart) + timelineChart.payload.uiObject.setValue('') + + function onChildrenMouseOver(event) { + thisObject.container.eventHandler.raiseEvent('onMouseOver', event) + } + } + } + + function childrenPhysics() { + if (thisObject.timeScale !== undefined) { + thisObject.timeScale.physics() + } + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.physics() + } + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.physics() + } + + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[i] + timelineChart.physics() + } + } + + function drawBackground() { + drawChartsBackground() + if (thisObject.container.frame.isInViewPort()) { + if (drawScales === true) { + if (thisObject.timeScale !== undefined && thisObject.timeScale.isVisible === true) { thisObject.timeScale.drawBackground() } + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { thisObject.rateScale.drawBackground() } + } + + let maxElementsPlotted = 0 + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[thisObject.timelineCharts.length - i - 1] + let elementsPlotted = timelineChart.drawBackground() + if (elementsPlotted !== undefined) { + if (elementsPlotted > maxElementsPlotted) { + maxElementsPlotted = elementsPlotted + } + } + } + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.adjustTimeFrame(maxElementsPlotted) + } + } + } + + function drawChartsBackground() { + UI.projects.superalgos.utilities.drawPrint.drawContainerBackground(thisObject.container, UI_COLOR.WHITE, 0.5, thisObject.fitFunction) + } + + function draw() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.node === undefined) { return } + if (thisObject.container.frame.isInViewPort()) { + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[thisObject.timelineCharts.length - i - 1] + timelineChart.draw() + } + + if (drawScales === true) { + if (thisObject.timeScale !== undefined && thisObject.timeScale.isVisible === true) { thisObject.timeScale.draw() } + if (thisObject.timeFrameScale !== undefined && thisObject.timeFrameScale.isVisible === true) { thisObject.timeFrameScale.draw() } + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { thisObject.rateScale.draw() } + } + } else { + if (UI.projects.superalgos.spaces.chartingSpace.viewport.zoomTargetLevel < UI.projects.superalgos.globals.zoom.ZOOM_OUT_THRESHOLD_FOR_DISPLAYING_TIME_MACHINES_ICONIZED) { + let icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.payload.node.type) + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + let imageSize = 40 + let imagePosition = { + x: thisObject.container.frame.width / 2, + y: thisObject.container.frame.height / 2 + } + + imagePosition = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(imagePosition, thisObject.container) + imagePosition = thisObject.fitFunction(imagePosition, true) + + browserCanvasContext.drawImage( + icon, + imagePosition.x - imageSize / 2, + imagePosition.y - imageSize / 2, + imageSize, + imageSize) + } + } + } + } + } + + function drawForeground() { + if (thisObject.container.frame.isInViewPort()) { + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelineChart = thisObject.timelineCharts[thisObject.timelineCharts.length - i - 1] + timelineChart.drawForeground() + } + + if (drawScales === true) { + if (thisObject.timeScale !== undefined && thisObject.timeScale.isVisible === true) { thisObject.timeScale.drawForeground() } + if (thisObject.timeFrameScale !== undefined && thisObject.timeFrameScale.isVisible === true) { thisObject.timeFrameScale.drawForeground() } + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { thisObject.rateScale.drawForeground() } + } + + drawLabel() + thisObject.edgeEditor.drawForeground() + } + } + + function drawLabel() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.node === undefined) { return } + /* Draw Title Above the Container */ + let position = { + x: 0, + y: 0 + } + let imageSize = 12 + let fontSize = 12 + let opacity = 1 + let icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.payload.node.type) + + position = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(position, thisObject.container) + + let label = thisObject.payload.node.name + let description = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'description') + + if (description !== undefined) { + label = description + } + UI.projects.superalgos.utilities.drawPrint.printLabel(label, position.x + 20, position.y - 10, opacity, fontSize) + + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + browserCanvasContext.drawImage( + icon, position.x + 5, + position.y - 18, + imageSize, + imageSize) + } + } + + imageSize = 13 + fontSize = 15 + let exchangeMarkets = new Map() + + for (let i = 0; i < thisObject.timelineCharts.length; i++) { + let timelinechart = thisObject.timelineCharts[i] + if (timelinechart.layerManager !== undefined) { + let layers = timelinechart.layerManager.layers + for (let j = 0; j < layers.length; j++) { + let layer = layers[j] + + let exchangeMarket = { + exchangeName: layer.exchange.name, + marketName: layer.market, + exchangeIcon: layer.exchangeIcon, + baseAssetIcon: layer.baseAssetIcon, + quotedAssetIcon: layer.quotedAssetIcon + } + exchangeMarkets.set(exchangeMarket.exchangeName + '-' + exchangeMarket.marketName, exchangeMarket) + } + } + } + + drawTab(0, 0, exchangeMarkets.size * tabAnimationCounter, 90, thisObject.container) + + const INTER_EXCHANGE_SPACE = 60 + const INTER_MARKET_SPACE = -100 + + if (tabAnimationStatus === 'Open') { + drawMarketNames() + } + + function drawMarketNames() { + exchangeMarkets.forEach((exchangeMarket, i) => { + browserCanvasContext.save() + browserCanvasContext.translate(position.x, position.y) + browserCanvasContext.rotate(-Math.PI / 2) + + let xOffSet = -80 + + icon = exchangeMarket.exchangeIcon + + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + browserCanvasContext.drawImage( + icon, + -20 + xOffSet, + -43, + imageSize, + imageSize) + } + } + + UI.projects.superalgos.utilities.drawPrint.printLabel(exchangeMarket.exchangeName, -5 + xOffSet, -30, opacity, fontSize, UI_COLOR.GREY) + + position.x = position.x + INTER_EXCHANGE_SPACE + + icon = exchangeMarket.baseAssetIcon + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + browserCanvasContext.drawImage( + icon, + -20 + xOffSet, + -22, + imageSize, + imageSize) + } + } + + UI.projects.superalgos.utilities.drawPrint.printLabel(exchangeMarket.marketName, -5 + xOffSet, -10, opacity, fontSize, UI_COLOR.GREY) + + icon = exchangeMarket.quotedAssetIcon + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + browserCanvasContext.drawImage( + icon, + +50 + xOffSet, + -22, + imageSize, + imageSize) + } + } + + position.x = position.x + INTER_MARKET_SPACE + + browserCanvasContext.restore() + }) + } + + function drawTab(x, y, width, height, container) { + const GRADIENT = 0 + const X_OFFSET = -3 + const Y_OFFSET = 13 + const TAB_CORNERS_RADIUS = 5 + + let point = { + x: x, + y: y + } + point = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point, container) + + let point1 = { + x: point.x + X_OFFSET, + y: point.y + Y_OFFSET + } + + let point2 = { + x: point.x + X_OFFSET, + y: point.y + height + Y_OFFSET + } + + let point3 = { + x: point.x - width + X_OFFSET, + y: point.y + height - GRADIENT + Y_OFFSET + } + + let point4 = { + x: point.x - width + X_OFFSET, + y: point.y + GRADIENT + Y_OFFSET + } + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.arc(point3.x, point3.y - TAB_CORNERS_RADIUS, TAB_CORNERS_RADIUS, 0.5 * Math.PI, 1.0 * Math.PI) + // browserCanvasContext.lineTo(point4.x, point4.y) + browserCanvasContext.arc(point4.x, point4.y + TAB_CORNERS_RADIUS, TAB_CORNERS_RADIUS, 1.0 * Math.PI, 1.5 * Math.PI) + browserCanvasContext.closePath() + browserCanvasContext.lineWidth = 1 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + opacity + '' + browserCanvasContext.stroke() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + opacity + '' + browserCanvasContext.fill() + } + } + + function recalculateCoordinateSystem() { + let minValue = { + x: MIN_PLOTABLE_DATE.valueOf(), + y: 0 + } + + let maxValue = { + x: MAX_PLOTABLE_DATE.valueOf(), + y: nextPorwerOf10(MAX_DEFAULT_RATE_SCALE_VALUE) / 4 + } + + timeMachineCoordinateSystem.initialize( + minValue, + maxValue, + thisObject.container.frame.width, + thisObject.container.frame.height + ) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/TimelineChart.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/TimelineChart.js new file mode 100644 index 000000000..27096dd71 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/TimelineChart.js @@ -0,0 +1,422 @@ +function newTimelineChart() { + const MODULE_NAME = 'Timeline Chart' + + let logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let timeFrame = INITIAL_TIME_PERIOD + let datetime = NEW_SESSION_INITIAL_DATE + + let thisObject = { + container: undefined, + fitFunction: undefined, + rateScale: undefined, + timeFrameScale: undefined, + payload: undefined, + layerManager: undefined, + plotterManager: undefined, + upstreamTimeFrame: undefined, + setDatetime: setDatetime, + setTimeFrame: setTimeFrame, + physics: physics, + draw: draw, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + let timeMachineCoordinateSystem + let timelineChartCoordinateSystem = newCoordinateSystem() + timelineChartCoordinateSystem.name = 'TIMELINE CHART' + + let coordinateSystem + + let layersPanel + let layersPanelHandle + + let onMouseOverEventSuscriptionId + let onMouseNotOverEventSuscriptionId + let rateScaleUpstreamEventSuscriptionId + let rateScaleMouseOverEventSuscriptionId + let timeFrameScaleEventSuscriptionId + let timeFrameScaleMouseOverEventSuscriptionId + let scaleChangedEventSubscriptionId + + let drawScales = false + let mouse = { + position: { + x: 0, + y: 0 + } + } + + setupContainer() + return thisObject + + function setupContainer() { + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.detectMouseOver = true + } + + function finalize() { + timeMachineCoordinateSystem.eventHandler.stopListening(scaleChangedEventSubscriptionId) + + if (thisObject.layerManager !== undefined) { + finalizeLayerManager() + } + + if (thisObject.rateScale !== undefined) { + finalizeRateScale() + } + + if (thisObject.timeFrameScale !== undefined) { + finalizeTimeFrameScale() + } + + thisObject.container.eventHandler.stopListening(onMouseOverEventSuscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSuscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + + mouse = undefined + logger = undefined + + timeMachineCoordinateSystem = undefined + timelineChartCoordinateSystem.finalize() + timelineChartCoordinateSystem = undefined + coordinateSystem = undefined + layersPanel = undefined + layersPanelHandle = undefined + } + + function finalizeLayerManager() { + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.finalize() + } + thisObject.plotterManager = undefined + thisObject.layerManager = undefined + + UI.projects.superalgos.spaces.panelSpace.destroyPanel(layersPanelHandle) + } + + function finalizeTimeFrameScale() { + if (thisObject.timeFrameScale === undefined) { return } + + thisObject.timeFrameScale.container.eventHandler.stopListening(timeFrameScaleEventSuscriptionId) + thisObject.timeFrameScale.container.eventHandler.stopListening(timeFrameScaleMouseOverEventSuscriptionId) + thisObject.timeFrameScale.finalize() + thisObject.timeFrameScale = undefined + if (thisObject.upstreamTimeFrame !== undefined) { + timeFrame = thisObject.upstreamTimeFrame + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.setTimeFrame(timeFrame) + } + } + } + + function finalizeRateScale() { + if (thisObject.rateScale === undefined) { return } + + thisObject.rateScale.container.eventHandler.stopListening(rateScaleMouseOverEventSuscriptionId) + thisObject.container.parentContainer.eventHandler.stopListening(rateScaleUpstreamEventSuscriptionId) + thisObject.rateScale.finalize() + thisObject.rateScale = undefined + + /* Resets the local container with the dimessions of its parent, the Time Machine */ + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + thisObject.container.frame.height = thisObject.container.parentContainer.frame.height + thisObject.container.frame.width = thisObject.container.parentContainer.frame.width + } + + function initialize(pTimeMachineCoordinateSystem, pTimeFrame) { + /* We load the logow we will need for the background. */ + + timeMachineCoordinateSystem = pTimeMachineCoordinateSystem + coordinateSystem = timeMachineCoordinateSystem + initializeCoordinateSystem() + + timeFrame = pTimeFrame + + onMouseOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + scaleChangedEventSubscriptionId = timeMachineCoordinateSystem.eventHandler.listenToEvent('Scale Changed', onScaleChanged) + } + + function initializeCoordinateSystem() { + let minValue = { + x: timeMachineCoordinateSystem.min.x, + y: 0 + } + + let maxValue = { + x: timeMachineCoordinateSystem.max.x, + y: nextPorwerOf10(MAX_DEFAULT_RATE_SCALE_VALUE) / 4 + } + + if (thisObject.rateScale !== undefined) { + minValue.y = thisObject.rateScale.minValue + maxValue.y = thisObject.rateScale.maxValue + } + + timelineChartCoordinateSystem.initialize( + minValue, + maxValue, + thisObject.container.frame.width, + thisObject.container.frame.heigh + ) + } + + function initializeLayerManager() { + let owner = thisObject.payload.node.payload.parentNode.id // The real owner is the Time Machine + layersPanelHandle = UI.projects.superalgos.spaces.panelSpace.createNewPanel('Layers Panel', undefined, owner) + thisObject.layerManager = UI.projects.superalgos.spaces.panelSpace.getPanel(layersPanelHandle) + thisObject.layerManager.payload = thisObject.payload.node.layerManager.payload + thisObject.layerManager.initialize() + + /* Initialize the Plotter Manager */ + thisObject.plotterManager = newPlottersManager() + + thisObject.plotterManager.container.connectToParent(thisObject.container, true, true, false, true, true, true, false, false, true) + + thisObject.plotterManager.container.frame.position.x = 0 + thisObject.plotterManager.container.frame.position.y = 0 + + thisObject.plotterManager.fitFunction = thisObject.fitFunction + thisObject.plotterManager.payload = thisObject.payload + thisObject.plotterManager.initialize(thisObject.layerManager) + thisObject.plotterManager.setTimeFrame(timeFrame) + thisObject.plotterManager.setDatetime(datetime) + thisObject.plotterManager.setCoordinateSystem(coordinateSystem) + } + + function initializeRateScale() { + thisObject.rateScale = newRateScale() + thisObject.rateScale.fitFunction = thisObject.fitFunction + thisObject.rateScale.payload = thisObject.payload.node.rateScale.payload + + rateScaleMouseOverEventSuscriptionId = thisObject.rateScale.container.eventHandler.listenToEvent('onMouseOverScale', rateScaleMouseOver) + thisObject.rateScale.initialize(timelineChartCoordinateSystem, thisObject.container.parentContainer, thisObject.container) + + function rateScaleMouseOver(event) { + thisObject.container.eventHandler.raiseEvent('onChildrenMouseOver', event) + } + } + + function initializeTimeFrameScale() { + thisObject.timeFrameScale = newTimeFrameScale() + thisObject.timeFrameScale.fitFunction = thisObject.fitFunction + thisObject.timeFrameScale.payload = thisObject.payload.node.timeFrameScale.payload + + timeFrameScaleEventSuscriptionId = thisObject.timeFrameScale.container.eventHandler.listenToEvent('Time Frame Value Changed', timeFrameScaleValueChanged) + timeFrameScaleMouseOverEventSuscriptionId = thisObject.timeFrameScale.container.eventHandler.listenToEvent('onMouseOverScale', timeFrameScaleMouseOver) + thisObject.timeFrameScale.initialize(thisObject.container.parentContainer) + + function timeFrameScaleValueChanged(event) { + let currentTimeFrame = timeFrame + timeFrame = event.timeFrame + if (timeFrame !== currentTimeFrame) { + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.setTimeFrame(timeFrame) + } + } + } + + function timeFrameScaleMouseOver(event) { + thisObject.container.eventHandler.raiseEvent('onChildrenMouseOver', event) + } + } + + function onScaleChanged(event) { + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.onUpstreamScaleChanged(event) + } + timelineChartCoordinateSystem.min.x = timeMachineCoordinateSystem.min.x + timelineChartCoordinateSystem.max.x = timeMachineCoordinateSystem.max.x + timelineChartCoordinateSystem.maxHeight = timeMachineCoordinateSystem.maxHeight + timelineChartCoordinateSystem.maxWidth = timeMachineCoordinateSystem.maxWidth + timelineChartCoordinateSystem.autoMinXScale = timeMachineCoordinateSystem.autoMinXScale + timelineChartCoordinateSystem.autoMaxXScale = timeMachineCoordinateSystem.autoMaxXScale + timelineChartCoordinateSystem.recalculateScale() + } + + function setTimeFrame(pTimeFrame) { + timeFrame = pTimeFrame + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.setTimeFrame(timeFrame) + } + } + + function setDatetime(pDatetime) { + datetime = pDatetime + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.setDatetime(datetime) + } + } + + function onMouseOver(event) { + /* This event gets to the timelinechart container because it inherits it from the time machine container, which is the one raising Mouse Over and Mouse not Over Events to its children. */ + drawScales = true + + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.onMouseOverSomeTimeMachineContainer(event) + } + + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.onMouseOverSomeTimeMachineContainer(event) + } + + mouse.position.x = event.x + mouse.position.y = event.y + } + + function onMouseNotOver(event) { + /* This event is inherited from the Time Machine */ + drawScales = false + + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.visible = false + } + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.visible = false + } + } + + function getContainer(point, purpose) { + let container + + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { + container = thisObject.rateScale.getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + return container + } + } + } + + if (thisObject.timeFrameScale !== undefined && thisObject.timeFrameScale.isVisible === true) { + container = thisObject.timeFrameScale.getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + return container + } + } + } + + return container + } + + function physics() { + thisObjectPhysics() + childrenPhysics() + syncWithDesignerScales() + syncWithDesignerLayerManager() + } + + function thisObjectPhysics() { + /* Only if the plotter manager has some layer turned on, we will pass the min and max upstream. */ + if (thisObject.plotterManager !== undefined && thisObject.plotterManager.connectors.length > 0) { + timelineChartCoordinateSystem.reportParentXValue(timeMachineCoordinateSystem.min.x, timeMachineCoordinateSystem.max.x) + timelineChartCoordinateSystem.physics() + + timeMachineCoordinateSystem.reportXValue(timelineChartCoordinateSystem.childrenMin.x) + timeMachineCoordinateSystem.reportXValue(timelineChartCoordinateSystem.childrenMax.x) + } + } + + function syncWithDesignerLayerManager() { + if (thisObject.payload.node === undefined) { + finalizeLayerManager() + return + } + + if (thisObject.payload.node.layerManager === undefined && thisObject.layerManager !== undefined) { + finalizeLayerManager() + } + if (thisObject.payload.node.layerManager !== undefined && thisObject.layerManager === undefined) { + initializeLayerManager() + } + } + + function syncWithDesignerScales() { + if (thisObject.payload.node === undefined) { + finalizeRateScale() + finalizeTimeFrameScale() + return + } + if (thisObject.payload.node.rateScale === undefined && thisObject.rateScale !== undefined) { + finalizeRateScale() + coordinateSystem = timeMachineCoordinateSystem + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.setCoordinateSystem(coordinateSystem) + } + } + if (thisObject.payload.node.rateScale !== undefined && thisObject.rateScale === undefined) { + initializeRateScale() + coordinateSystem = timelineChartCoordinateSystem + if (thisObject.plotterManager !== undefined) { + thisObject.plotterManager.setCoordinateSystem(coordinateSystem) + } + } + if (thisObject.payload.node.timeFrameScale === undefined && thisObject.timeFrameScale !== undefined) { + finalizeTimeFrameScale() + } + if (thisObject.payload.node.timeFrameScale !== undefined && thisObject.timeFrameScale === undefined) { + initializeTimeFrameScale() + } + } + + function childrenPhysics() { + if (thisObject.rateScale !== undefined) { + thisObject.rateScale.layersOn = thisObject.plotterManager.connectors.length + thisObject.rateScale.physics() + } + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.layersOn = thisObject.plotterManager.connectors.length + thisObject.timeFrameScale.physics() + } + } + + function drawBackground() { + if (thisObject.container.frame.isInViewPort()) { + if (drawScales === true) { + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { thisObject.rateScale.drawBackground() } + } + drawChartsBackground() + if (thisObject.plotterManager !== undefined) { + let elementsPlotted = thisObject.plotterManager.draw() + if (thisObject.timeFrameScale !== undefined) { + thisObject.timeFrameScale.adjustTimeFrame(elementsPlotted) + } else { + return elementsPlotted + } + } + } + } + + function draw() { + if (thisObject.container.frame.isInViewPort()) { + if (drawScales === true) { + if (thisObject.timeFrameScale !== undefined && thisObject.timeFrameScale.isVisible === true) { thisObject.timeFrameScale.draw() } + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { thisObject.rateScale.draw() } + } + } + } + + function drawForeground() { + if (thisObject.container.frame.isInViewPort()) { + if (drawScales === true) { + if (thisObject.timeFrameScale !== undefined && thisObject.timeFrameScale.isVisible === true) { thisObject.timeFrameScale.drawForeground() } + if (thisObject.rateScale !== undefined && thisObject.rateScale.isVisible === true) { thisObject.rateScale.drawForeground() } + } + } + } + + function drawChartsBackground() { + UI.projects.superalgos.utilities.drawPrint.drawContainerBackground(thisObject.container, UI_COLOR.WHITE, 0, thisObject.fitFunction) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Charting-Space/Viewport.js b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Viewport.js new file mode 100644 index 000000000..9aaff8ef6 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Charting-Space/Viewport.js @@ -0,0 +1,423 @@ + +function newViewport() { + const MODULE_NAME = 'Viewport' + + const MIN_ZOOM_LEVEL = 0 + const MAX_ZOOM_LEVEL = 500 + + let ANIMATION_INCREMENT = 0.25 + let ANIMATION_STEPS = 5 + let MARGIN_AMOUNT = 40 + + let TOP_MARGIN = TOP_SPACE_HEIGHT + let BOTTOM_MARGIN = COCKPIT_SPACE_HEIGHT + MARGIN_AMOUNT + let LEFT_MARGIN = 0 + let RIGHT_MARGIN = 0 + let MARGINS = { + TOP: TOP_MARGIN, + BOTTOM: BOTTOM_MARGIN, + LEFT: LEFT_MARGIN, + RIGHT: RIGHT_MARGIN + } + + let thisObject = { + visible: false, + visibleArea: undefined, + width: undefined, + height: undefined, + eventHandler: undefined, + zoomTargetLevel: undefined, + zoomLevel: undefined, + mousePosition: undefined, + marginAmount: MARGIN_AMOUNT, + margins: MARGINS, + payload: undefined, + zoomAtCenter: zoomAtCenter, + changeZoom: changeZoom, + onMouseWheel: onMouseWheel, + transformThisPoint: transformThisPoint, + unTransformThisPoint: unTransformThisPoint, + isThisPointVisible: isThisPointVisible, + isThisPointInViewport: isThisPointInViewport, + fitIntoVisibleArea: fitIntoVisibleArea, + fitIntoViewport: fitIntoViewport, + displace: displace, + displaceToContainer: displaceToContainer, + physics: physics, + raiseEvents: raiseEvents, + resize: resize, + initialize: initialize, + finalize: finalize + } + + let position = { + x: 0, + y: 0 + } + + thisObject.mousePosition = { + x: 0, + y: 0 + } + thisObject.eventHandler = newEventHandler() + + /* Initial default value */ + thisObject.zoomLevel = MIN_ZOOM_LEVEL + thisObject.zoomTargetLevel = Math.round(MIN_ZOOM_LEVEL) + INITIAL_TIME_PERIOD = recalculatePeriod(thisObject.zoomLevel) + + let overrideMousePositionCounter = 0 + + return thisObject + + function finalize() { + thisObject.eventHandler.finalize() + thisObject.payload = undefined + thisObject = undefined + } + + function initialize() { + if (thisObject.payload !== undefined) { + /* Read the position from the frame structure */ + + let frame = { + position: { + x: 0, + y: 0 + } + } + UI.projects.superalgos.utilities.loadSaveFrame.loadFrame(thisObject.payload, frame) + if (!isNaN(frame.position.x)) { + position.x = frame.position.x + } + if (!isNaN(frame.position.y)) { + position.y = frame.position.y + } + } + + resize() + readObjectState() + } + + function resize() { + TOP_MARGIN = TOP_SPACE_HEIGHT + MARGIN_AMOUNT + BOTTOM_MARGIN = browserCanvas.height - COCKPIT_SPACE_POSITION + MARGIN_AMOUNT + LEFT_MARGIN = 0 + RIGHT_MARGIN = 0 + MARGINS = { + TOP: TOP_MARGIN, + BOTTOM: BOTTOM_MARGIN, + LEFT: LEFT_MARGIN, + RIGHT: RIGHT_MARGIN + } + + thisObject.visibleArea = { + topLeft: { x: LEFT_MARGIN, y: TOP_MARGIN }, + topRight: { x: browserCanvas.width - RIGHT_MARGIN, y: TOP_MARGIN }, + bottomRight: { x: browserCanvas.width - RIGHT_MARGIN, y: browserCanvas.height - BOTTOM_MARGIN }, + bottomLeft: { x: LEFT_MARGIN, y: browserCanvas.height - BOTTOM_MARGIN } + } + + thisObject.width = thisObject.visibleArea.topRight.x - thisObject.visibleArea.topLeft.x + thisObject.height = thisObject.visibleArea.bottomRight.y - thisObject.visibleArea.topLeft.y + + thisObject.center = { + x: LEFT_MARGIN + thisObject.width / 2, + y: TOP_MARGIN + thisObject.height / 2 + } + } + + function raiseEvents() { + if (thisObject.visible === false) { return } + let event = { + newPosition: position + } + + thisObject.eventHandler.raiseEvent('Position Changed', event) + } + + function displaceToContainer(container) { + let targetPoint = { + x: -container.frame.position.x - container.frame.width / 2, + y: -container.frame.position.y - container.frame.height / 2 + } + position.x = targetPoint.x + browserCanvas.width / 2 + position.y = targetPoint.y + (COCKPIT_SPACE_POSITION - TOP_SPACE_HEIGHT) / 2 + TOP_SPACE_HEIGHT + } + + function zoomAtCenter(level) { + thisObject.zoomTargetLevel = Math.round(level) + overrideMousePositionCounter = 50 + ANIMATION_INCREMENT = 0.5 + } + + function mousePositionPhysics() { + if (overrideMousePositionCounter > 0) { + thisObject.mousePosition.x = browserCanvas.width / 2 + thisObject.mousePosition.y = (COCKPIT_SPACE_POSITION - TOP_SPACE_HEIGHT) / 2 + TOP_SPACE_HEIGHT + + overrideMousePositionCounter-- + if (overrideMousePositionCounter < 0) { + overrideMousePositionCounter = 0 + } + } + } + + function physics() { + if (thisObject.visible === false) { return } + mousePositionPhysics() + animationPhysics() + positioningPhysics() + readObjectState() + } + + function positioningPhysics() { + if (thisObject.visible === false) { return } + if (thisObject.payload === undefined) { return } + /* Save the position at the frame lavel */ + let frame = {} + frame.position = position + UI.projects.superalgos.utilities.loadSaveFrame.saveFrame(thisObject.payload, frame) + } + + function animationPhysics() { + if (thisObject.zoomLevel < thisObject.zoomTargetLevel) { + if (thisObject.zoomTargetLevel - thisObject.zoomLevel < ANIMATION_INCREMENT) { + ANIMATION_INCREMENT = Math.round(Math.abs(thisObject.zoomTargetLevel - thisObject.zoomLevel) * 100) / 100 + } + thisObject.zoomLevel = Math.round((thisObject.zoomLevel + ANIMATION_INCREMENT) * 100) / 100 + changeZoom(thisObject.zoomLevel - ANIMATION_INCREMENT, thisObject.zoomLevel) + } + + if (thisObject.zoomLevel > thisObject.zoomTargetLevel) { + if (thisObject.zoomLevel - thisObject.zoomTargetLevel < ANIMATION_INCREMENT) { + ANIMATION_INCREMENT = Math.round(Math.abs(thisObject.zoomTargetLevel - thisObject.zoomLevel) * 100) / 100 + } + thisObject.zoomLevel = Math.round((thisObject.zoomLevel - ANIMATION_INCREMENT) * 100) / 100 + changeZoom(thisObject.zoomLevel + ANIMATION_INCREMENT, thisObject.zoomLevel) + } + } + + function onMouseWheel(event) { + if (thisObject.visible === false) { return } + if ((event.ctrlKey === true || event.metaKey === true)) { return } + let morePower = 1 + + if (IS_MAC) { + event.delta = event.delta * MAC_AMOUNT_FACTOR + } + + let amount = event.delta + + if (event.buttons === 4) { morePower = 2 } // Mouse wheel pressed. + /* We adjust the sensitivity for Mac Users */ + + if (thisObject.zoomTargetLevel > 10) { + amount = amount * 2 + } + if (thisObject.zoomTargetLevel > 25) { + amount = amount * 3 + } + if (thisObject.zoomTargetLevel > 50) { + amount = amount * 3.5 + } + + if (thisObject.zoomTargetLevel + amount * morePower > MAX_ZOOM_LEVEL) { + return false + } + if (thisObject.zoomTargetLevel + amount * morePower < MIN_ZOOM_LEVEL) { + return false + } + thisObject.zoomTargetLevel = Math.round(thisObject.zoomTargetLevel + amount * morePower) + + ANIMATION_INCREMENT = Math.round(Math.abs(thisObject.zoomTargetLevel - thisObject.zoomLevel) / ANIMATION_STEPS * 100) / 100 + + let newEvent = { + newLevel: thisObject.zoomTargetLevel, + newPosition: position, + type: undefined, + shiftKey: event.shiftKey + } + + if (amount > 0) { + newEvent.type = 'Zoom In' + } else { + newEvent.type = 'Zoom Out' + } + + thisObject.eventHandler.raiseEvent('Zoom Changed', newEvent) + return true + } + + function fitIntoVisibleArea(point) { + let pointCopy = { + x: point.x, + y: point.y + } + + /* Here we check the boundaries of the resulting points, so they dont go out of the visible area. */ + if (pointCopy.x > thisObject.visibleArea.bottomRight.x + 1) { + pointCopy.x = thisObject.visibleArea.bottomRight.x + 1 + } + + if (pointCopy.x < thisObject.visibleArea.topLeft.x - 1) { + pointCopy.x = thisObject.visibleArea.topLeft.x - 1 + } + + if (pointCopy.y > thisObject.visibleArea.bottomRight.y + 1) { + pointCopy.y = thisObject.visibleArea.bottomRight.y + 1 + } + + if (pointCopy.y < thisObject.visibleArea.topLeft.y - 1) { + pointCopy.y = thisObject.visibleArea.topLeft.y - 1 + } + + return pointCopy + } + + function fitIntoViewport(point) { + let pointCopy = { + x: point.x, + y: point.y + } + if (pointCopy.y > COCKPIT_SPACE_POSITION) { + pointCopy.y = COCKPIT_SPACE_POSITION + } + return pointCopy + } + + function displace(displaceVector, recalculate) { + if (thisObject.visible === false) { return } + position.x = position.x + displaceVector.x + position.y = position.y + displaceVector.y + + saveObjectState() + + let event = { + newPosition: position, + recalculate: recalculate + } + + thisObject.eventHandler.raiseEvent('Position Changed', event) + } + + function changeZoom(oldLevel, newLevel) { + if (thisObject.visible === false) { return } + let oldMouse = unTransformThisPoint(thisObject.mousePosition, oldLevel) + let newMouse = transformThisPoint(oldMouse, newLevel) + + position.x = position.x - newMouse.x + thisObject.mousePosition.x + position.y = position.y - newMouse.y + thisObject.mousePosition.y + + saveObjectState() + + thisObject.eventHandler.raiseEvent('Zoom Changed') + } + + function transformThisPoint(point, level) { + if (thisObject.visible === false) { return } + let transformedPoint = { + x: 0, + y: 0 + } + + if (level === undefined) { + transformedPoint.x = point.x * (1 + thisObject.zoomLevel) + position.x + transformedPoint.y = point.y * (1 + thisObject.zoomLevel) + position.y + } else { + transformedPoint.x = point.x * (1 + level) + position.x + transformedPoint.y = point.y * (1 + level) + position.y + } + + return transformedPoint + } + + function unTransformThisPoint(point, level) { + let pointWithoutZoom = { + x: 0, + y: 0 + } + if (level === undefined) { + pointWithoutZoom.x = (point.x - position.x) / (1 + thisObject.zoomLevel) + pointWithoutZoom.y = (point.y - position.y) / (1 + thisObject.zoomLevel) + } else { + pointWithoutZoom.x = (point.x - position.x) / (1 + level) + pointWithoutZoom.y = (point.y - position.y) / (1 + level) + } + + return pointWithoutZoom + } + + function isThisPointVisible(point) { + if (thisObject.visible === false) { return } + if (point.x < thisObject.visibleArea.topLeft.x || point.x > thisObject.visibleArea.bottomRight.x || point.y < thisObject.visibleArea.topLeft.y || point.y > thisObject.visibleArea.bottomRight.y) { + return false + } else { + return true + } + } + + function isThisPointInViewport(point) { + if (thisObject.visible === false) { return } + if (point.y > COCKPIT_SPACE_POSITION) { + return false + } else { + return true + } + } + + function saveObjectState() { + if (thisObject.payload === undefined) { return } + + /* Save the zoom at the node config, so that the user can change it if he wishes to. */ + try { + let config = JSON.parse(thisObject.payload.node.config) + config.zoom = (thisObject.zoomTargetLevel - MIN_ZOOM_LEVEL) / (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) * 100 + config.zoom = config.zoom.toFixed(2) + thisObject.payload.node.config = JSON.stringify(config) + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function readObjectState() { + if (thisObject.payload === undefined) { return } + + /* Read the zoom level from the node config */ + try { + let config = JSON.parse(thisObject.payload.node.config) + + if (isNaN(config.zoom) || config.zoom === null || config.zoom === undefined) { + saveObjectState() + return + } + config.zoom = config.zoom / 100 * (MAX_ZOOM_LEVEL - MIN_ZOOM_LEVEL) + MIN_ZOOM_LEVEL + if (config.zoom < MIN_ZOOM_LEVEL) { config.zoom = MIN_ZOOM_LEVEL } + if (config.zoom > MAX_ZOOM_LEVEL) { config.zoom = MAX_ZOOM_LEVEL } + + if (config.zoom.toFixed(2) !== thisObject.zoomTargetLevel.toFixed(2)) { + newZoomLevel(config.zoom) + } else { + saveObjectState() + } + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + + function newZoomLevel(level) { + thisObject.zoomLevel = Math.round(level) + thisObject.zoomTargetLevel = Math.round(level) + INITIAL_TIME_PERIOD = recalculatePeriod(level) + saveObjectState() + ANIMATION_INCREMENT = 0 + + let event = { + newLevel: thisObject.zoomTargetLevel, + newPosition: position, + type: undefined + } + thisObject.eventHandler.raiseEvent('Zoom Changed', event) + return true + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Chat-Space/ChatSpace.js b/Integrations/Projects/Superalgos/Spaces/Chat-Space/ChatSpace.js new file mode 100644 index 000000000..972578094 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Chat-Space/ChatSpace.js @@ -0,0 +1,199 @@ +function newSuperalgosChatSpace() { + const MODULE_NAME = 'Chat Space' + let thisObject = { + sidePanelTab: undefined, + container: undefined, + navigateTo: navigateTo, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + let isInitialized = false + + thisObject.container = newContainer() + thisObject.container.name = MODULE_NAME + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = true + thisObject.container.status = 'hidden' + + resize() + + let browserResizedEventSubscriptionId + let openingEventSubscriptionId + let closingEventSubscriptionId + + const DEFAULT_URL = 'https://web.telegram.org/#/im?p=@superalgoscommunity' + return thisObject + + function initialize() { + + thisObject.sidePanelTab = newSidePanelTab() + thisObject.sidePanelTab.container.connectToParent(thisObject.container, false, false) + thisObject.sidePanelTab.initialize('right', 1) + openingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('opening', onOpening) + closingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('closing', onClosing) + + browserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize) + + isInitialized = true + } + + function finalize() { + canvas.eventHandler.stopListening(browserResizedEventSubscriptionId) + thisObject.sidePanelTab.container.eventHandler.stopListening(openingEventSubscriptionId) + thisObject.sidePanelTab.container.eventHandler.stopListening(closingEventSubscriptionId) + } + + function onOpening() { + let docIFrame = document.getElementById('docIFrame') + if (docIFrame.src === "") { + docIFrame.src = DEFAULT_URL + } + } + + function onClosing() { + + } + + function navigateTo(url) { + let docIFrame = document.getElementById('docIFrame') + docIFrame.src = url + thisObject.sidePanelTab.open() + } + + function resize() { + thisObject.container.frame.width = 1200 + thisObject.container.frame.height = browserCanvas.height // - TOP_SPACE_HEIGHT + thisObject.container.frame.position.x = browserCanvas.width + thisObject.container.frame.position.y = 0 // TOP_SPACE_HEIGHT + + if (thisObject.sidePanelTab !== undefined) { + thisObject.sidePanelTab.resize() + } + } + + function getContainer(point, purpose) { + let container + + container = thisObject.sidePanelTab.getContainer(point, purpose) + if (container !== undefined) { return container } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + if (container.isWheelable === true) { + return thisObject.container + } else { + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + return + } + } else { + return + } + } + + function physics() { + thisObject.sidePanelTab.physics() + chatAppDivPhysics() + iFramePhysics() + + function chatAppDivPhysics() { + let chatAppDiv = document.getElementById('chatApp') + chatAppDivPosition = { + x: 0, + y: 0 + } + chatAppDivPosition = thisObject.container.frame.frameThisPoint(chatAppDivPosition) + chatAppDiv.style = ' ' + + 'position:fixed; top:' + chatAppDivPosition.y + 'px; ' + + 'left:' + chatAppDivPosition.x + 'px; z-index:1; ' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + } + + function iFramePhysics() { + let docIFrame = document.getElementById('chatIFrame') + docIFrame.style = ' ' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + } + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + if (isInitialized === false) { return } + borders() + thisObject.sidePanelTab.draw() + } + + function borders() { + let point1 + let point2 + let point3 + let point4 + + point1 = { + x: 0, + y: 0 + } + + point2 = { + x: thisObject.container.frame.width, + y: 0 + } + + point3 = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + } + + point4 = { + x: 0, + y: thisObject.container.frame.height + } + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + point4 = thisObject.container.frame.frameThisPoint(point4) + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.lineTo(point4.x, point4.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + let opacity = 1 + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.LIGHT_GREY + ', ' + opacity + '' + browserCanvasContext.fill() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + opacity + '' + browserCanvasContext.lineWidth = 0.3 + browserCanvasContext.stroke() + + /* Shadow */ + + if (thisObject.container.status !== 'hidden') { + for (let i = 0; i <= 30; i++) { + opacity = 1 - (i / 300) - 0.95 + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point2.x + i, point2.y) + browserCanvasContext.lineTo(point3.x + i, point3.y) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + opacity + '' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/AssetBalances.js b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/AssetBalances.js new file mode 100644 index 000000000..afcb65668 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/AssetBalances.js @@ -0,0 +1,123 @@ + +function newAssetalances() { + const MODULE_NAME = 'Asset Balances' + + let thisObject = { + visible: true, + container: undefined, + paramsArray: undefined, + setParamsArray: setParamsArray, + physics: physics, + draw: draw, + speedometers: [], + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = false + + return thisObject + + function finalize() { + for (let i = 0; i < thisObject.speedometers.length; i++) { + let speedometer = thisObject.speedometers[i] + speedometer.finalize() + } + thisObject.container.finalize() + thisObject.container = undefined + thisObject.paramsArray = undefined + } + + function initialize() { + for (let i = 0; i < 2; i++) { + let speedometer = newSpeedometer() + + speedometer.initialize() + speedometer.container.connectToParent(thisObject.container) + speedometer.container.frame.position.x = thisObject.container.frame.width / 2 + speedometer.container.frame.position.y = thisObject.container.frame.height * (2 + i * 2) / 5 + thisObject.speedometers.push(speedometer) + } + } + + function getContainer(point, purpose) { + if (thisObject.visible !== true) { return } + + let container + for (let i = 0; i < thisObject.speedometers.length; i++) { + container = thisObject.speedometers[i].getContainer(point, purpose) + if (container !== undefined) { + if (container.isForThisPurpose(purpose)) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return container + } + } + } + } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function setParamsArray(paramsArray) { + thisObject.paramsArray = paramsArray + for (let i = 0; i < thisObject.speedometers.length; i++) { + let speedometer = thisObject.speedometers[i] + speedometer.params = paramsArray[i] + } + } + + function physics() { + + } + + function draw() { + if (thisObject.visible !== true) { return } + + for (let i = 0; i < thisObject.speedometers.length; i++) { + let speedometer = thisObject.speedometers[i] + speedometer.draw() + } + + drawQuotedAssetalances() + } + + function drawQuotedAssetalances() { + if (thisObject.paramsArray === undefined) { return } + + let fontSize + let label + let xOffset + let yOffset + + const OPACITY = 1 + + /* We put the params.VALUE in the middle */ + + fontSize = 15 + + browserCanvasContext.font = 'bold ' + fontSize + 'px ' + UI_FONT.PRIMARY + + label = 'ASSET BALANCES' + + xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + yOffset = -45 + 9 + + labelPoint = { + x: 0 - xOffset + (thisObject.paramsArray[1].LEFT_OFFSET - thisObject.paramsArray[0].LEFT_OFFSET) / 2 + thisObject.paramsArray[0].LEFT_OFFSET - 20, + y: COCKPIT_SPACE_POSITION - COCKPIT_SPACE_HEIGHT / 2 - yOffset + } + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/CockpitSpace.js b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/CockpitSpace.js new file mode 100644 index 000000000..715ae1a91 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/CockpitSpace.js @@ -0,0 +1,366 @@ + +function newSuperalgosCockpitSpace() { + const MODULE_NAME = 'CockpitSpace' + const STATUS_TYPES = { + ALL_GOOD: 1, + WARNING: 2 + } + let thisObject = { + container: undefined, + status: 'BOTTOM', + fullscreen: undefined, + statusTypes: STATUS_TYPES, + setStatus: setStatus, + toTop: toTop, + toBottom: toBottom, + toMiddle: toMiddle, + moveUp: moveUp, + moveDown: moveDown, + draw: draw, + physics: physics, + getContainer: getContainer, + finalize: finalize, + initialize: initialize, + initializePosition: initializePosition + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = false + thisObject.container.detectMouseOver = true + thisObject.container.isDraggeable = true + thisObject.container.notDraggingOnX = true + + controlsMap = new Map() + resize() + + let selfMouseClickEventSubscriptionId + let canvasBrowserResizedEventSubscriptionId + + let statusCounter = 0 + let statusText + let statusType + let isInitilized = false + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(selfMouseClickEventSubscriptionId) + thisObject.container.eventHandler.stopListening(canvasBrowserResizedEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fullscreen = undefined + } + + function initialize() { + canvasBrowserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize) + selfMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + + thisObject.fullscreen = newFullScreen() + thisObject.fullscreen.container.connectToParent(thisObject.container) + thisObject.fullscreen.initialize() + } + + function initializePosition() { + thisObject.container.frame.position.x = 0 + + let INITIAL_POSITION + if (UI.projects.superalgos.spaces.designSpace.workspace.enabled === true) { + let localStorage = window.localStorage.getItem(MODULE_NAME) + if (localStorage !== null) { + storage = JSON.parse(localStorage) + thisObject.container.frame.position.y = storage.spacePosition + } else { + INITIAL_POSITION = browserCanvas.height - 55 + thisObject.container.frame.position.y = browserCanvas.height * INITIAL_POSITION / 100 - COCKPIT_SPACE_HEIGHT + } + } else { + INITIAL_POSITION = 100 + thisObject.container.frame.position.y = browserCanvas.height * INITIAL_POSITION / 100 - COCKPIT_SPACE_HEIGHT + } + isInitilized = true + } + + function onMouseClick(event) { + + } + + function setStatus(text, counter, type) { + statusCounter = counter + statusText = text + statusType = type + } + + function resize() { + thisObject.container.frame.width = browserCanvas.width + thisObject.container.frame.height = COCKPIT_SPACE_HEIGHT + + switch (thisObject.status) { + case 'BOTTOM': { + thisObject.container.frame.position.y = browserCanvas.height - COCKPIT_SPACE_HEIGHT + break + } + case 'TOP': { + thisObject.container.frame.position.y = TOP_SPACE_HEIGHT + break + } + } + } + + function toTop() { + thisObject.container.frame.position.y = TOP_SPACE_HEIGHT + } + + function toBottom() { + thisObject.container.frame.position.y = browserCanvas.height - COCKPIT_SPACE_HEIGHT + } + + function toMiddle() { + thisObject.container.frame.position.y = browserCanvas.height / 2 - COCKPIT_SPACE_HEIGHT + } + + function moveUp() { + thisObject.container.frame.position.y = thisObject.container.frame.position.y - COCKPIT_SPACE_HEIGHT * 2 + if (thisObject.container.frame.position.y < 0) { + thisObject.container.frame.position.y = 0 + } + } + + function moveDown() { + thisObject.container.frame.position.y = thisObject.container.frame.position.y + COCKPIT_SPACE_HEIGHT * 2 + if (thisObject.container.frame.position.y > browserCanvas.height - COCKPIT_SPACE_HEIGHT) { + thisObject.container.frame.position.y = browserCanvas.height - COCKPIT_SPACE_HEIGHT + } + } + + function statusPhysics() { + statusCounter-- + if (statusCounter < 0) { + statusCounter = 0 + statusText = undefined, + statusType = undefined + } + } + + function physics() { + thisObjectPhysics() + childrenPhysics() + statusPhysics() + } + + function childrenPhysics() { + thisObject.fullscreen.physics() + } + function thisObjectPhysics() { + if (isInitilized === false) { return } + /* Check the limits */ + + thisObject.status = 'MIDDLE' + + if (UI.projects.superalgos.spaces.designSpace.workspace.enabled === true) { + thisObject.container.isDraggeable = true + } else { + thisObject.container.isDraggeable = false + } + + if (thisObject.container.frame.position.y > browserCanvas.height * 99.5 / 100 - COCKPIT_SPACE_HEIGHT) { + thisObject.container.frame.position.y = browserCanvas.height - COCKPIT_SPACE_HEIGHT + thisObject.status = 'BOTTOM' + UI.projects.superalgos.spaces.designSpace.makeInvisible() + UI.projects.superalgos.spaces.floatingSpace.makeInvisible() + } else { + UI.projects.superalgos.spaces.designSpace.makeVisible() + UI.projects.superalgos.spaces.floatingSpace.makeVisible() + } + + if (thisObject.container.frame.position.y < browserCanvas.height * 0.5 / 100 + TOP_SPACE_HEIGHT) { + thisObject.container.frame.position.y = TOP_SPACE_HEIGHT + thisObject.status = 'TOP' + UI.projects.superalgos.spaces.panelSpace.visible = false + } else { + UI.projects.superalgos.spaces.panelSpace.visible = true + } + + COCKPIT_SPACE_POSITION = thisObject.container.frame.position.y + let storage = { + spacePosition: COCKPIT_SPACE_POSITION + } + window.localStorage.setItem(MODULE_NAME, JSON.stringify(storage)) + if (UI.projects.superalgos.spaces.chartingSpace.viewport !== undefined) { + UI.projects.superalgos.spaces.chartingSpace.viewport.resize() + } + } + + function getContainer(point) { + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + thisObject.container.space = 'Cockpit Space' + return thisObject.container + } else { + return undefined + } + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + thisObject.container.frame.draw(false, false) + + drawBackground() + + // thisObject.fullscreen.draw() + } + + function drawBackground() { + let opacity = 1 + + let zeroPoint = { + x: 0, + y: 0 + } + + const RED_LINE_HIGHT = 2 + + zeroPoint = thisObject.container.frame.frameThisPoint(zeroPoint) + + let barColor = UI_COLOR.DARK_TURQUOISE // default + let textColor + + if (statusType !== undefined) { + switch (statusType) { + case STATUS_TYPES.ALL_GOOD: + { + barColor = UI_COLOR.PATINATED_TURQUOISE + textColor = UI_COLOR.WHITE + break + } + case STATUS_TYPES.WARNING: + { + barColor = UI_COLOR.RED + textColor = UI_COLOR.TITANIUM_YELLOW + break + } + } + } + + browserCanvasContext.beginPath() + browserCanvasContext.rect(zeroPoint.x, zeroPoint.y, thisObject.container.frame.width, thisObject.container.frame.height) + browserCanvasContext.fillStyle = 'rgba(' + barColor + ', ' + opacity + ')' + browserCanvasContext.closePath() + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.rect(zeroPoint.x, zeroPoint.y, thisObject.container.frame.width, RED_LINE_HIGHT) + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + opacity + ')' + browserCanvasContext.closePath() + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.rect(zeroPoint.x, zeroPoint.y - RED_LINE_HIGHT + thisObject.container.frame.height, thisObject.container.frame.width, RED_LINE_HIGHT) + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + opacity + ')' + browserCanvasContext.closePath() + browserCanvasContext.fill() + + if (statusText !== undefined) { + let position = { + x: thisObject.container.frame.width / 2, + y: thisObject.container.frame.height / 2 + 5 + } + UI.projects.superalgos.utilities.drawPrint.printLabel(statusText, position.x, position.y, 1, 15, textColor, true, thisObject.container) + } + + if (UI.projects.superalgos.spaces.designSpace !== undefined) { + if (UI.projects.superalgos.spaces.designSpace.workspace.enabled === true && statusText === undefined) { + arrow() + } + } + } + + function arrow() { + const X_OFFSET = thisObject.container.frame.width / 2 + const Y_OFFSET = thisObject.container.frame.height / 2 + const HEIGHT = 6 + const WIDTH = 18 + const LINE_WIDTH = 3 + const OPACITY = 1 + let DISTANCE_BETWEEN_ARROWS = 0 + let ARROW_1_DIRECTION = 0 + let ARROW_2_DIRECTION = 0 + + switch (thisObject.status) { + + case ('TOP'): { + ARROW_1_DIRECTION = -1 + ARROW_2_DIRECTION = -1 + DISTANCE_BETWEEN_ARROWS = 6 + break + } + case ('MIDDLE'): { + ARROW_1_DIRECTION = 1 + ARROW_2_DIRECTION = -1 + DISTANCE_BETWEEN_ARROWS = 9 + break + } + case ('BOTTOM'): { + ARROW_1_DIRECTION = 1 + ARROW_2_DIRECTION = 1 + DISTANCE_BETWEEN_ARROWS = 6 + break + } + } + + point1 = { + x: X_OFFSET - WIDTH / 2, + y: Y_OFFSET + HEIGHT / 2 * ARROW_1_DIRECTION - DISTANCE_BETWEEN_ARROWS / 2 + } + + point2 = { + x: X_OFFSET, + y: Y_OFFSET - HEIGHT / 2 * ARROW_1_DIRECTION - DISTANCE_BETWEEN_ARROWS / 2 + } + + point3 = { + x: X_OFFSET + WIDTH / 2, + y: Y_OFFSET + HEIGHT / 2 * ARROW_1_DIRECTION - DISTANCE_BETWEEN_ARROWS / 2 + } + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + + point4 = { + x: X_OFFSET - WIDTH / 2, + y: Y_OFFSET + HEIGHT / 2 * ARROW_2_DIRECTION + DISTANCE_BETWEEN_ARROWS / 2 + } + + point5 = { + x: X_OFFSET, + y: Y_OFFSET - HEIGHT / 2 * ARROW_2_DIRECTION + DISTANCE_BETWEEN_ARROWS / 2 + } + + point6 = { + x: X_OFFSET + WIDTH / 2, + y: Y_OFFSET + HEIGHT / 2 * ARROW_2_DIRECTION + DISTANCE_BETWEEN_ARROWS / 2 + } + + point4 = thisObject.container.frame.frameThisPoint(point4) + point5 = thisObject.container.frame.frameThisPoint(point5) + point6 = thisObject.container.frame.frameThisPoint(point6) + + browserCanvasContext.setLineDash([0, 0]) + + browserCanvasContext.beginPath() + + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + + browserCanvasContext.moveTo(point4.x, point4.y) + browserCanvasContext.lineTo(point5.x, point5.y) + browserCanvasContext.lineTo(point6.x, point6.y) + + browserCanvasContext.lineWidth = LINE_WIDTH + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.stroke() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/FullScreen.js b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/FullScreen.js new file mode 100644 index 000000000..a5b2c759b --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/FullScreen.js @@ -0,0 +1,169 @@ + +function newFullScreen() { + const MODULE_NAME = 'Fullscreen' + + let thisObject = { + visible: true, + container: undefined, + status: undefined, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = false + thisObject.container.isClickeable = true + thisObject.container.detectMouseOver = true + + let selfMouseOverEventSubscriptionId + let selfMouseClickEventSubscriptionId + let selfMouseNotOverEventSubscriptionId + + let isMouseOver = false + let counterTillNextState = 0 + + let layersToTurnOn = [] + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(selfMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseClickEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseNotOverEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + } + + function initialize() { + thisObject.container.frame.width = 50 + thisObject.container.frame.height = COCKPIT_SPACE_HEIGHT - 12 + + selfMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + selfMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + selfMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + + thisObject.status = 'Ready' + } + + function getContainer(point, purpose) { + if (thisObject.visible !== true) { return } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function onMouseOver(point) { + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + isMouseOver = true + } else { + isMouseOver = false + } + } + + function onMouseNotOver(point) { + isMouseOver = false + } + + function onMouseClick(event) { + if (thisObject.status !== 'On') { + thisObject.status = 'On' + goFullscreen() + CURRENT_TOP_MARGIN = 0 + AT_FULL_SCREEN_MODE = true + } else { + thisObject.status = 'Off' + goOutFullscreen() + CURRENT_TOP_MARGIN = TOP_MARGIN + AT_FULL_SCREEN_MODE = false + } + } + + function goFullscreen() { + if (browserCanvas.requestFullscreen) { + browserCanvas.requestFullscreen() + } else if (browserCanvas.mozRequestFullScreen) { + browserCanvas.mozRequestFullScreen() + } else if (browserCanvas.webkitRequestFullscreen) { + browserCanvas.webkitRequestFullscreen() + } else if (browserCanvas.msRequestFullscreen) { browserCanvas.msRequestFullscreen() } + } + + function goOutFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen() + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen() + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen() + } else if (document.msExitFullscreen) { document.msExitFullscreen() } + } + + function physics() { + positionPhysics() + } + + function positionPhysics() { + thisObject.container.frame.position.x = thisObject.container.parentContainer.frame.width - thisObject.container.frame.width + thisObject.container.frame.position.y = 6 + } + + function draw() { + if (thisObject.visible !== true) { return } + drawBackground() + drawText() + } + + function drawBackground() { + let params = { + cornerRadius: 3, + lineWidth: 0.01, + container: thisObject.container, + borderColor: UI_COLOR.DARK, + castShadow: false, + opacity: 1 + } + + if (isMouseOver === true) { + params.backgroundColor = UI_COLOR.TURQUOISE + } else { + params.backgroundColor = UI_COLOR.DARK_TURQUOISE + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + } + + function drawText() { + let fontSize + let label + let xOffset + let yOffset + + const OPACITY = 1 + + /* We put the params.VALUE in the middle */ + + fontSize = 15 + + browserCanvasContext.font = 'bold ' + fontSize + 'px ' + UI_FONT.PRIMARY + label = 'FS' + + let labelPoint = { + x: thisObject.container.frame.width / 2 - label.length / 2 * fontSize * FONT_ASPECT_RATIO, + y: thisObject.container.frame.height - 9 + } + labelPoint = thisObject.container.frame.frameThisPoint(labelPoint) + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/Speedometer.js b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/Speedometer.js new file mode 100644 index 000000000..7d64af030 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Cockpit-Space/Speedometer.js @@ -0,0 +1,265 @@ + +function newSpeedometer() { + const MODULE_NAME = 'Speedometer' + + let thisObject = { + visible: true, + container: undefined, + params: undefined, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = false + + return thisObject + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.params = undefined + } + + function initialize() { + + } + + function getContainer(point, purpose) { + if (thisObject.visible !== true) { return } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function physics() { + + } + + function draw() { + if (thisObject.params === undefined) { return } + drawAssetCircle() + } + + function drawAssetCircle() { + const RED_LINE_HIGHT = 2 + const RADIUS = 50 + const OPACITY = 1 + + let centerPoint = { + x: 0, + y: 0 + } + + centerPoint = thisObject.container.frame.frameThisPoint(centerPoint) + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, RADIUS + RED_LINE_HIGHT, 0 * Math.PI, 2.0 * Math.PI) + browserCanvasContext.closePath() + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + OPACITY + ')' + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, RADIUS, 0.0 * Math.PI, 2.0 * Math.PI) + browserCanvasContext.closePath() + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + OPACITY + ')' + browserCanvasContext.fill() + + /* We draw the circle bar */ + + const DEFAULT_THICKNESS = 2 + const VALUE_THICKNESS = 8 + const VALUE_BG_THICKNESS = 1 + const BAR_RADIUS = RADIUS * 0.85 + + let BAR_START_ANGLE = 0.85 * Math.PI + let BAR_END_ANGLE = 2.15 * Math.PI + let CURRENT_VALUE_ANGLE = thisObject.params.VALUE * (BAR_END_ANGLE - BAR_START_ANGLE) / thisObject.params.MAX_VALUE + BAR_START_ANGLE + let MIN_VALUE_ANGLE = thisObject.params.MIN_VALUE * (BAR_END_ANGLE - BAR_START_ANGLE) / thisObject.params.MAX_VALUE + BAR_START_ANGLE + let INIT_VALUE_ANGLE = thisObject.params.INIT_VALUE * (BAR_END_ANGLE - BAR_START_ANGLE) / thisObject.params.MAX_VALUE + BAR_START_ANGLE + let PROFIT_VALUE_ANGLE = CURRENT_VALUE_ANGLE + + if (CURRENT_VALUE_ANGLE > INIT_VALUE_ANGLE) { CURRENT_VALUE_ANGLE = INIT_VALUE_ANGLE } + if (CURRENT_VALUE_ANGLE < MIN_VALUE_ANGLE) { CURRENT_VALUE_ANGLE = MIN_VALUE_ANGLE } + if (PROFIT_VALUE_ANGLE > BAR_END_ANGLE) { PROFIT_VALUE_ANGLE = BAR_END_ANGLE } + + if ( + thisObject.params.VALUE > 0 && + thisObject.params.ASSET_NAME === thisObject.params.BASE_ASSET && + thisObject.params.POSITION_TAKEN === false + ) { + browserCanvasContext.setLineDash([0, 0]) + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, BAR_START_ANGLE, MIN_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_BG_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, MIN_VALUE_ANGLE, CURRENT_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_BG_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, CURRENT_VALUE_ANGLE, INIT_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_BG_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + + if (PROFIT_VALUE_ANGLE > INIT_VALUE_ANGLE) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, INIT_VALUE_ANGLE, PROFIT_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREEN + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_BG_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + } + + browserCanvasContext.setLineDash([2, 3]) + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, BAR_START_ANGLE, MIN_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, MIN_VALUE_ANGLE, CURRENT_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, CURRENT_VALUE_ANGLE, INIT_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + + if (PROFIT_VALUE_ANGLE > INIT_VALUE_ANGLE) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(centerPoint.x, centerPoint.y, BAR_RADIUS, INIT_VALUE_ANGLE, PROFIT_VALUE_ANGLE) + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREEN + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = VALUE_THICKNESS + browserCanvasContext.stroke() + browserCanvasContext.closePath() + } + } + + /* Common Variables */ + + let fontSize + let label + let xOffset + let yOffset + + /* We put the thisObject.params.VALUE in the middle */ + + label = thisObject.params.VALUE + + if (isNaN(label) === false) { + label = Number(label) + + label = dynamicDecimals(label, 2) + if (label === 0) { label = label.toFixed(2) } + } + + fontSize = 22 + if (label.length > 8) { + fontSize = 18 + } + if (label.length > 10) { + fontSize = 14 + } + if (label.length > 12) { + fontSize = 10 + } + if (label.length > 14) { + fontSize = 8 + } + + browserCanvasContext.font = 'bold ' + fontSize + 'px ' + UI_FONT.PRIMARY + + // label = label.substring(0, 6) + + xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + 7 + + labelPoint = { + x: centerPoint.x - xOffset, + y: centerPoint.y + } + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + + /* We put the top label */ + + fontSize = 12 + + browserCanvasContext.font = 'bold ' + fontSize + 'px ' + UI_FONT.PRIMARY + + label = thisObject.params.ASSET_LABEL + + xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + 3 + yOffset = 22 + + labelPoint = { + x: centerPoint.x - xOffset, + y: centerPoint.y - yOffset + } + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + + /* We put the bottom label */ + + fontSize = 12 + + browserCanvasContext.font = 'bold ' + fontSize + 'px ' + UI_FONT.PRIMARY + + label = thisObject.params.ASSET_NAME + ' ' + + xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + 3 + yOffset = -15 + + labelPoint = { + x: centerPoint.x - xOffset, + y: centerPoint.y - yOffset + } + + if (thisObject.params.ASSET_NAME === thisObject.params.BASE_ASSET) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + } else { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + OPACITY + ')' + } + + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Design-Space/DesignSpace.js b/Integrations/Projects/Superalgos/Spaces/Design-Space/DesignSpace.js new file mode 100644 index 000000000..51267a439 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Design-Space/DesignSpace.js @@ -0,0 +1,131 @@ + +function newSuperalgosDesignSpace() { + const MODULE_NAME = 'Designe Space' + let thisObject = { + container: undefined, + iconsByProjectAndName: undefined, + iconsByProjectAndType: undefined, + workspace: undefined, + getIconByProjectAndName: getIconByProjectAndName, + getIconByProjectAndType: getIconByProjectAndType, + physics: physics, + draw: draw, + getContainer: getContainer, + makeVisible: makeVisible, + makeInvisible: makeInvisible, + initialize: initialize + } + + let container = newContainer() + container.initialize() + thisObject.container = container + + container.isDraggeable = false + + thisObject.iconsByProjectAndName = new Map() + thisObject.iconsByProjectAndType = new Map() + + return thisObject + + function initialize() { + loadIconCollection(onIconsReady) + + function onIconsReady() { + buildIconByProjectAndTypeMap() + thisObject.workspace = newWorkspace() + thisObject.workspace.initialize() + } + + function buildIconByProjectAndTypeMap() { + /* Take types-icons relationships defined at the schema */ + for (let i = 0; i < PROJECTS_ARRAY.length; i++) { + let project = PROJECTS_ARRAY[i] + let appSchema = SCHEMAS_BY_PROJECT.get(project).array.appSchema + for (let j = 0; j < appSchema.length; j++) { + let nodeDefinition = appSchema[j] + let iconName = nodeDefinition.icon + if (iconName === undefined) { + iconName = nodeDefinition.type.toLowerCase() + iconName = iconName.split(" ").join("-") + } + let icon = thisObject.getIconByProjectAndName(project, iconName) + if (icon !== undefined) { + let key = project + '-' + nodeDefinition.type + thisObject.iconsByProjectAndType.set(key, icon) + } + } + } + } + } + + function loadIconCollection(callBack) { + + callWebServer(undefined, 'IconNames', onResponse) + + function onResponse(err, data) { + if (err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + console.log('Failed to Fetch Image Names from the Backend') + return + } + + let iconsArray = JSON.parse(data) + let imageLoadedCounter = 0 + + for (let i = 0; i < iconsArray.length; i++) { + let project = iconsArray[i][0] + let name = iconsArray[i][1] + loadImage(project, name) + } + + function loadImage(project, name) { + imageLoadedCounter++ + const PATH = 'Icons/' + project + '/' + let image = new Image() + image.onload = onImageLoad + image.fileName = name + + function onImageLoad() { + image.canDrawIcon = true + } + image.src = PATH + name + let key = project + '-' + name.substring(0, name.length - 4) + thisObject.iconsByProjectAndName.set(key, image) + + if (imageLoadedCounter === iconsArray.length) { + callBack() + } + } + } + } + + function getIconByProjectAndName(project, name) { + return thisObject.iconsByProjectAndName.get(project + '-' + name) + } + + function getIconByProjectAndType(project, type) { + return thisObject.iconsByProjectAndType.get(project + '-' + type) + } + + function physics() { + if (thisObject.workspace === undefined) { return } + thisObject.workspace.physics() + } + + function makeVisible() { + visible = true + } + + function makeInvisible() { + visible = false + } + + function getContainer(point) { + + } + + function draw() { + if (thisObject.workspace === undefined) { return } + if (CAN_SPACES_DRAW === false) { return } + thisObject.workspace.draw() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Design-Space/Workspace.js b/Integrations/Projects/Superalgos/Spaces/Design-Space/Workspace.js new file mode 100644 index 000000000..edac9924c --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Design-Space/Workspace.js @@ -0,0 +1,493 @@ + +function newWorkspace() { + const MODULE_NAME = 'Workspace' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + workspaceNode: undefined, + container: undefined, + enabled: false, + nodeChildren: undefined, + eventsServerClients: new Map(), + replaceWorkspaceByLoadingOne: replaceWorkspaceByLoadingOne, + save: saveWorkspace, + backup: backupWorkspace, + share: shareWorkspace, + getProjectsHeads: getProjectsHeads, + getHierarchyHeads: getHierarchyHeads, + getHierarchyHeadsById: getHierarchyHeadsById, + getHierarchyHeadsByType: getHierarchyHeadsByType, + getNodeThatIsOnFocus: getNodeThatIsOnFocus, + getNodeByShortcutKey: getNodeByShortcutKey, + getNodeById: getNodeById, + stopAllRunningTasks: stopAllRunningTasks, + executeAction: executeAction, + physics: physics, + draw: draw, + spawn: spawn, + initialize: initialize, + finalize: finalize + } + + spawnPosition = { + x: UI.projects.superalgos.spaces.floatingSpace.container.frame.width / 2, + y: UI.projects.superalgos.spaces.floatingSpace.container.frame.height / 2 + } + + thisObject.workspaceNode = {} + thisObject.workspaceNode.rootNodes = [] + + thisObject.nodeChildren = newNodeChildren() + + let savingWorkspaceIntervalId + let isInitialized = false + let workingAtTask = 0 + let circularProgressBar = newBusyProgressBar() + circularProgressBar.fitFunction = UI.projects.superalgos.spaces.floatingSpace.fitIntoVisibleArea + let loadedWorkspaceNode + let sessionTimestamp = (new Date()).valueOf() + window.localStorage.setItem('Session Timestamp', sessionTimestamp) + + let actionSwitchesByProject = new Map() + + return thisObject + + function finalize() { + thisObject.definition = undefined + thisObject.workspaceNode = undefined + circularProgressBar.finalize() + circularProgressBar = undefined + actionSwitchesByProject = undefined + } + + function initialize() { + try { + /* Set up the action switches map */ + for (let i = 0; i < PROJECTS_ARRAY.length; i++) { + let project = PROJECTS_ARRAY[i] + let actionSwitch = eval('new' + project + 'ActionSwitch()') + actionSwitchesByProject.set(project, actionSwitch) + } + + /* Check which was the last workspace. */ + let lastUsedWorkspace = window.localStorage.getItem('Last Used Workspace') + + if (lastUsedWorkspace !== 'undefined' && lastUsedWorkspace !== null && lastUsedWorkspace !== undefined) { + callWebServer(undefined, 'LoadMyWorkspace' + '/' + lastUsedWorkspace, onFileReceived) + function onFileReceived(err, text, response) { + if (err && err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + UI.projects.superalgos.spaces.cockpitSpace.setStatus('Could not load the last Workspace used, called "' + lastUsedWorkspace + '". Will switch to the default Workspace instead.', 500, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.WARNING) + thisObject.workspaceNode = getWorkspace() // This is the default workspace that comes with the system. + thisObject.workspaceNode.project = 'Superalgos' + recreateWorkspace() + return + } + thisObject.workspaceNode = JSON.parse(text) + thisObject.workspaceNode.project = 'Superalgos' + recreateWorkspace() + } + } else { + thisObject.workspaceNode = getWorkspace() // This is the default workspace that comes with the system. + thisObject.workspaceNode.project = 'Superalgos' + recreateWorkspace() + } + + function recreateWorkspace() { + executeAction({ node: thisObject.workspaceNode, name: 'Recreate Workspace', project: 'Superalgos', callBackFunction: finishInitialization }) + } + + function finishInitialization() { + setupEventsServerClients() + runTasksAndSessions() + thisObject.enabled = true + UI.projects.superalgos.spaces.cockpitSpace.initializePosition() + CAN_SPACES_DRAW = true + savingWorkspaceIntervalId = setInterval(saveWorkspace, 60000) + isInitialized = true + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = ' + err.stack) } + } + } + + function runTasksAndSessions() { + executeAction({ name: 'Syncronize Tasks', project: 'Superalgos' }) + executeAction({ name: 'Syncronize Sessions', project: 'Superalgos' }) + executeAction({ name: 'Play Tutorials', project: 'Superalgos' }) + } + + function setupEventsServerClients() { + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + + if (rootNode.type === 'Network') { + if (rootNode.networkNodes === undefined) { continue } + for (let j = 0; j < rootNode.networkNodes.length; j++) { + let networkNode = rootNode.networkNodes[j] + + let eventsServerClient = newEventsServerClient(networkNode) + eventsServerClient.initialize() + + thisObject.eventsServerClients.set(networkNode.id, eventsServerClient) + } + } + } + } + + async function saveWorkspace() { + let workspace = UI.projects.superalgos.spaces.designSpace.workspace.workspaceNode + + /* Validation if it is too early to save. */ + if (isInitialized === false) { return } + + /* Validation of 2 sessions opened at the same time. */ + let savedSessionTimestamp = window.localStorage.getItem('Session Timestamp') + if (Number(savedSessionTimestamp) !== sessionTimestamp) { + UI.projects.superalgos.spaces.cockpitSpace.setStatus( + 'Could not save the Workspace. You have more that one instance of the Superlagos User Interface open at the same time. Plese close this instance as it is older than the others.' + , 150, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.WARNING) + return + } + + /* Validation that there is something to save */ + let textToSave = await stringifyWorkspace() + if (textToSave === undefined) { + UI.projects.superalgos.spaces.cockpitSpace.setStatus( + 'Could not save the Workspace. Something is preventing the System to do it at this time.' + , 150, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.WARNING) + return + } + + window.localStorage.setItem('Session Timestamp', sessionTimestamp) + + /* Validation Workspace has a name */ + if (workspace.name === undefined) { + UI.projects.superalgos.spaces.cockpitSpace.setStatus( + 'Could not save the Workspace. You need to specify a name for it.' + , 150, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.WARNING) + return + } + + let url = 'SaveWorkspace/' + workspace.name + callWebServer(textToSave, url, onResponse) + return true + + function onResponse(err) { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + window.localStorage.setItem('Last Used Workspace', workspace.name) + window.localStorage.setItem('Session Timestamp', sessionTimestamp) + if (ARE_WE_RECORDING_A_MARKET_PANORAMA === false) { + UI.projects.superalgos.spaces.cockpitSpace.setStatus(workspace.name + ' Saved.', 50, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.ALL_GOOD) + } + } else { + UI.projects.superalgos.spaces.cockpitSpace.setStatus('Could not save the Workspace at the Backend. Please check the Backend Console for more information.', 150, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.WARNING) + } + } + } + + function backupWorkspace(action) { + let text = stringifyWorkspace(false) + let fileName = 'Backup - ' + action.node.type + ' - ' + action.node.name + '.json' + if (text !== undefined) { + UI.projects.superalgos.utilities.download.downloadText(fileName, text) + } + } + + function shareWorkspace(action) { + let text = stringifyWorkspace(true) + let fileName = 'Share - ' + action.node.type + ' - ' + action.node.name + '.json' + if (text !== undefined) { + UI.projects.superalgos.utilities.download.downloadText(fileName, text) + } + } + + async function getNodeById(nodeId) { + return await executeAction({ name: 'Get Node By Id', project: 'Superalgos', relatedNodeId: nodeId }) + } + + function physics() { + eventsServerClientsPhysics() + replacingWorkspacePhysics() + } + + function eventsServerClientsPhysics() { + thisObject.eventsServerClients.forEach(applyPhysics) + + function applyPhysics(eventServerClient) { + eventServerClient.physics() + } + } + + function finalizeEventsServerClients() { + thisObject.eventsServerClients.forEach(finalize) + + function finalize(eventServerClient) { + eventServerClient.finalize() + } + } + + function replacingWorkspacePhysics() { + if (thisObject.enabled !== true) { return } + + if (workingAtTask > 0) { + circularProgressBar.physics() + + switch (workingAtTask) { + case 1: + UI.projects.superalgos.spaces.tutorialSpace.stop() + executeAction({ node: thisObject.workspaceNode, name: 'Delete Workspace', project: 'Superalgos' }) + workingAtTask++ + break + case 2: + finalizeEventsServerClients() + thisObject.eventsServerClients = new Map() + workingAtTask++ + break + case 3: + clearInterval(savingWorkspaceIntervalId) + workingAtTask++ + break + case 4: + thisObject.workspaceNode = loadedWorkspaceNode + thisObject.workspaceNode.project = 'Superalgos' + loadedWorkspaceNode = undefined + workingAtTask++ + break + case 5: + executeAction({ node: thisObject.workspaceNode, name: 'Recreate Workspace', project: 'Superalgos', callBackFunction: finishInitialization }) + function finishInitialization() { + setupEventsServerClients() + runTasksAndSessions() + } + workingAtTask++ + break + case 6: + UI.projects.superalgos.spaces.chartingSpace.reset() + workingAtTask++ + break + case 7: + workingAtTask = 0 + circularProgressBar.visible = false + break + } + } + } + + function draw() { + if (circularProgressBar !== undefined) { + circularProgressBar.draw() + } + } + + async function stringifyWorkspace(removePersonalData) { + let stringifyReadyNodes = [] + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + + if (rootNode.isPlugin !== true) { + let node = await executeAction({ node: rootNode, name: 'Get Node Data Structure', project: 'Superalgos', extraParameter: removePersonalData }) + stringifyReadyNodes.push(node) + } + } + let workspace = { + type: 'Workspace', + name: thisObject.workspaceNode.name, + rootNodes: stringifyReadyNodes + } + + /* + Sometimes it happens that there are no rootNodes. + In those situations the workspace can not be stringified, + to prevent later saving it corrupting the original file. + */ + if (stringifyReadyNodes.length > 0) { + return JSON.stringify(workspace) + } else { + return + } + } + + function stopAllRunningTasks() { + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + if (rootNode.type === 'Network') { + if (rootNode.networkNodes !== undefined) { + for (let j = 0; j < rootNode.networkNodes.length; j++) { + let networkNode = rootNode.networkNodes[j] + if (networkNode.dataMining !== undefined && networkNode.dataMining.payload !== undefined) { + networkNode.dataMining.payload.uiObject.menu.internalClick('Stop All Exchange Data Tasks') + networkNode.dataMining.payload.uiObject.menu.internalClick('Stop All Exchange Data Tasks') + } + if (networkNode.testingEnvironment !== undefined && networkNode.testingEnvironment.payload !== undefined) { + networkNode.testingEnvironment.payload.uiObject.menu.internalClick('Stop All Exchange Trading Tasks') + networkNode.testingEnvironment.payload.uiObject.menu.internalClick('Stop All Exchange Trading Tasks') + } + if (networkNode.productionEnvironment !== undefined && networkNode.productionEnvironment.payload !== undefined) { + networkNode.productionEnvironment.payload.uiObject.menu.internalClick('Stop All Exchange Trading Tasks') + networkNode.productionEnvironment.payload.uiObject.menu.internalClick('Stop All Exchange Trading Tasks') + } + } + } + } + } + } + + async function getNodeByShortcutKey(searchingKey) { + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + let node = await executeAction({ node: rootNode, name: 'Get Node By Shortcut Key', project: 'Superalgos', extraParameter: searchingKey }) + if (node !== undefined) { return node } + } + } + + async function getNodeThatIsOnFocus() { + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + let node = await executeAction({ node: rootNode, name: 'Get Node On Focus', project: 'Superalgos' }) + if (node !== undefined) { return node } + } + } + + function getHierarchyHeads() { + let nodes = [] + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + let nodeDefinition = getNodeDefinition(rootNode) + if (nodeDefinition !== undefined) { + if (nodeDefinition.isHierarchyHead === true) { + nodes.push(rootNode) + } + } + } + return nodes + } + + function getProjectsHeads() { + let nodes = [] + for (let i = 0; i < thisObject.workspaceNode.rootNodes.length; i++) { + let rootNode = thisObject.workspaceNode.rootNodes[i] + let nodeDefinition = getNodeDefinition(rootNode) + if (nodeDefinition !== undefined) { + if (nodeDefinition.isProjectHead === true) { + nodes.push(rootNode) + } + } + } + return nodes + } + + function getHierarchyHeadsById(nodeId) { + let hiriatchyHeads = getHierarchyHeads() + for (let i = 0; i < hiriatchyHeads.length; i++) { + let hiriatchyHead = hiriatchyHeads[i] + if (hiriatchyHead.id === nodeId) { + return hiriatchyHead + } + } + } + + function getHierarchyHeadsByType(nodeType) { + let hiriatchyHeads = getHierarchyHeads() + for (let i = 0; i < hiriatchyHeads.length; i++) { + let hiriatchyHead = hiriatchyHeads[i] + if (hiriatchyHead.type === nodeType) { + return hiriatchyHead + } + } + } + + function replaceWorkspaceByLoadingOne(name) { + + let webCommand + if (name.indexOf('Plugin \u2192 ') >= 0) { + name = name.replace('Plugin \u2192 ', '') + webCommand = 'LoadPlugin' + '/' + 'Superalgos' + '/' + 'Workspaces' + '/' + name + '.json' + } else { + webCommand = 'LoadMyWorkspace' + '/' + name + } + + callWebServer(undefined, webCommand, onFileReceived) + function onFileReceived(err, text, response) { + if (err && err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + UI.projects.superalgos.spaces.cockpitSpace.setStatus('Could not load the Workspace called "' + name + '". ', 500, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.WARNING) + return + } + + loadedWorkspaceNode = JSON.parse(text) + saveWorkspace() + UI.projects.superalgos.spaces.cockpitSpace.toTop() + + let position = { + x: browserCanvas.width / 2, + y: browserCanvas.height / 2 + } + + circularProgressBar.initialize(position) + circularProgressBar.visible = true + workingAtTask = 1 + } + } + + function spawn(nodeText, mousePointer) { + try { + let point = { + x: mousePointer.x, + y: mousePointer.y + } + point = UI.projects.superalgos.spaces.floatingSpace.container.frame.unframeThisPoint(point) + spawnPosition.x = point.x + spawnPosition.y = point.y + + let droppedNode = JSON.parse(nodeText) + + if (droppedNode.type === 'Workspace') { + loadedWorkspaceNode = droppedNode + circularProgressBar.initialize(mousePointer) + circularProgressBar.visible = true + workingAtTask = 1 + return + } + + /* It does not exist, so we recreeate it respecting the inner state of each object. */ + let positionOffset = { + x: spawnPosition.x, + y: spawnPosition.y + } + + if (droppedNode.savedPayload !== undefined) { + positionOffset.x = positionOffset.x - droppedNode.savedPayload.position.x + positionOffset.y = positionOffset.y - droppedNode.savedPayload.position.y + } + + thisObject.workspaceNode.rootNodes.push(droppedNode) + executeAction({ node: droppedNode, name: 'Create UI Object', project: 'Superalgos', extraParameter: positionOffset }) + executeAction({ name: 'Connect Children to Reference Parents', project: 'Superalgos' }) + + droppedNode = undefined + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] spawn -> err = ' + err.stack) } + } + } + + async function executeAction(action) { + /* + Supported parameters as part of the action object: + + action.node : It is the node at which the action was taken. If not specific node is involved then it should be the workspace node. + action.relatedNode : It is a secondary node involved with the action. For example the node to which the main node is being attached to. + action.relatedNodeId : It is the id of a node related to the action. + action.relatedNodeType : It is the type of the node related to the action. + action.callBackFunction : A callback function to call when the action is complete. + action.extraParameter : A parameter to send unusual info to the fnction processing the action. + + We add rootNodes property here. + */ + action.rootNodes = thisObject.workspaceNode.rootNodes + + let actionSwitch = actionSwitchesByProject.get(action.project) + return actionSwitch.executeAction(action) + + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Doc-Space/DocSpace.js b/Integrations/Projects/Superalgos/Spaces/Doc-Space/DocSpace.js new file mode 100644 index 000000000..2ac097b16 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Doc-Space/DocSpace.js @@ -0,0 +1,192 @@ +function newSuperalgosDocSpace() { + const MODULE_NAME = 'Doc Space' + let thisObject = { + sidePanelTab: undefined, + container: undefined, + navigateTo: navigateTo, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + let isInitialized = false + + thisObject.container = newContainer() + thisObject.container.name = MODULE_NAME + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = true + thisObject.container.status = 'hidden' + + resize() + + let browserResizedEventSubscriptionId + let openingEventSubscriptionId + let closingEventSubscriptionId + + return thisObject + + function initialize() { + thisObject.sidePanelTab = newSidePanelTab() + thisObject.sidePanelTab.container.connectToParent(thisObject.container, false, false) + thisObject.sidePanelTab.initialize('right') + openingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('opening', onOpening) + closingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('closing', onClosing) + + browserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize) + + isInitialized = true + } + + function finalize() { + canvas.eventHandler.stopListening(browserResizedEventSubscriptionId) + thisObject.sidePanelTab.container.eventHandler.stopListening(openingEventSubscriptionId) + thisObject.sidePanelTab.container.eventHandler.stopListening(closingEventSubscriptionId) + } + + function onOpening() { + let docIFrame = document.getElementById('docIFrame') + if (docIFrame.src === "") { + docIFrame.src = UI.projects.superalgos.globals.docs.DOCUMENTATION_URL_DEFAULT + } + } + + function onClosing() { + + } + + function navigateTo(url) { + let docIFrame = document.getElementById('docIFrame') + docIFrame.src = url + thisObject.sidePanelTab.open() + } + + function resize() { + thisObject.container.frame.width = 1200 + thisObject.container.frame.height = browserCanvas.height // - TOP_SPACE_HEIGHT + thisObject.container.frame.position.x = browserCanvas.width + thisObject.container.frame.position.y = 0 // TOP_SPACE_HEIGHT + + if (thisObject.sidePanelTab !== undefined) { + thisObject.sidePanelTab.resize() + } + } + + function getContainer(point, purpose) { + let container + + container = thisObject.sidePanelTab.getContainer(point, purpose) + if (container !== undefined) { return container } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function physics() { + thisObject.sidePanelTab.physics() + docAppDivPhysics() + iFramePhysics() + + function docAppDivPhysics() { + let docAppDiv = document.getElementById('docApp') + docAppDivPosition = { + x: 0, + y: 0 + } + docAppDivPosition = thisObject.container.frame.frameThisPoint(docAppDivPosition) + docAppDiv.style = ' ' + + 'position:fixed; top:' + docAppDivPosition.y + 'px; ' + + 'left:' + docAppDivPosition.x + 'px; z-index:1; ' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + } + + function iFramePhysics() { + let docIFrame = document.getElementById('docIFrame') + docIFrame.style = ' ' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + } + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + if (isInitialized === false) { return } + borders() + thisObject.sidePanelTab.draw() + } + + function borders() { + let point1 + let point2 + let point3 + let point4 + + point1 = { + x: 0, + y: 0 + } + + point2 = { + x: thisObject.container.frame.width, + y: 0 + } + + point3 = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + } + + point4 = { + x: 0, + y: thisObject.container.frame.height + } + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + point4 = thisObject.container.frame.frameThisPoint(point4) + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.lineTo(point4.x, point4.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + let opacity = 1 + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.LIGHT_GREY + ', ' + opacity + '' + browserCanvasContext.fill() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + opacity + '' + browserCanvasContext.lineWidth = 0.3 + browserCanvasContext.stroke() + + /* Shadow */ + + if (thisObject.container.status !== 'hidden') { + for (let i = 0; i <= 30; i++) { + opacity = 1 - (i / 300) - 0.95 + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point2.x + i, point2.y) + browserCanvasContext.lineTo(point3.x + i, point3.y) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + opacity + '' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/BusyProgressBar.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/BusyProgressBar.js new file mode 100644 index 000000000..4b325000a --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/BusyProgressBar.js @@ -0,0 +1,72 @@ + +function newBusyProgressBar() { + const MODULE_NAME = 'Circular Progress Bar' + + let thisObject = { + container: undefined, + isDeployed: undefined, + visible: undefined, + physics: physics, + draw: draw, + finalize: finalize, + initialize: initialize + } + + let position + let addRadius + + let eventSubscriptionHeartbeat + + return thisObject + + function finalize() { + thisObject.payload = undefined + thisObject.fitFunction = undefined + } + + function initialize(pPosition) { + position = pPosition + + addRadius = 0 + } + + function physics() { + addRadius = addRadius + 20 + } + + function draw() { + if (thisObject.visible !== true) { return } + + let visiblePosition = { + x: position.x, + y: position.y + } + + visiblePosition = thisObject.fitFunction(visiblePosition) + + const RADIUS = 10 + addRadius + let OPACITY = 1 + + let initialAngle = 0 + let finalAngle = Math.PI * 2 + + browserCanvasContext.beginPath() + + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, RADIUS, initialAngle, finalAngle, false) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', 1)' + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GOLDEN_ORANGE + ', ' + OPACITY * 2 + ')' + browserCanvasContext.lineWidth = 15 + browserCanvasContext.setLineDash([3, 4]) + + browserCanvasContext.stroke() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([3, 4]) + browserCanvasContext.stroke() + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularMenu.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularMenu.js new file mode 100644 index 000000000..c099d9690 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularMenu.js @@ -0,0 +1,225 @@ + +function newCircularMenu() { + const MODULE_NAME = 'Circular Menu' + + let thisObject = { + container: undefined, + isDeployed: undefined, + internalClick: internalClick, + physics: physics, + invisiblePhysics: invisiblePhysics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME, 'Circle') + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let menuItems = [] + + let selfFocusEventSubscriptionId + let selfNotFocuskEventSubscriptionId + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(selfFocusEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfNotFocuskEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + menuItem.finalize() + } + + menuItems = undefined + } + + function initialize(menuItemsInitialValues, payload) { + /* Create the array of Menu Items */ + + let iconAndTextArray = [] + let iconOnlyArray + let ringsArray = [[], [], [], []] + + for (let i = 0; i < menuItemsInitialValues.length; i++) { + let menuItem = newCircularMenuItem() + let menuItemInitialValue = menuItemsInitialValues[i] + + menuItem.action = menuItemInitialValue.action + menuItem.actionProject = menuItemInitialValue.actionProject + menuItem.actionFunction = menuItemInitialValue.actionFunction + menuItem.actionStatus = menuItemInitialValue.actionStatus + menuItem.label = menuItemInitialValue.label + menuItem.workingLabel = menuItemInitialValue.workingLabel + menuItem.workDoneLabel = menuItemInitialValue.workDoneLabel + menuItem.workFailedLabel = menuItemInitialValue.workFailedLabel + menuItem.secondaryAction = menuItemInitialValue.secondaryAction + menuItem.secondaryLabel = menuItemInitialValue.secondaryLabel + menuItem.secondaryWorkingLabel = menuItemInitialValue.secondaryWorkingLabel + menuItem.secondaryWorkDoneLabel = menuItemInitialValue.secondaryWorkDoneLabel + menuItem.secondaryWorkFailedLabel = menuItemInitialValue.secondaryWorkFailedLabel + menuItem.secondaryIcon = menuItemInitialValue.secondaryIcon + menuItem.visible = menuItemInitialValue.visible + menuItem.iconPathOn = menuItemInitialValue.iconPathOn + menuItem.iconPathOff = menuItemInitialValue.iconPathOff + menuItem.rawRadius = menuItemInitialValue.rawRadius + menuItem.targetRadius = menuItemInitialValue.label + menuItem.currentRadius = menuItemInitialValue.currentRadius + menuItem.angle = menuItemInitialValue.angle + menuItem.currentStatus = menuItemInitialValue.currentStatus + menuItem.relatedUiObject = menuItemInitialValue.relatedUiObject + menuItem.dontShowAtFullscreen = menuItemInitialValue.dontShowAtFullscreen + menuItem.askConfirmation = menuItemInitialValue.askConfirmation + menuItem.confirmationLabel = menuItemInitialValue.confirmationLabel + menuItem.disableIfPropertyIsDefined = menuItemInitialValue.disableIfPropertyIsDefined + menuItem.propertyToCheckFor = menuItemInitialValue.propertyToCheckFor + menuItem.ring = menuItemInitialValue.ring + menuItem.icons = menuItemInitialValue.icons + + if (menuItem.label === undefined) { + menuItem.type = 'Icon Only' + + if (menuItem.ring === undefined) { + menutItem.ring = 1 + } + iconOnlyArray = ringsArray[menuItem.ring - 1] + iconOnlyArray.push(menuItem) + } else { + menuItem.type = 'Icon & Text' + iconAndTextArray.push(menuItem) + } + + menuItem.initialize(payload) + menuItem.container.connectToParent(thisObject.container, false, false, true, true, false, false, true, true) + menuItems.push(menuItem) + } + + /* There are 4 possible rings of icons, we will go through each of them here. */ + let amplitudeArray = [80, 50, 40, 80] + let initialAngleArray = [220, 205, 200, 180] + for (let j = 1; j < ringsArray.length + 1; j++) { + let iconOnlyArray = ringsArray[j - 1] + /* Here we calculate the angles for each menu item, and then apply it if it was not previously defined. */ + let amplitude = amplitudeArray[j - 1] + let initialAngle = initialAngleArray[j - 1] + let step = amplitude / (iconOnlyArray.length - 1) + if (iconOnlyArray.length === 1) { + step = 0 + } + + for (let i = 0; i < iconOnlyArray.length; i++) { + let menuItem = iconOnlyArray[i] + let angle = initialAngle - step * i + if (menuItem.angle === undefined) { + menuItem.angle = angle + } + } + } + /* Text and Icon */ + let amplitude = 140 + let initialAngle = -70 + step = amplitude / 7 + let currentItem = (7 - iconAndTextArray.length) / 2 + 1 + for (let i = 0; i < iconAndTextArray.length; i++) { + let menuItem = iconAndTextArray[i] + let angle = initialAngle + step * (currentItem - 0.5) + currentItem++ + if (menuItem.angle === undefined) { + menuItem.angle = angle + } + } + + iconOnlyArray = undefined + iconAndTextArray = undefined + + /* Listen to events */ + selfFocusEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onFocus', onFocus) + selfNotFocuskEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onNotFocus', onNotFocus) + } + + function getContainer(point) { + let container + + if (thisObject.isDeployed === true) { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + if (menuItem.visible === true) { + container = menuItem.getContainer(point) + } + if (container !== undefined) { return container } + } + } + } + + function internalClick(action) { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + if (menuItem.visible === true) { + if (menuItem.nextAction === action) { + menuItem.internalClick() + } + } + } + } + + function invisiblePhysics() { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + menuItem.invisiblePhysics() + } + } + + function physics() { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + menuItem.physics() + } + } + + function onFocus() { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + menuItem.targetRadius = Math.trunc(menuItem.rawRadius * 1.5) + menuItem.isDeployed = true + } + thisObject.isDeployed = true + } + + function onNotFocus() { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + menuItem.targetRadius = menuItem.rawRadius * 0 - i * 1.5 + menuItem.isDeployed = false + } + thisObject.isDeployed = false + } + + function drawBackground(pFloatingObject) { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + if (menuItem.visible === true) { + menuItem.drawBackground() + } + } + } + + function drawForeground(pFloatingObject) { + for (let i = 0; i < menuItems.length; i++) { + let menuItem = menuItems[i] + if (menuItem.visible === true) { + menuItem.drawForeground() + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularMenuItem.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularMenuItem.js new file mode 100644 index 000000000..4c87f78e6 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularMenuItem.js @@ -0,0 +1,492 @@ + +function newCircularMenuItem() { + const MODULE_NAME = 'Circular Menu Iem' + + let thisObject = { + type: undefined, + isDeployed: undefined, + askConfirmation: undefined, + confirmationLabel: undefined, + iconOn: undefined, + iconOff: undefined, + icons: undefined, + currentIcon: undefined, + disableIfPropertyIsDefined: undefined, + propertyToCheckFor: undefined, + action: undefined, + actionProject: undefined, + actionFunction: undefined, + actionStatus: undefined, + label: undefined, + workingLabel: undefined, + workDoneLabel: undefined, + workFailedLabel: undefined, + secondaryAction: undefined, + secondaryLabel: undefined, + secondaryWorkingLabel: undefined, + secondaryWorkDoneLabel: undefined, + secondaryWorkFailedLabel: undefined, + secondaryIcon: undefined, + nextAction: undefined, + visible: false, + iconPathOn: undefined, + iconPathOff: undefined, + rawRadius: undefined, + targetRadius: undefined, + currentRadius: undefined, + angle: undefined, + container: undefined, + payload: undefined, + relatedUiObject: undefined, + dontShowAtFullscreen: undefined, + isEnabled: true, + shorcutNumber: undefined, + internalClick: internalClick, + physics: physics, + invisiblePhysics: invisiblePhysics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = true + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + thisObject.container.frame.width = 0 + thisObject.container.frame.height = 0 + + let isMouseOver = false + + let selfMouseOverEventSubscriptionId + let selfMouseClickEventSubscriptionId + let selfMouseNotOverEventSubscriptionId + + let labelToPrint = '' + let defaultBackgroudColor = UI_COLOR.BLACK + let backgroundColorToUse = UI_COLOR.RED + let temporaryStatus = 0 + let temporaryStatusCounter = 0 + + const STATUS_NO_ACTION_TAKEN_YET = 0 + const STATUS_PRIMARY_ACTION_WORKING = -1 + const STATUS_SECONDARY_ACTION_WORKING = -2 + const STATUS_PRIMARY_WORK_DONE = -3 + const STATUS_PRIMARY_WORK_FAILED = -4 + const STATUS_WAITING_CONFIRMATION = -5 + const STATUS_SECONDARY_WORK_DONE = -6 + const STATUS_SECONDARY_WORK_FAILED = -7 + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(selfMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseClickEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseNotOverEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.iconOn = undefined + thisObject.iconOff = undefined + thisObject.icons = undefined + thisObject.currentIcon = undefined + thisObject.payload = undefined + thisObject.actionFunction = undefined + thisObject.actionStatus = undefined + thisObject.disableIfPropertyIsDefined = undefined + thisObject.propertyToCheckFor = undefined + } + + function initialize(pPayload) { + thisObject.payload = pPayload + + iconPhysics() + + selfMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + selfMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + selfMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + + containerPhysics() + + thisObject.nextAction = thisObject.action + } + + function getContainer(point) { + if (thisObject.dontShowAtFullscreen === true && AT_FULL_SCREEN_MODE === true) { return } + + let container + if (thisObject.isDeployed === true) { + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + return thisObject.container + } else { + return undefined + } + } + } + + function invisiblePhysics() { + temporaryStatusPhysics() + } + + function temporaryStatusPhysics() { + /* Temporary Status impacts on the label to use and the background of that label */ + if (temporaryStatusCounter > 0) { + temporaryStatusCounter-- + } + + if (temporaryStatusCounter === 0) { + temporaryStatus = STATUS_NO_ACTION_TAKEN_YET + labelToPrint = thisObject.label + backgroundColorToUse = defaultBackgroudColor + thisObject.nextAction = thisObject.action + } + } + + function physics() { + if (thisObject.dontShowAtFullscreen === true && AT_FULL_SCREEN_MODE === true) { return } + positionPhysics() + temporaryStatusPhysics() + disablePhysics() + iconPhysics() + backgroundColorPhysics() + containerPhysics() + } + + function positionPhysics() { + let INCREASE_STEP = 2 + + if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= INCREASE_STEP) { + if (thisObject.currentRadius < thisObject.targetRadius) { + thisObject.currentRadius = thisObject.currentRadius + INCREASE_STEP + } else { + thisObject.currentRadius = thisObject.currentRadius - INCREASE_STEP + } + } + + let radiusGrowthFactor + if (thisObject.type === 'Icon Only') { + switch (thisObject.ring) { + case 1: { + radiusGrowthFactor = 5.5 + break + } + case 2: { + radiusGrowthFactor = 4.0 + break + } + case 3: { + radiusGrowthFactor = 3.0 + break + } + case 4: { + radiusGrowthFactor = 2.0 + break + } + } + } else { + radiusGrowthFactor = 3.2 + } + radiusGrowthFactor = radiusGrowthFactor * UI.projects.superalgos.spaces.floatingSpace.settings.node.menuItem.radiusPercentage / 100 + + thisObject.container.frame.position.x = + thisObject.payload.floatingObject.targetRadius * + radiusGrowthFactor / 7 + * Math.cos(toRadians(thisObject.angle)) - + thisObject.currentRadius * 1.5 + + thisObject.container.frame.position.y = + thisObject.payload.floatingObject.targetRadius * + radiusGrowthFactor / 7 + * Math.sin(toRadians(thisObject.angle)) + - thisObject.container.frame.height / 2 + } + + function disablePhysics() { + /* Here we will check if we need to monitor a property that influences the status of the Menu Item. */ + if (thisObject.disableIfPropertyIsDefined === true) { + if (thisObject.payload.node[thisObject.propertyToCheckFor] === undefined) { + /* This menu item is enabled. */ + thisObject.isEnabled = true + } else { + /* This menu item is disabled. */ + thisObject.isEnabled = false + } + } + } + + function containerPhysics() { + if (thisObject.type === 'Icon & Text') { + thisObject.container.frame.width = 220 * UI.projects.superalgos.spaces.floatingSpace.settings.node.menuItem.widthPercentage / 100 + } else { + thisObject.container.frame.width = 50 * UI.projects.superalgos.spaces.floatingSpace.settings.node.menuItem.widthPercentage / 100 + } + + thisObject.container.frame.height = 40 * UI.projects.superalgos.spaces.floatingSpace.settings.node.menuItem.heightPercentage / 100 + } + + function backgroundColorPhysics() { + defaultBackgroudColor = UI.projects.superalgos.spaces.floatingSpace.style.node.menuItem.backgroundColor + } + + function iconPhysics() { + if ( + ( + temporaryStatus === STATUS_PRIMARY_WORK_DONE || + temporaryStatus === STATUS_SECONDARY_ACTION_WORKING || + temporaryStatus === STATUS_SECONDARY_WORK_DONE || + temporaryStatus === STATUS_SECONDARY_WORK_FAILED + ) && thisObject.secondaryAction !== undefined + ) { + thisObject.iconOn = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', thisObject.secondaryIcon) + thisObject.iconOff = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', thisObject.secondaryIcon) + } else { + if (thisObject.relatedUiObject !== undefined) { + thisObject.iconOn = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.relatedUiObject) + thisObject.iconOff = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.relatedUiObject) + } else { + if (thisObject.iconPathOn !== undefined && thisObject.iconPathOff !== undefined) { + thisObject.iconOn = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', thisObject.iconPathOn) + thisObject.iconOff = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', thisObject.iconPathOff) + } else { + thisObject.iconOn = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', thisObject.icons[thisObject.actionStatus()]) + thisObject.iconOff = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', thisObject.icons[thisObject.actionStatus()]) + } + } + } + + /* Current Status might be linked to some other object status */ + + if (thisObject.actionStatus !== undefined) { + thisObject.currentStatus = thisObject.actionStatus() + } + + /* Current Status sets the icon to be used */ + + if (thisObject.currentStatus === true) { + thisObject.icon = thisObject.iconOn + } else { + thisObject.icon = thisObject.iconOff + } + } + + function onMouseOver(point) { + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + isMouseOver = true + } else { + isMouseOver = false + } + MENU_ITEM_ON_FOCUS = thisObject + } + + function onMouseNotOver(point) { + isMouseOver = false + MENU_ITEM_ON_FOCUS = undefined + } + + function internalClick() { + if (thisObject.shorcutNumber !== undefined) { + let label = thisObject.payload.node.name + ' ' + labelToPrint + UI.projects.superalgos.spaces.cockpitSpace.setStatus(label, 4, UI.projects.superalgos.spaces.cockpitSpace.statusTypes.ALL_GOOD) + } + + onMouseClick() + } + + function onMouseClick() { + if (thisObject.isEnabled === false) { return } + + if (thisObject.label === undefined) { + /* This is what we have in the case of menu items that are only Icons. In this situation there is no complex logic, just execute the specified action. */ + thisObject.actionFunction({ node: thisObject.payload.node, name: thisObject.action, project: thisObject.actionProject, relatedNodeType: thisObject.relatedUiObject }) + return + } + + if (thisObject.askConfirmation !== true) { /* No confirmation is needed */ + if (temporaryStatus === STATUS_NO_ACTION_TAKEN_YET || temporaryStatus === STATUS_PRIMARY_WORK_DONE) { + executeAction() + } // Any click out of those states is ignored + } else { + /* Confirmation is needed */ + + /* The first click ask for confirmation. */ + if (temporaryStatus === STATUS_NO_ACTION_TAKEN_YET) { + setStatus(thisObject.confirmationLabel, UI_COLOR.GOLDEN_ORANGE, 100, STATUS_WAITING_CONFIRMATION) + return + } + /* A Click during confirmation executes the pre-defined action. */ + if (temporaryStatus === STATUS_WAITING_CONFIRMATION || temporaryStatus === STATUS_PRIMARY_WORK_DONE) { + executeAction() + if (thisObject.workDoneLabel !== undefined) { + setStatus(thisObject.workDoneLabel, UI_COLOR.PATINATED_TURQUOISE, 5, STATUS_SECONDARY_WORK_DONE) + } else { + setStatus('Done', UI_COLOR.PATINATED_TURQUOISE, 5, STATUS_SECONDARY_WORK_DONE) + } + return + } + } + + function executeAction() { + if (temporaryStatus === STATUS_NO_ACTION_TAKEN_YET || temporaryStatus === STATUS_WAITING_CONFIRMATION) { + /* We need to execute the main Action */ + /* If there is a working label defined, we use it here. */ + if (thisObject.workingLabel !== undefined) { + setStatus(thisObject.workingLabel, UI_COLOR.GREY, undefined, STATUS_PRIMARY_ACTION_WORKING) // Status will not expire, will only change with a callback. Mouse Clicks will be ignored. + } + + /* Execute the action and wait for callbacks to update our statuus. */ + thisObject.actionFunction({ node: thisObject.payload.node, name: thisObject.action, project: thisObject.actionProject, relatedNodeType: thisObject.relatedUiObject, callBackFunction: onPrimaryCallBack }) + return + } + if (temporaryStatus === STATUS_PRIMARY_WORK_DONE && thisObject.secondaryAction !== undefined) { + /* We need to execute the secondary action. */ + if (thisObject.secondaryWorkingLabel !== undefined) { + setStatus(thisObject.secondaryWorkingLabel, UI_COLOR.GREY, undefined, STATUS_SECONDARY_ACTION_WORKING) // Status will not expire, will only change with a callback. Mouse Clicks will be ignored. + } + + /* Execute the action and wait for callbacks to update our statuus. */ + thisObject.actionFunction({ node: thisObject.payload.node, name: thisObject.secondaryAction, project: thisObject.actionProject, relatedNodeType: thisObject.relatedUiObject, callBackFunction: onSecondaryCallBack }) + return + } + + function onPrimaryCallBack(err, event) { + /* While the primary action is being executed some event might have happen. Following we process the ones we can */ + + if (event !== undefined) { + if (event.type === 'Secondary Action Already Executed') { + setStatus(thisObject.secondaryWorkDoneLabel, UI_COLOR.PATINATED_TURQUOISE, 5, STATUS_SECONDARY_WORK_DONE) + return + } + } + + /* If there is a secondary action we will act different that if there is not */ + if (thisObject.secondaryAction === undefined) { // This means there are no more possible actions. + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + if (thisObject.workDoneLabel !== undefined) { + setStatus(thisObject.workDoneLabel, UI_COLOR.PATINATED_TURQUOISE, 5, STATUS_PRIMARY_WORK_DONE) + } + } else { + if (thisObject.workFailedLabel != undefined) { + setStatus(thisObject.workFailedLabel, UI_COLOR.TITANIUM_YELLOW, 5, STATUS_PRIMARY_WORK_FAILED) + } + } + } else { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + if (thisObject.workDoneLabel !== undefined) { + thisObject.nextAction = thisObject.secondaryAction + setStatus(thisObject.secondaryLabel, defaultBackgroudColor, undefined, STATUS_PRIMARY_WORK_DONE) + } + } else { + if (thisObject.workFailedLabel != undefined) { + setStatus(thisObject.workFailedLabel, UI_COLOR.TITANIUM_YELLOW, 5, STATUS_PRIMARY_WORK_FAILED) + } + } + } + } + function onSecondaryCallBack(err) { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + if (thisObject.secondaryWorkDoneLabel !== undefined) { + setStatus(thisObject.secondaryWorkDoneLabel, UI_COLOR.PATINATED_TURQUOISE, 5, STATUS_SECONDARY_WORK_DONE) + } + } else { + if (thisObject.secondaryWorkFailedLabel != undefined) { + setStatus(thisObject.secondaryWorkFailedLabel, UI_COLOR.TITANIUM_YELLOW, 5, STATUS_SECONDARY_WORK_FAILED) + } + } + } + } + } + + function setStatus(text, backgroundColor, waitingCycles, newStatus) { + labelToPrint = text + backgroundColorToUse = backgroundColor + temporaryStatus = newStatus + temporaryStatusCounter = newStatus // This will often put this into negatives numbers, which will disable the counting back and automatic reseting. + if (waitingCycles !== undefined) { // This will override the often negative value with a positive one that will tend to zero onto the default state. + temporaryStatusCounter = waitingCycles + } + } + + function drawBackground() { + if (thisObject.dontShowAtFullscreen === true && AT_FULL_SCREEN_MODE === true) { return } + + if (thisObject.container.frame.position.x > 0 && thisObject.isDeployed === true && thisObject.currentRadius >= thisObject.targetRadius) { + if (thisObject.type === 'Icon & Text') { + let backgroundColor = backgroundColorToUse + if (thisObject.isEnabled === false) { + backgroundColor = UI_COLOR.GREY + } + let params = { + cornerRadius: 5, + lineWidth: 0.1, + container: thisObject.container, + borderColor: backgroundColor, + backgroundColor: backgroundColor, + castShadow: false, + xOffset: 60 + } + + if (isMouseOver === true) { + params.opacity = 1 + } else { + params.opacity = 0.8 + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + } + } + } + + function drawForeground() { + if (thisObject.dontShowAtFullscreen === true && AT_FULL_SCREEN_MODE === true) { return } + + let menuPosition = { + x: thisObject.currentRadius * 1.5, + y: thisObject.container.frame.height / 2 + } + + menuPosition = thisObject.container.frame.frameThisPoint(menuPosition) + + /* Menu Item */ + let iconSize + if (isMouseOver === true) { + iconSize = UI.projects.superalgos.spaces.floatingSpace.style.node.menuItem.imageSize * 150 / 100 + } else { + iconSize = UI.projects.superalgos.spaces.floatingSpace.style.node.menuItem.imageSize + } + + if (thisObject.icon === undefined) { return } + if (thisObject.icon.canDrawIcon === true && thisObject.currentRadius > 1 && thisObject.isDeployed === true) { + browserCanvasContext.drawImage(thisObject.icon, menuPosition.x - iconSize, menuPosition.y - iconSize, iconSize * 2, iconSize * 2) + + /* Menu Label */ + if (thisObject.type === 'Icon & Text') { + label = labelToPrint + if (thisObject.shorcutNumber !== undefined) { + label = '' + thisObject.shorcutNumber + '- ' + labelToPrint + } + + let labelPoint + let fontSize = UI.projects.superalgos.spaces.floatingSpace.style.node.menuItem.fontSize + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + if (thisObject.currentRadius >= thisObject.targetRadius) { + labelPoint = { + x: menuPosition.x + thisObject.currentRadius + 25, + y: menuPosition.y + fontSize * FONT_ASPECT_RATIO + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI.projects.superalgos.spaces.floatingSpace.style.node.menuItem.fontColor + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularProgressBar.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularProgressBar.js new file mode 100644 index 000000000..883f72f9d --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CircularProgressBar.js @@ -0,0 +1,147 @@ + +function newCircularProgressBar() { + const MODULE_NAME = 'Circular Progress Bar' + + let thisObject = { + container: undefined, + isDeployed: undefined, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME, 'Circle') + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let opacityCounters = [] + let eventSubscriptionIdHeartbeat + let needToDrawRing = true + let eventsServerClient + + return thisObject + + function finalize() { + finalizeEventsServerClient() + + thisObject.container = undefined + thisObject.payload = undefined + thisObject.fitFunction = undefined + } + + function finalizeEventsServerClient() { + if (eventsServerClient !== undefined) { + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + if (eventSubscriptionIdHeartbeat !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdHeartbeat) + } + } + } + + function initialize(payload, pEventsServerClient) { + thisObject.payload = payload + + finalizeEventsServerClient() + eventsServerClient = pEventsServerClient + + for (let i = 0; i < 60; i++) { + opacityCounters.push(0) + } + + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Heartbeat', undefined, key, onResponse, onHeartBeat) + + function onResponse(message) { + eventSubscriptionIdHeartbeat = message.eventSubscriptionId + } + } + + function onHeartBeat(message) { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.uiObject === undefined) { return } + opacityCounters[message.event.seconds] = 2000 + if (message.event.processingDate !== undefined) { + thisObject.payload.uiObject.valueAtAngle = false + thisObject.payload.uiObject.setValue(message.event.processingDate, 2000) + } + if (message.event.percentage !== undefined) { + thisObject.payload.uiObject.setPercentage(message.event.percentage, 2000) + } + if (message.event.status !== undefined) { + needToDrawRing = false + thisObject.payload.uiObject.setStatus(message.event.status, 200000) + } + + thisObject.payload.uiObject.heartBeat() + } + + function getContainer(point) { + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { return } + let container + + for (let i = 0; i < menuItems.length; i++) { + let menutItem = menuItems[i] + container = menutItem.getContainer(point) + if (container !== undefined) { return container } + } + } + + function physics() { + for (let i = 0; i < 60; i++) { + opacityCounters[i] = opacityCounters[i] - 2 + + if (opacityCounters[i] < 0) { + opacityCounters[i] = 0 + } + } + } + + function drawForeground(pFloatingObject) { + + } + + function drawBackground(pFloatingObject) { + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { return } + if (needToDrawRing !== true) { return } + + const VISIBLE_RADIUS = thisObject.container.frame.radius + + let visiblePosition = { + x: thisObject.container.frame.position.x, + y: thisObject.container.frame.position.y + } + + visiblePosition = thisObject.container.frame.frameThisPoint(visiblePosition) + visiblePosition = thisObject.fitFunction(visiblePosition) + + for (let i = 0; i < 60; i++) { + let OPACITY = opacityCounters[i] / 4000 + + if (OPACITY === 0) { continue } + + let initialAngle = Math.PI * 2 / 60 * i - Math.PI / 2 + let finalAngle = Math.PI * 2 / 60 * (i + 1) - Math.PI / 2 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, initialAngle, finalAngle, false) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GOLDEN_ORANGE + ', ' + OPACITY * 2 + ')' + browserCanvasContext.lineWidth = 6 + opacityCounters[i] / 100 / 4 + browserCanvasContext.setLineDash([1, 10]) + browserCanvasContext.stroke() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 0 + opacityCounters[i] / 100 / 4 + browserCanvasContext.setLineDash([2, 10]) + browserCanvasContext.stroke() + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/CodeEditor.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CodeEditor.js new file mode 100644 index 000000000..0c21305ea --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/CodeEditor.js @@ -0,0 +1,200 @@ + +function newCodeEditor() { + const MODULE_NAME = 'Code Editor' + + let thisObject = { + isVisibleFunction: undefined, + visible: false, + imagePathOK: undefined, + imagePathNOT_OK: undefined, + rawRadius: undefined, + targetRadius: undefined, + currentRadius: undefined, + container: undefined, + payload: undefined, + deactivate: deactivate, + activate: activate, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = false + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let isMouseOver = false + let SIZE_FACTOR = 1.0 + + return thisObject + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.isVisibleFunction = undefined + } + + function initialize() { + + } + + function deactivate() { + if (thisObject.visible === true) { + thisObject.visible = false + let textArea = document.getElementById('textArea') + textArea.style.display = 'none' + thisObject.payload.node.code = textArea.value + } + EDITOR_ON_FOCUS = false + } + + function activate(action) { + thisObject.visible = true + thisObject.payload = action.node.payload + thisObject.rawRadius = 8 + thisObject.targetRadius = thisObject.container.frame.radius + thisObject.currentRadius = 0 + thisObject.payload.uiObject.resetErrorMessage() + + let textArea = document.getElementById('textArea') + textArea.value = thisObject.payload.node.code + textArea.style = 'resize: none;' + + ' border: none;' + + ' outline: none;' + + 'box-shadow: none;' + + 'overflow:hidden;' + + 'font-family: ' + UI_FONT.PRIMARY + ';' + + 'font-size: 14px;' + + 'background-color: rgb(' + UI_COLOR.RUSTED_RED + ');' + + 'color:rgb(255, 255, 255);' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + textArea.style.display = 'block' + textArea.focus() + EDITOR_ON_FOCUS = true + } + + function getContainer(point) { + let container + if (thisObject.visible === true) { + if (point.x === VERY_LARGE_NUMBER) { + /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that + anyone can react. In our case, the code editor has a text area that is not part of the canvas, so the event is + triggered. We compensate recognizing this coordinate and returning our container. */ + return thisObject.container + } + + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + return thisObject.container + } else { + return undefined + } + } + } + + function physics() { + if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= 0.5) { + if (thisObject.currentRadius < thisObject.targetRadius) { + thisObject.currentRadius = thisObject.currentRadius + 0.5 + } else { + thisObject.currentRadius = thisObject.currentRadius - 0.5 + } + } + + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + thisObject.container.frame.width = thisObject.container.frame.radius * 1 * SIZE_FACTOR + thisObject.container.frame.height = thisObject.container.frame.radius * 1 * SIZE_FACTOR + + let textAreaPosition = { + x: 0 - thisObject.container.frame.width / 2, + y: 0 - thisObject.container.frame.height * 3 / 7 + CURRENT_TOP_MARGIN + } + + textAreaPosition = thisObject.container.frame.frameThisPoint(textAreaPosition) + if (thisObject.visible === true) { + let checkPosition = { + x: textAreaPosition.x, + y: textAreaPosition.y - CURRENT_TOP_MARGIN + } + if (thisObject.isVisibleFunction(checkPosition) === false) { + deactivate() + } + } + if (thisObject.visible === true) { + let textAreaDiv = document.getElementById('textAreaDiv') + textAreaDiv.style = 'position:fixed; top:' + textAreaPosition.y + 'px; left:' + textAreaPosition.x + 'px; z-index:1; ' + } + } + + function drawBackground() { + if (thisObject.visible === true) { + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + let radius = thisObject.container.frame.radius * SIZE_FACTOR + + if (radius > 0.5) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius + 3, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius , 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + 1 + ')' + browserCanvasContext.fill() + } + } + } + + function drawForeground() { + let iconPosition = { + x: 0, + y: thisObject.currentRadius * 1.5 + } + + iconPosition = thisObject.container.frame.frameThisPoint(iconPosition) + + /* Menu Item */ + + if (thisObject.canDrawIcon === true && thisObject.currentRadius > 1) { + browserCanvasContext.drawImage(thisObject.icon, iconPosition.x - thisObject.currentRadius, iconPosition.y - thisObject.currentRadius, thisObject.currentRadius * 2, thisObject.currentRadius * 2) + + /* Menu Label */ + + let label = 'Code looks good!' + let labelPoint + let fontSize = 10 + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + if (thisObject.currentRadius >= thisObject.targetRadius) { + labelPoint = { + x: iconPosition.x + thisObject.currentRadius + 10, + y: iconPosition.y + fontSize * FONT_ASPECT_RATIO + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/ConditionEditor.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/ConditionEditor.js new file mode 100644 index 000000000..1d8446f3e --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/ConditionEditor.js @@ -0,0 +1,1198 @@ + +function newConditionEditor() { + const MODULE_NAME = 'Condition Editor' + + let thisObject = { + isVisibleFunction: undefined, + visible: false, + imagePathOK: undefined, + imagePathNOT_OK: undefined, + container: undefined, + payload: undefined, + deactivate: deactivate, + activate: activate, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME, 'Circle') + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = false + thisObject.container.frame.radius = 750 + thisObject.container.frame.width = thisObject.container.frame.radius * 2 + thisObject.container.frame.height = thisObject.container.frame.radius * 2 + thisObject.container.frame.containerName = MODULE_NAME + + let isMouseOver = false + let conditionStructure + let scanResult + + let timeFrameIcon + let dataMineIcon + let botIcon + let whenIcon + let productIcon + let propertyIcon + let operationIcon + let valueIcon + + const PICKER_WIDTH = 150 + const PICKER_HEIGHT = 60 + + return thisObject + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.isVisibleFunction = undefined + + conditionStructure = undefined + scanResult = undefined + + timeFrameIcon = undefined + dataMineIcon = undefined + whenIcon = undefined + botIcon = undefined + productIcon = undefined + operationIcon = undefined + propertyIcon = undefined + valueIcon = undefined + } + + function initialize() { + timeFrameIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'time-frame') + dataMineIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'data-mine') + botIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'indicator-bot') + whenIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'trigger-on') + productIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'product-definition') + propertyIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'record-property') + operationIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'space-settings') + valueIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'trading-strategy') + } + + function activate(action) { + thisObject.visible = true + thisObject.payload = action.node.payload + thisObject.payload.uiObject.resetErrorMessage() + + conditionStructure = { + code: '', + logicOperands: [] + } + + scanDataMines() + loadFromCode() + EDITOR_ON_FOCUS = true + } + + function deactivate() { + convertToCode() + finalizePickers() + + conditionStructure = undefined + + if (thisObject.visible === true) { + thisObject.visible = false + } + EDITOR_ON_FOCUS = false + } + + function loadFromCode() { + if (thisObject.payload.node.code === undefined) { thisObject.payload.node.code = '' } + conditionStructure.code = thisObject.payload.node.code.replace(/ /g, '') + + /* LOGICAL ORs */ + let orArray = conditionStructure.code.split('||') + for (let j = 0; j < 3; j++) { + let logicOperand = { + code: orArray[j], + index: j, + comparison: {}, + visible: false + } + + if (logicOperand.code !== undefined) { + logicOperand.visible = true + if (orArray[j + 1] !== undefined) { + logicOperand.operator = '||' + } + } else { + logicOperand.code = '' + } + + checkComparison(logicOperand, j) + conditionStructure.logicOperands.push(logicOperand) + } + + function checkComparison(logicOperand) { + /* We are going to decompose the code into 2 comparison operators */ + logicOperand.comparison.operandA = { + index: 0, + code: '' + } + logicOperand.comparison.operandB = { + index: 1, + code: '' + } + + if (logicOperand.code.indexOf('>') > 0) { + let codeArray = logicOperand.code.split('>') + logicOperand.comparison.operandA.code = codeArray[0] + logicOperand.comparison.operandB.code = codeArray[1] + logicOperand.comparison.operator = '>' + logicOperand.comparison.operatorIndex = 0 + } + + if (logicOperand.code.indexOf('<') > 0) { + let codeArray = logicOperand.code.split('<') + logicOperand.comparison.operandA.code = codeArray[0] + logicOperand.comparison.operandB.code = codeArray[1] + logicOperand.comparison.operator = '<' + logicOperand.comparison.operatorIndex = 1 + } + + if (logicOperand.code.indexOf('>=') > 0) { + let codeArray = logicOperand.code.split('>=') + logicOperand.comparison.operandA.code = codeArray[0] + logicOperand.comparison.operandB.code = codeArray[1] + logicOperand.comparison.operator = '>=' + logicOperand.comparison.operatorIndex = 2 + } + + if (logicOperand.code.indexOf('<=') > 0) { + let codeArray = logicOperand.code.split('<=') + logicOperand.comparison.operandA.code = codeArray[0] + logicOperand.comparison.operandB.code = codeArray[1] + logicOperand.comparison.operator = '<=' + logicOperand.comparison.operatorIndex = 3 + } + + if (logicOperand.code.indexOf('===') > 0) { + let codeArray = logicOperand.code.split('===') + logicOperand.comparison.operandA.code = codeArray[0] + logicOperand.comparison.operandB.code = codeArray[1] + logicOperand.comparison.operator = '===' + logicOperand.comparison.operatorIndex = 4 + } + + /* Each comparison code may have one or more algebra operators */ + checkAlgebra(logicOperand, logicOperand.comparison.operandA) + checkAlgebra(logicOperand, logicOperand.comparison.operandB) + } + + function checkAlgebra(logicOperand, comparisonOperand) { + comparisonOperand.algebra = { + operandA: { index: 0 }, + operandB: { index: 1 } + } + + /* The default situation is that there is no algebra operation, meaning we will setup a algebra operation only with operandA */ + comparisonOperand.algebra.operandA.code = comparisonOperand.code + comparisonOperand.algebra.operandB.code = '' + comparisonOperand.algebra.operator = '...' + comparisonOperand.algebra.operatorIndex = 0 + + if (comparisonOperand.code.indexOf('+') > 0) { + let codeArray = comparisonOperand.code.split('+') + comparisonOperand.algebra.operandA.code = codeArray[0] + comparisonOperand.algebra.operandB.code = codeArray[1] + comparisonOperand.algebra.operator = '+' + comparisonOperand.algebra.operatorIndex = 1 + } + + if (comparisonOperand.code.indexOf('-') > 0) { + let codeArray = comparisonOperand.code.split('-') + comparisonOperand.algebra.operandA.code = codeArray[0] + comparisonOperand.algebra.operandB.code = codeArray[1] + comparisonOperand.algebra.operator = '-' + comparisonOperand.algebra.operatorIndex = 2 + } + + if (comparisonOperand.code.indexOf('*') > 0) { + let codeArray = comparisonOperand.code.split('*') + comparisonOperand.algebra.operandA.code = codeArray[0] + comparisonOperand.algebra.operandB.code = codeArray[1] + comparisonOperand.algebra.operator = '*' + comparisonOperand.algebra.operatorIndex = 3 + } + + if (comparisonOperand.code.indexOf('/') > 0) { + let codeArray = comparisonOperand.code.split('/') + comparisonOperand.algebra.operandA.code = codeArray[0] + comparisonOperand.algebra.operandB.code = codeArray[1] + comparisonOperand.algebra.operator = '/' + comparisonOperand.algebra.operatorIndex = 4 + } + + initializePickersSet(logicOperand, comparisonOperand, comparisonOperand.algebra.operandA) + initializePickersSet(logicOperand, comparisonOperand, comparisonOperand.algebra.operandB) + updatePickers(logicOperand, comparisonOperand, comparisonOperand.algebra.operandA) + updatePickers(logicOperand, comparisonOperand, comparisonOperand.algebra.operandB) + } + + function initializePickersSet(logicOperand, comparisonOperand, algebraOperand) { + const ALGEBRA_SEPARATION = PICKER_HEIGHT + const COMPARISON_SEPARATION = ALGEBRA_SEPARATION * 2 + const LOGIC_SEPARATION = COMPARISON_SEPARATION * 2 + + let properties + let parent + let current + let yOffset = (logicOperand.index - 1) * LOGIC_SEPARATION + (comparisonOperand.index - 0.5) * COMPARISON_SEPARATION + (algebraOperand.index - 0.5) * ALGEBRA_SEPARATION + + if (algebraOperand.index === 0) { + comparisonOperand.algebra.picker = newPicker() + let picker = comparisonOperand.algebra.picker + picker.name = 'Algebra' + picker.container.connectToParent(thisObject.container) + picker.container.frame.position.x = 0 - picker.container.frame.width / 2 + picker.container.frame.width * 2.5 + picker.container.frame.position.y = 0 - picker.container.frame.height / 2 + yOffset + current = ['...', 'Plus', 'Minus', 'Times', 'Divided by'] + picker.initialize(current, current) + switch (comparisonOperand.algebra.operator) { + case '+': { + picker.setSelected(undefined, undefined, undefined, 1) + break + } + case '-': { + picker.setSelected(undefined, undefined, undefined, 2) + break + } + case '*': { + picker.setSelected(undefined, undefined, undefined, 3) + break + } + case '/': { + picker.setSelected(undefined, undefined, undefined, 4) + break + } + } + picker.visible = true + let structureBranch = { + logicOperand: logicOperand, + comparisonOperand: comparisonOperand, + algebraOperand: algebraOperand, + pickerName: 'Algebra' + } + picker.eventSuscriptionId = picker.container.eventHandler.listenToEvent('onParentChanged', onParentChanged, structureBranch) + } + + if (comparisonOperand.index === 0 && algebraOperand.index === 1) { + logicOperand.comparison.picker = newPicker() + let picker = logicOperand.comparison.picker + picker.name = 'Comparison' + picker.container.connectToParent(thisObject.container) + picker.container.frame.position.x = 0 - picker.container.frame.width / 2 + picker.container.frame.width * 2.5 + picker.container.frame.position.y = 0 - picker.container.frame.height / 2 + yOffset + let optionsList = ['Greater Than', 'Less Than', 'Greater or Equal Than', 'Less or Equal Than', 'Equal To'] + picker.initialize(optionsList) + switch (logicOperand.comparison.operator) { + case '>': { + picker.setSelected(undefined, undefined, undefined, 0) + break + } + case '<': { + picker.setSelected(undefined, undefined, undefined, 1) + break + } + case '>=': { + picker.setSelected(undefined, undefined, undefined, 2) + break + } + case '<=': { + picker.setSelected(undefined, undefined, undefined, 3) + break + } + case '===': { + picker.setSelected(undefined, undefined, undefined, 4) + break + } + } + picker.visible = true + let structureBranch = { + logicOperand: logicOperand, + comparisonOperand: comparisonOperand, + algebraOperand: algebraOperand, + pickerName: 'Comparison' + } + picker.eventSuscriptionId = picker.container.eventHandler.listenToEvent('onParentChanged', onParentChanged, structureBranch) + } + + if (comparisonOperand.index === 1 && algebraOperand.index === 1 && logicOperand.index <= 1) { + logicOperand.picker = newPicker() + let picker = logicOperand.picker + picker.name = 'Logic' + picker.container.connectToParent(thisObject.container) + picker.container.frame.position.x = 0 - picker.container.frame.width / 2 + picker.container.frame.width * 2.5 + picker.container.frame.position.y = 0 - picker.container.frame.height / 2 + yOffset + let optionsList = ['...', 'OR'] + picker.initialize(optionsList) + if (logicOperand.operator === '||') { + picker.setSelected(undefined, undefined, undefined, 1) + } + picker.visible = true + let structureBranch = { + logicOperand: logicOperand, + comparisonOperand: comparisonOperand, + algebraOperand: algebraOperand, + pickerName: 'Logic' + } + picker.eventSuscriptionId = picker.container.eventHandler.listenToEvent('onParentChanged', onParentChanged, structureBranch) + } + + let visible = true + if (algebraOperand.index === 1 && comparisonOperand.algebra.picker.getSelected() === '...') { visible = false } + if (logicOperand.comparison.picker !== undefined && comparisonOperand.index === 1 && logicOperand.comparison.picker.getSelected() === 'Equal To') { visible = false } + + algebraOperand.whenPicker = newPicker() + algebraOperand.whenPicker.name = 'When' + algebraOperand.whenPicker.container.connectToParent(thisObject.container) + algebraOperand.whenPicker.container.frame.position.x = 0 - algebraOperand.whenPicker.container.frame.width / 2 - algebraOperand.whenPicker.container.frame.width * 0.5 + algebraOperand.whenPicker.container.frame.position.y = 0 - algebraOperand.whenPicker.container.frame.height / 2 + yOffset + current = ['Current', '1 Previous', '2 Previous', '3 Previous', '4 Previous', '5 Previous'] + algebraOperand.whenPicker.initialize(current, current) + algebraOperand.whenPicker.visible = visible + + algebraOperand.dataMinePicker = newPicker() + algebraOperand.dataMinePicker.name = 'Data Mine' + algebraOperand.dataMinePicker.container.connectToParent(thisObject.container) + algebraOperand.dataMinePicker.container.frame.position.x = 0 - algebraOperand.dataMinePicker.container.frame.width / 2 - algebraOperand.dataMinePicker.container.frame.width * 2.5 + algebraOperand.dataMinePicker.container.frame.position.y = 0 - algebraOperand.dataMinePicker.container.frame.height / 2 + yOffset + current = scanResult + properties = Object.keys(current) + algebraOperand.dataMinePicker.initialize(properties, current) + parent = current + algebraOperand.dataMinePicker.visible = visible + + algebraOperand.botPicker = newPicker() + algebraOperand.botPicker.name = 'Bot' + algebraOperand.botPicker.container.connectToParent(thisObject.container) + algebraOperand.botPicker.container.frame.position.x = 0 - algebraOperand.botPicker.container.frame.width / 2 - algebraOperand.botPicker.container.frame.width * 1.5 + algebraOperand.botPicker.container.frame.position.y = 0 - algebraOperand.botPicker.container.frame.height / 2 + yOffset + current = parent[properties[0]] + properties = Object.keys(current) + algebraOperand.botPicker.initialize(properties, current, parent) + parent = current + algebraOperand.botPicker.visible = visible + + algebraOperand.productPicker = newPicker() + algebraOperand.productPicker.name = 'Product' + algebraOperand.productPicker.container.connectToParent(thisObject.container) + algebraOperand.productPicker.container.frame.position.x = 0 - algebraOperand.productPicker.container.frame.width / 2 + algebraOperand.productPicker.container.frame.width * 0.5 + algebraOperand.productPicker.container.frame.position.y = 0 - algebraOperand.productPicker.container.frame.height / 2 + yOffset + current = parent[properties[0]] + properties = Object.keys(current) + algebraOperand.productPicker.initialize(properties, current, parent) + parent = current + algebraOperand.productPicker.visible = visible + + let productParent = parent + let productProperties = properties + + algebraOperand.propertyPicker = newPicker() + algebraOperand.propertyPicker.name = 'Property' + algebraOperand.propertyPicker.container.connectToParent(thisObject.container) + algebraOperand.propertyPicker.container.frame.position.x = 0 - algebraOperand.propertyPicker.container.frame.width / 2 + algebraOperand.propertyPicker.container.frame.width * 1.5 + algebraOperand.propertyPicker.container.frame.position.y = 0 - algebraOperand.propertyPicker.container.frame.height / 2 + yOffset + current = productParent[productProperties[0]] + current = current.properties + properties = Object.keys(current) + algebraOperand.propertyPicker.initialize(properties, current, productParent, 'properties') + parent = current + algebraOperand.propertyPicker.visible = visible + + algebraOperand.valuePicker = newPicker() + algebraOperand.valuePicker.name = 'Value' + algebraOperand.valuePicker.container.connectToParent(thisObject.container) + algebraOperand.valuePicker.container.frame.position.x = 0 - algebraOperand.valuePicker.container.frame.width / 2 + algebraOperand.valuePicker.container.frame.width * 3.5 + algebraOperand.valuePicker.container.frame.position.y = 0 - algebraOperand.valuePicker.container.frame.height / 2 + yOffset + ALGEBRA_SEPARATION + current = parent[properties[0]] + properties = current.possibleValues + algebraOperand.valuePicker.initialize(properties, current, parent, 'possibleValues') + parent = current + algebraOperand.valuePicker.visible = false + if (logicOperand.comparison.picker !== undefined && logicOperand.comparison.picker.getSelected() === 'Equal To') { + comparisonOperand.algebra.operandA.valuePicker.visible = true + } + + algebraOperand.timeFramePicker = newPicker() + algebraOperand.timeFramePicker.name = 'Time Frame' + algebraOperand.timeFramePicker.container.connectToParent(thisObject.container) + algebraOperand.timeFramePicker.container.frame.position.x = 0 - algebraOperand.timeFramePicker.container.frame.width / 2 - algebraOperand.timeFramePicker.container.frame.width * 3.0 + algebraOperand.timeFramePicker.container.frame.position.y = 0 - algebraOperand.timeFramePicker.container.frame.height / 2 + yOffset + algebraOperand.timeFramePicker.container.frame.width = algebraOperand.timeFramePicker.container.frame.width / 2 + current = productParent[productProperties[0]] + properties = current.validTimeFrames + algebraOperand.timeFramePicker.initialize(properties, current, productParent, 'validTimeFrames') + parent = current + algebraOperand.timeFramePicker.visible = visible + + algebraOperand.botPicker.eventSuscriptionId = algebraOperand.dataMinePicker.container.eventHandler.listenToEvent('onParentChanged', algebraOperand.botPicker.onParentChanged) + algebraOperand.productPicker.eventSuscriptionId = algebraOperand.botPicker.container.eventHandler.listenToEvent('onParentChanged', algebraOperand.productPicker.onParentChanged) + algebraOperand.propertyPicker.eventSuscriptionId = algebraOperand.productPicker.container.eventHandler.listenToEvent('onParentChanged', algebraOperand.propertyPicker.onParentChanged) + algebraOperand.valuePicker.eventSuscriptionId = algebraOperand.propertyPicker.container.eventHandler.listenToEvent('onParentChanged', algebraOperand.valuePicker.onParentChanged) + algebraOperand.timeFramePicker.eventSuscriptionId = algebraOperand.productPicker.container.eventHandler.listenToEvent('onParentChanged', algebraOperand.timeFramePicker.onParentChanged) + } + + function updatePickers(logicOperand, comparisonOperand, algebraOperand) { + let codeArray = algebraOperand.code.split('.') + + let codeTimeFrame + if (codeArray[1] !== undefined) { + codeTimeFrame = codeArray[1].substring(2, 4) + '-' + codeArray[1].substring(4, 7) + } + + let codeProduct + if (codeArray[2] !== undefined) { + codeProduct = codeArray[2] + } + + /* Parse When */ + let propertyDisplacement = 0 + let codeWhen = 'Current' + if (codeArray[3] === 'previous') { + propertyDisplacement++ + codeWhen = '1 Previous' + } + if (codeArray[4] === 'previous') { + propertyDisplacement++ + codeWhen = '2 Previous' + } + if (codeArray[5] === 'previous') { + propertyDisplacement++ + codeWhen = '3 Previous' + } + if (codeArray[6] === 'previous') { + propertyDisplacement++ + codeWhen = '4 Previous' + } + if (codeArray[7] === 'previous') { + propertyDisplacement++ + codeWhen = '5 Previous' + } + algebraOperand.whenPicker.setSelected(undefined, undefined, undefined, propertyDisplacement) + + let codeProperty + if (codeArray[3 + propertyDisplacement] !== undefined) { + codeProperty = codeArray[3 + propertyDisplacement] + } + + let codeValue = logicOperand.comparison.operandB.code.replace('"', '').replace('"', '') + + let dataMines = Object.keys(scanResult) + for (let i = 0; i < dataMines.length; i++) { + let dataMine = dataMines[i] + let dataMineObject = scanResult[dataMine] + let bots = Object.keys(dataMineObject) + for (let j = 0; j < bots.length; j++) { + let bot = bots[j] + let botObject = dataMineObject[bot] + let products = Object.keys(botObject) + for (let k = 0; k < products.length; k++) { + let product = products[k] + let productObject = botObject[product] + if (codeProduct === product) { + algebraOperand.dataMinePicker.setSelected(dataMines, scanResult, undefined, i) + algebraOperand.botPicker.setSelected(bots, dataMineObject, scanResult, j) + algebraOperand.productPicker.setSelected(products, botObject, dataMineObject, k) + let properties = Object.keys(productObject.properties) + for (let m = 0; m < properties.length; m++) { + let property = properties[m] + let propertyObject = productObject.properties[property] + if (codeProperty === property) { + algebraOperand.propertyPicker.setSelected(properties, productObject.properties, botObject, m) + let values = propertyObject.possibleValues + for (let n = 0; n < values.length; n++) { + let value = values[n] + let valueObject = propertyObject[value] + if (codeValue === value) { + algebraOperand.valuePicker.setSelected(values, propertyObject, productObject, n) + } + } + } + } + let timeFrames = productObject.validTimeFrames + for (let m = 0; m < timeFrames.length; m++) { + let timeFrame = timeFrames[m] + let timeFrameObject = productObject.validTimeFrames[timeFrame] + if (codeTimeFrame === timeFrame) { + algebraOperand.timeFramePicker.setSelected(timeFrames, productObject, botObject, m) + } + } + } + } + } + } + } + } + + function scanDataMines() { + let selector = {} + + let workspace = UI.projects.superalgos.spaces.designSpace.workspace.workspaceNode + + for (let i = 0; i < workspace.rootNodes.length; i++) { + let rootNode = workspace.rootNodes[i] + if (rootNode.type !== 'Data Mine') { continue } + let dataMine = rootNode + let dataMineName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(dataMine.payload, 'codeName') + selector[dataMineName] = {} + let bots = dataMine.sensorBots.concat(dataMine.indicatorBots) + for (let j = 0; j < bots.length; j++) { + let bot = bots[j] + let botName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(bot.payload, 'codeName') + let selectorDataMine = selector[dataMineName] + selectorDataMine[botName] = {} + for (let k = 0; k < bot.products.length; k++) { + let product = bot.products[k] + let productName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(product.payload, 'singularVariableName') + if (productName === undefined) { continue } + let selectorProduct = selectorDataMine[botName] + selectorProduct[productName] = { + properties: {} + } + if (product.record === undefined) { continue } + for (let m = 0; m < product.record.properties.length; m++) { + let property = product.record.properties[m] + let propertyName = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(property.payload, 'codeName') + let selectorProperty = selectorProduct[productName] + selectorProperty = selectorProperty.properties + selectorProperty[propertyName] = {} + let possibleValues = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(property.payload, 'possibleValues') + if (possibleValues === undefined) { possibleValues = [] } + let selectorPossibleValue = selectorProperty[propertyName] + selectorPossibleValue.possibleValues = possibleValues + } + let allPossibleTimeFrames = [] + for (let m = 0; m < product.datasets.length; m++) { + let dataset = product.datasets[m] + let validTimeFrames = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(dataset.payload, 'validTimeFrames') + if (validTimeFrames !== undefined) { + allPossibleTimeFrames = allPossibleTimeFrames.concat(validTimeFrames) + } + } + let selectorValidTimeFrames = selectorProduct[productName] + selectorValidTimeFrames.validTimeFrames = allPossibleTimeFrames + + let productKeys = Object.keys(selectorProduct[productName]) + if (productKeys.length === 0) { + selectorProduct[productName] = undefined + } + } + let botKeys = Object.keys(selectorDataMine[botName]) + if (botKeys.length === 0) { + selectorDataMine[botName] = undefined + } + } + let dataMineKeys = Object.keys(selector[dataMineName]) + if (dataMineKeys.length === 0) { + selector[dataMineName] = undefined + } + } + scanResult = JSON.parse(JSON.stringify(selector)) + } + + function convertToCode() { + if (conditionStructure === undefined) { return } + + // chart.at01hs.bollingerSubChannel.previous.previous.slope=== "Extreme" + + let code = '' + + for (let i = 0; i < conditionStructure.logicOperands.length; i++) { + let logicOperand = conditionStructure.logicOperands[i] + logicOperandToCode(logicOperand) + if (logicOperand.picker.getSelected() !== 'OR') { + break + } else { + code = code + ' || ' + } + } + + thisObject.payload.node.code = code + + function logicOperandToCode(logicOperand) { + algebraOperandToCode(logicOperand.comparison.operandA.algebra.operandA) + if (logicOperand.comparison.operandA.algebra.picker.getSelected() !== '...') { + insertAlgebraOperator(logicOperand.comparison.operandA.algebra) + algebraOperandToCode(logicOperand.comparison.operandA.algebra.operandB) + } + + switch (logicOperand.comparison.picker.getSelected()) { + case 'Greater Than': { + code = code + ' > ' + break + } + case 'Less Than': { + code = code + ' < ' + break + } + case 'Greater or Equal Than': { + code = code + ' >= ' + break + } + case 'Less or Equal Than': { + code = code + ' <= ' + break + } + case 'Equal To': { + code = code + ' === ' + break + } + } + + if (logicOperand.comparison.picker.getSelected() === 'Equal To') { + code = code + '"' + logicOperand.comparison.operandA.algebra.operandA.valuePicker.getSelected() + '"' + } else { + algebraOperandToCode(logicOperand.comparison.operandB.algebra.operandA) + if (logicOperand.comparison.operandB.algebra.picker.getSelected() !== '...') { + insertAlgebraOperator(logicOperand.comparison.operandB.algebra) + algebraOperandToCode(logicOperand.comparison.operandB.algebra.operandB) + } + } + + function algebraOperandToCode(algebraOperand) { + if (algebraOperand.timeFramePicker.getSelected() === 'Any Time Frame') { + code = code + 'chart.atAnyTimeFrame' + } else { + code = code + 'chart.at' + algebraOperand.timeFramePicker.getSelected().replace('-', '') + } + + code = code + '.' + algebraOperand.productPicker.getSelected() + insertWhen(algebraOperand) + code = code + '.' + algebraOperand.propertyPicker.getSelected() + } + + function insertAlgebraOperator(algebra) { + switch (algebra.picker.getSelected()) { + case '...': { + return + } + case 'Plus': { + code = code + ' + ' + return + } + case 'Minus': { + code = code + ' - ' + return + } + case 'Times': { + code = code + ' * ' + return + } + case 'Divided by': { + code = code + ' / ' + return + } + } + } + function insertWhen(algebraOperand) { + switch (algebraOperand.whenPicker.getSelected()) { + case 'Current': { + return + } + case '1 Previous': { + code = code + '.previous' + return + } + case '2 Previous': { + code = code + '.previous.previous' + return + } + case '3 Previous': { + code = code + '.previous.previous.previous' + return + } + case '4 Previous': { + code = code + '.previous.previous.previous.previous' + return + } + case '5 Previous': { + code = code + '.previous.previous.previous.previous.previous' + return + } + } + } + } + } + + function finalizePickers() { + if (conditionStructure === undefined) { return } + for (let i = 0; i < conditionStructure.logicOperands.length; i++) { + let logicOperand = conditionStructure.logicOperands[i] + finalizeOperator(logicOperand.comparison.operandA.algebra.operandA) + finalizeOperator(logicOperand.comparison.operandA.algebra.operandB) + finalizeOperator(logicOperand.comparison.operandB.algebra.operandA) + finalizeOperator(logicOperand.comparison.operandB.algebra.operandB) + + if (logicOperand.comparison.operandA.algebra.picker !== undefined) { + logicOperand.comparison.operandA.algebra.picker.container.eventHandler.stopListening(logicOperand.comparison.operandA.algebra.picker.eventSuscriptionId) + logicOperand.comparison.operandA.algebra.picker.finalize() + } + + if (logicOperand.comparison.operandB.algebra.picker !== undefined) { + logicOperand.comparison.operandB.algebra.picker.container.eventHandler.stopListening(logicOperand.comparison.operandB.algebra.picker.eventSuscriptionId) + logicOperand.comparison.operandB.algebra.picker.finalize() + } + + if (logicOperand.comparison.picker !== undefined) { + logicOperand.comparison.picker.container.eventHandler.stopListening(logicOperand.comparison.picker.eventSuscriptionId) + logicOperand.comparison.picker.finalize() + } + + if (logicOperand.picker !== undefined) { + logicOperand.picker.container.eventHandler.stopListening(logicOperand.picker.eventSuscriptionId) + logicOperand.picker.finalize() + } + } + } + + function finalizeOperator(operand) { + if (operand === undefined) { return } + + if (operand.whenPicker !== undefined) { + operand.whenPicker.finalize() + operand.whenPicker = undefined + } + + if (operand.dataMinePicker !== undefined) { + operand.dataMinePicker.container.eventHandler.stopListening(operand.botPicker.eventSuscriptionId) + operand.dataMinePicker.finalize() + operand.dataMinePicker = undefined + } + + if (operand.botPicker !== undefined) { + operand.botPicker.container.eventHandler.stopListening(operand.productPicker.eventSuscriptionId) + operand.botPicker.finalize() + operand.botPicker = undefined + } + + if (operand.productPicker !== undefined) { + operand.productPicker.container.eventHandler.stopListening(operand.propertyPicker.eventSuscriptionId) + operand.productPicker.container.eventHandler.stopListening(operand.timeFramePicker.eventSuscriptionId) + operand.productPicker.finalize() + operand.productPicker = undefined + } + + if (operand.propertyPicker !== undefined) { + operand.propertyPicker.container.eventHandler.stopListening(operand.valuePicker.eventSuscriptionId) + operand.propertyPicker.finalize() + operand.propertyPicker = undefined + } + + if (operand.valuePicker !== undefined) { + operand.valuePicker.finalize() + operand.valuePicker = undefined + } + + if (operand.timeFramePicker !== undefined) { + operand.timeFramePicker.finalize() + operand.timeFramePicker = undefined + } + } + + function onParentChanged(event, structureBranch) { + let logicOperand = structureBranch.logicOperand + + if (structureBranch.pickerName === 'Logic') { + if (event.selected === 0) { + if (logicOperand.index === 0) { + conditionStructure.logicOperands[1].visible = false + conditionStructure.logicOperands[2].visible = false + } + if (logicOperand.index === 1) { + conditionStructure.logicOperands[2].visible = false + } + } else { + if (logicOperand.index === 0) { + conditionStructure.logicOperands[1].visible = true + + if (conditionStructure.logicOperands[1].picker.getSelected() !== '...') { + conditionStructure.logicOperands[2].visible = true + } + } + if (logicOperand.index === 1) { + conditionStructure.logicOperands[2].visible = true + } + } + } + + if (structureBranch.pickerName === 'Algebra') { + if (event.selected === 0) { + setVisibility(structureBranch.comparisonOperand.algebra.operandB, false) + } else { + setVisibility(structureBranch.comparisonOperand.algebra.operandB, true) + } + } + + if (structureBranch.pickerName === 'Comparison') { + if (event.selected === 4) { + // this means Equal To + + logicOperand.comparison.operandA.algebra.operandA.valuePicker.visible = true + logicOperand.comparison.operandA.algebra.picker.visible = false + logicOperand.comparison.operandB.algebra.picker.visible = false + setVisibility(logicOperand.comparison.operandA.algebra.operandB, false) + setVisibility(logicOperand.comparison.operandB.algebra.operandA, false) + setVisibility(logicOperand.comparison.operandB.algebra.operandB, false) + } else { + logicOperand.comparison.operandA.algebra.operandA.valuePicker.visible = false + logicOperand.comparison.operandA.algebra.picker.visible = true + logicOperand.comparison.operandB.algebra.picker.visible = true + if (logicOperand.comparison.operandA.algebra.picker.getSelected() !== '...') { + setVisibility(logicOperand.comparison.operandA.algebra.operandB, true) + } + + if (logicOperand.comparison.picker.getSelected() !== 'Equal To') { + setVisibility(logicOperand.comparison.operandB.algebra.operandA, true) + } + + if (logicOperand.comparison.operandB.algebra.picker.getSelected() !== '...') { + setVisibility(logicOperand.comparison.operandB.algebra.operandB, true) + } + } + } + + function setLogicOperanVisibility(logicOperand, isVisible) { + + } + + function setVisibility(algebraOperand, isVisible) { + algebraOperand.whenPicker.visible = isVisible + algebraOperand.dataMinePicker.visible = isVisible + algebraOperand.botPicker.visible = isVisible + algebraOperand.productPicker.visible = isVisible + algebraOperand.propertyPicker.visible = isVisible + algebraOperand.timeFramePicker.visible = isVisible + } + } + + function getContainer(point) { + let container + if (thisObject.visible === true) { + if (conditionStructure === undefined) { return } + + for (let i = 0; i < conditionStructure.logicOperands.length; i++) { + let logicOperand = conditionStructure.logicOperands[i] + if (logicOperand.visible !== true) { continue } + + container = operandGetContainer(point, logicOperand.comparison.operandA.algebra.operandA) + if (container !== undefined) { return container } + container = operandGetContainer(point, logicOperand.comparison.operandA.algebra.operandB) + if (container !== undefined) { return container } + container = operandGetContainer(point, logicOperand.comparison.operandB.algebra.operandA) + if (container !== undefined) { return container } + container = operandGetContainer(point, logicOperand.comparison.operandB.algebra.operandB) + if (container !== undefined) { return container } + + if (container = logicOperand.comparison.operandA.algebra.picker !== undefined) { + if (container = logicOperand.comparison.operandA.algebra.picker.visible === true) { + container = logicOperand.comparison.operandA.algebra.picker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (logicOperand.comparison.operandB.algebra.picker !== undefined) { + if (container = logicOperand.comparison.operandB.algebra.picker.visible === true) { + container = logicOperand.comparison.operandB.algebra.picker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (container = logicOperand.comparison.picker !== undefined) { + if (container = logicOperand.comparison.picker.visible === true) { + container = logicOperand.comparison.picker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (container = logicOperand.picker !== undefined) { + if (container = logicOperand.picker.visible === true) { + container = logicOperand.picker.getContainer(point) + if (container !== undefined) { return container } + } + } + } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + } + + function operandGetContainer(point, operand) { + let container + if (operand.whenPicker !== undefined) { + if (operand.whenPicker.visible === true) { + container = operand.whenPicker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (operand.dataMinePicker !== undefined) { + if (operand.dataMinePicker.visible === true) { + container = operand.dataMinePicker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (operand.botPicker !== undefined) { + if (operand.botPicker.visible === true) { + container = operand.botPicker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (operand.productPicker !== undefined) { + if (operand.productPicker.visible === true) { + container = operand.productPicker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (operand.propertyPicker !== undefined) { + if (operand.propertyPicker.visible === true) { + container = operand.propertyPicker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (operand.valuePicker !== undefined) { + if (operand.valuePicker.visible === true) { + container = operand.valuePicker.getContainer(point) + if (container !== undefined) { return container } + } + } + + if (operand.timeFramePicker !== undefined) { + if (operand.timeFramePicker.visible === true) { + container = operand.timeFramePicker.getContainer(point) + if (container !== undefined) { return container } + } + } + } + + function physics() { + + } + + function drawBackground() { + thisObjectDrawBackground() + childrenDrawBackground() + } + + function childrenDrawBackground() { + if (conditionStructure === undefined) { return } + + for (let i = 0; i < conditionStructure.logicOperands.length; i++) { + let logicOperand = conditionStructure.logicOperands[i] + if (logicOperand.visible !== true) { continue } + + operandDrawBackground(logicOperand.comparison.operandA.algebra.operandA) + operandDrawBackground(logicOperand.comparison.operandA.algebra.operandB) + operandDrawBackground(logicOperand.comparison.operandB.algebra.operandA) + operandDrawBackground(logicOperand.comparison.operandB.algebra.operandB) + + if (logicOperand.comparison.operandA.algebra.picker !== undefined) { + if (logicOperand.comparison.operandA.algebra.picker.visible === true) { + logicOperand.comparison.operandA.algebra.picker.drawBackground() + } + } + if (logicOperand.comparison.operandB.algebra.picker !== undefined) { + if (logicOperand.comparison.operandB.algebra.picker.visible === true) { + logicOperand.comparison.operandB.algebra.picker.drawBackground() + } + } + if (logicOperand.comparison.picker !== undefined) { + if (logicOperand.comparison.picker.visible === true) { + logicOperand.comparison.picker.drawBackground() + } + } + if (logicOperand.picker !== undefined) { + if (logicOperand.picker.visible === true) { + logicOperand.picker.drawBackground() + } + } + } + } + + function operandDrawBackground(operand) { + if (operand === undefined) { return } + + if (operand.whenPicker !== undefined) { + if (operand.whenPicker.visible === true) { + operand.whenPicker.drawBackground() + } + } + + if (operand.dataMinePicker !== undefined) { + if (operand.dataMinePicker.visible === true) { + operand.dataMinePicker.drawBackground() + } + } + + if (operand.botPicker !== undefined) { + if (operand.botPicker.visible === true) { + operand.botPicker.drawBackground() + } + } + + if (operand.productPicker !== undefined) { + if (operand.productPicker.visible === true) { + operand.productPicker.drawBackground() + } + } + + if (operand.propertyPicker !== undefined) { + if (operand.propertyPicker.visible === true) { + operand.propertyPicker.drawBackground() + } + } + + if (operand.valuePicker !== undefined) { + if (operand.valuePicker.visible === true) { + operand.valuePicker.drawBackground() + } + } + + if (operand.timeFramePicker !== undefined) { + if (operand.timeFramePicker.visible === true) { + operand.timeFramePicker.drawBackground() + } + } + } + + function thisObjectDrawBackground() { + if (thisObject.visible === true) { + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + let radius = thisObject.container.frame.radius + + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius + 3, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREY + ', ' + 1 + ')' + browserCanvasContext.fill() + } + } + + function drawForeground() { + thisObjectDrawForeground() + childrenDrawForeground() + } + + function childrenDrawForeground() { + if (conditionStructure === undefined) { return } + + for (let i = 0; i < conditionStructure.logicOperands.length; i++) { + let logicOperand = conditionStructure.logicOperands[i] + if (logicOperand.visible !== true) { continue } + + operandDrawForeground(logicOperand.comparison.operandA.algebra.operandA) + operandDrawForeground(logicOperand.comparison.operandA.algebra.operandB) + operandDrawForeground(logicOperand.comparison.operandB.algebra.operandA) + operandDrawForeground(logicOperand.comparison.operandB.algebra.operandB) + + if (logicOperand.comparison.operandA.algebra.picker !== undefined) { + if (logicOperand.comparison.operandA.algebra.picker.visible === true) { + logicOperand.comparison.operandA.algebra.picker.drawForeground() + } + } + if (logicOperand.comparison.operandB.algebra.picker !== undefined) { + if (logicOperand.comparison.operandB.algebra.picker.visible === true) { + logicOperand.comparison.operandB.algebra.picker.drawForeground() + } + } + if (logicOperand.comparison.picker !== undefined) { + if (logicOperand.comparison.picker.visible === true) { + logicOperand.comparison.picker.drawForeground() + } + } + if (logicOperand.picker !== undefined) { + if (logicOperand.picker.visible === true) { + logicOperand.picker.drawForeground() + } + } + } + } + + function operandDrawForeground(operand) { + if (operand === undefined) { return } + + if (operand.whenPicker !== undefined) { + if (operand.whenPicker.visible === true) { + operand.whenPicker.drawForeground() + } + } + + if (operand.dataMinePicker !== undefined) { + if (operand.dataMinePicker.visible === true) { + operand.dataMinePicker.drawForeground() + } + } + + if (operand.botPicker !== undefined) { + if (operand.botPicker.visible === true) { + operand.botPicker.drawForeground() + } + } + + if (operand.productPicker !== undefined) { + if (operand.productPicker.visible === true) { + operand.productPicker.drawForeground() + } + } + + if (operand.propertyPicker !== undefined) { + if (operand.propertyPicker.visible === true) { + operand.propertyPicker.drawForeground() + } + } + + if (operand.valuePicker !== undefined) { + if (operand.valuePicker.visible === true) { + operand.valuePicker.drawForeground() + } + } + + if (operand.timeFramePicker !== undefined) { + if (operand.timeFramePicker.visible === true) { + operand.timeFramePicker.drawForeground() + } + } + } + + function thisObjectDrawForeground() { + if (thisObject.visible === true) { + const SIZE = 36 + UI.projects.superalgos.utilities.drawPrint.drawIcon(timeFrameIcon, 0, 0, -PICKER_WIDTH * 3.25, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(dataMineIcon, 0, 0, -PICKER_WIDTH * 2.5, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(botIcon, 0, 0, -PICKER_WIDTH * 1.5, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(whenIcon, 0, 0, -PICKER_WIDTH * 0.5, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(productIcon, 0, 0, PICKER_WIDTH * 0.5, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(propertyIcon, 0, 0, PICKER_WIDTH * 1.45, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(operationIcon, 0, 0, PICKER_WIDTH * 2.45, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(valueIcon, 0, 0, PICKER_WIDTH * 3.45, -PICKER_HEIGHT * 7, SIZE, thisObject.container) + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/ConfigEditor.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/ConfigEditor.js new file mode 100644 index 000000000..94eb25c9a --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/ConfigEditor.js @@ -0,0 +1,205 @@ + +function newConfigEditor() { + const MODULE_NAME = 'Config Editor' + + let thisObject = { + isVisibleFunction: undefined, + visible: false, + imagePathOK: undefined, + imagePathNOT_OK: undefined, + rawRadius: undefined, + targetRadius: undefined, + currentRadius: undefined, + container: undefined, + payload: undefined, + deactivate: deactivate, + activate: activate, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = false + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let isMouseOver = false + let SIZE_FACTOR = 1 + + return thisObject + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.isVisibleFunction = undefined + } + + function initialize() { + + } + + function deactivate() { + if (thisObject.visible === true) { + thisObject.visible = false + let textArea = document.getElementById('textArea') + textArea.style.display = 'none' + thisObject.payload.node.config = textArea.value + + try { + let config = JSON.parse(thisObject.payload.node.config) + } catch (err) { + thisObject.payload.uiObject.setErrorMessage(err.message, 10) + } + } + EDITOR_ON_FOCUS = false + } + + function activate(action) { + thisObject.visible = true + thisObject.payload = action.node.payload + thisObject.rawRadius = 8 + thisObject.targetRadius = thisObject.container.frame.radius + thisObject.currentRadius = 0 + thisObject.payload.uiObject.resetErrorMessage() + + let textArea = document.getElementById('textArea') + textArea.value = thisObject.payload.node.config + textArea.style = 'resize: none;' + + ' border: none;' + + ' outline: none;' + + 'box-shadow: none;' + + 'overflow:hidden;' + + 'font-family: ' + UI_FONT.PRIMARY + ';' + + 'font-size: 14px;' + + 'background-color: rgb(' + UI_COLOR.DARK_TURQUOISE + ');' + + 'color:rgb(255, 255, 255);' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + textArea.style.display = 'block' + textArea.focus() + EDITOR_ON_FOCUS = true + } + + function getContainer(point) { + let container + if (thisObject.visible === true) { + if (point.x === VERY_LARGE_NUMBER) { + /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that + anyone can react. In our case, the config editor has a text area that is not part of the canvas, so the event is + triggered. We compensate recognizing this coordinate and returning our container. */ + return thisObject.container + } + + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + return thisObject.container + } else { + return undefined + } + } + } + + function physics() { + if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= 0.5) { + if (thisObject.currentRadius < thisObject.targetRadius) { + thisObject.currentRadius = thisObject.currentRadius + 0.5 + } else { + thisObject.currentRadius = thisObject.currentRadius - 0.5 + } + } + + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + thisObject.container.frame.width = thisObject.container.frame.radius * 1 * SIZE_FACTOR + thisObject.container.frame.height = thisObject.container.frame.radius * 1 * SIZE_FACTOR + + let textAreaPosition = { + x: 0 - thisObject.container.frame.width / 2, + y: 0 - thisObject.container.frame.height * 3 / 7 + CURRENT_TOP_MARGIN + } + + textAreaPosition = thisObject.container.frame.frameThisPoint(textAreaPosition) + if (thisObject.visible === true) { + let checkPosition = { + x: textAreaPosition.x, + y: textAreaPosition.y - CURRENT_TOP_MARGIN + } + if (thisObject.isVisibleFunction(checkPosition) === false) { + deactivate() + } + } + if (thisObject.visible === true) { + let textAreaDiv = document.getElementById('textAreaDiv') + textAreaDiv.style = 'position:fixed; top:' + textAreaPosition.y + 'px; left:' + textAreaPosition.x + 'px; z-index:1; ' + } + } + + function drawBackground() { + if (thisObject.visible === true) { + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + let radius = thisObject.container.frame.radius * SIZE_FACTOR + + if (radius > 0.5) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius + 3, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius , 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + 1 + ')' + browserCanvasContext.fill() + } + } + } + function drawForeground() { + let iconPosition = { + x: 0, + y: thisObject.currentRadius * 1.5 + } + + iconPosition = thisObject.container.frame.frameThisPoint(iconPosition) + + /* Menu Item */ + + if (thisObject.canDrawIcon === true && thisObject.currentRadius > 1) { + browserCanvasContext.drawImage(thisObject.icon, iconPosition.x - thisObject.currentRadius, iconPosition.y - thisObject.currentRadius, thisObject.currentRadius * 2, thisObject.currentRadius * 2) + + /* Menu Label */ + + let label = 'Code looks good!' + let labelPoint + let fontSize = 10 + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + if (thisObject.currentRadius >= thisObject.targetRadius) { + labelPoint = { + x: iconPosition.x + thisObject.currentRadius + 10, + y: iconPosition.y + fontSize * FONT_ASPECT_RATIO + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingLayer.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingLayer.js new file mode 100644 index 000000000..d35a23697 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingLayer.js @@ -0,0 +1,752 @@ +/* + This module represent one layer of many possible layers that could be at the Floating Space. Objects in the same layer are subject to + the same physics, and by being at the same level, they interact with each other. For example, they can bounce when one hit the other. + This module posesses its own physics engine. There are different types of floating objects, but this layer takes care of each of them. + + Floating Objects Types: + + 1. Profile Balls: These are small balls with a profile picture, and two levels, one above and one below the ball. They are usually used + to specify the location of a bot instance. + + 2. Notes: Notes are a rectangular area where some text is posted. It has a subject and a body. + + 3. UI Objects: These are small balls that represent parts of an strategy. + */ + +function newFloatingLayer() { + const MODULE_NAME = 'Floating Layer' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + addFloatingObject: addFloatingObject, + removeFloatingObject: removeFloatingObject, + getFloatingObject: getFloatingObject, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + /* + When objects are added to this layer they are added to the invisibleFloatingObjects array and they stay there because they are invisible. + Visibility of objects is not decided at this layer, but outside. The ones who decides which object is visible or not are plotters, because + they know the position of objects and the coordinate system which these positions belong to. + + To save resources, the physics engine only apply its calculations to visible floating objects. + */ + + let invisibleFloatingObjects = [] + + /* + Floating objects are accesible to plotters because they carry a "payload" object that was creted by them. This object includes the position + and visible property. When visible turns to true, this layer module will move the reference of the floating object from the invisible array + to the visibleFloatingObjects array. If the plotter decides that the object is to far from the what the user is seeing, it can eventually + turn the visible property off again, forcing the layer to remove the object from the visibleFloatingObjects and place it again at the invisible + array. + */ + + let visibleFloatingObjects = [] + + let maxTargetRepulsionForce = 0.001 + let currentHandle = 0 + let invisiblePhysicsIntervalId + + return thisObject + + function finalize() { + clearInterval(invisiblePhysicsIntervalId) + invisibleFloatingObjects = [] + visibleFloatingObjects = [] + } + + function initialize() { + invisiblePhysicsIntervalId = setInterval(invisiblePhysics, 10000) + } + + function getContainer(point) { + let container + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + container = floatingObject.getContainer(point) + if (container !== undefined) { return container } + } + + return container + } + + function addFloatingObject(pFloatingObject) { + currentHandle++ + pFloatingObject.handle = currentHandle // Math.floor((Math.random() * 10000000) + 1); + visibleFloatingObjects.push(pFloatingObject) + } + + function removeFloatingObject(pFloatingObjectHandle) { + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + + if (floatingObject.handle === pFloatingObjectHandle) { + invisibleFloatingObjects.splice(i, 1) // Delete item from array. + return + } + } + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + + if (floatingObject.handle === pFloatingObjectHandle) { + visibleFloatingObjects.splice(i, 1) // Delete item from array. + return + } + } + } + + function getFloatingObject(pFloatingObjectHandle, pFloatingObjectIndex) { + /* + There are two ways to look for a floating object: by handle or by index. The search by handle is done on the visible and invisible array. + The search by index is done only on the visible ones. The search by index is usually needed when mouse events occurs. In those cases + only objects visible to the end usser matters. + */ + + try { + if (pFloatingObjectHandle !== undefined) { + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + + if (floatingObject.handle === pFloatingObjectHandle) { + return floatingObject + } + } + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + + if (floatingObject.handle === pFloatingObjectHandle) { + return floatingObject + } + } + } + + if (pFloatingObjectIndex !== undefined) { + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + + if (i === pFloatingObjectIndex) { + return floatingObject + } + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] getFloatingObject -> err= ' + err.stack) } + } + } + + function draw() { + drawInvisibleObjects() + drawVisibleObjects() + } + + function drawInvisibleObjects() { + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + floatingObject.drawBackground() + } + + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + floatingObject.drawMiddleground() + } + + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[invisibleFloatingObjects.length - i - 1] + floatingObject.drawForeground() + } + } + + function drawVisibleObjects() { + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + floatingObject.drawBackground() + } + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + floatingObject.drawMiddleground() + } + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[visibleFloatingObjects.length - i - 1] + floatingObject.drawForeground() + } + + /* Invisible objects on focus (freezed) should still have some priority */ + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + floatingObject.drawOnFocus() + } + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + floatingObject.drawOnFocus() + } + } + + function collapsePhysics(thisObject) { + let parent = thisObject.payload.chainParent + if (parent === undefined) { return } + if (parent.payload === undefined) { return } + if (parent.payload.floatingObject === undefined) { return } + + thisObject.isParentCollapsed = parent.payload.floatingObject.isCollapsed + if (thisObject.collapsedManually === false) { + thisObject.isCollapsed = parent.payload.floatingObject.isCollapsed + } + } + + function makeVisible() { + let newInvisibleArray = [] + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + + collapsePhysics(floatingObject) + + if (floatingObject.isParentCollapsed === false) { + visibleFloatingObjects.push(floatingObject) + } else { + newInvisibleArray.push(floatingObject) + } + } + invisibleFloatingObjects = newInvisibleArray + } + + function makeInvisible() { + let newVisibleArray = [] + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + + collapsePhysics(floatingObject) + + if (floatingObject.isParentCollapsed === true) { + invisibleFloatingObjects.push(floatingObject) + } else { + newVisibleArray.push(floatingObject) + } + } + visibleFloatingObjects = newVisibleArray + } + + /******************************************/ + /* */ + /* Physics Engine Follows */ + /* */ + /******************************************/ + + function invisiblePhysics() { + /* + Note that the Physics for invisible objects is called with a fixed time interval, + since it does not need the high intensity physics called during the animation. + */ + for (let i = 0; i < invisibleFloatingObjects.length; i++) { + let floatingObject = invisibleFloatingObjects[i] + floatingObject.invisiblePhysics() + } + } + + function physics() { + /* + The Physics engine is hooked at the animation loop, so it executes very very often. According to this Physics engine, each + floating object has a position and a speed. In fact, each floating object has two positions at the same time: + + 1. The current position: is the position of the ball, floating around. + 2. The anchor position: is the position of where the ball is achored on the plot. This one is inside the payload object controlled by the + plotter. + + The anchor position influences the current position, since the Physics engine will apply a gravity force to the ball's position in order + to make it gravitate towards the anchor position. At the same time there will be a repulsion force that will prevent the ball reaching + the anchor position, so that the anchor point keeps visible to the user. + + */ + + try { + makeVisible() + makeInvisible() + visiblePhysics() + enginePhysics() + proximityPhysics() + + function visiblePhysics() { + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + floatingObject.physics() + } + } + + function proximityPhysics(){ + for (let i = 0; i < visibleFloatingObjects.length; i++) { + proximityBetweenFloatingObjects(i) + } + } + + function enginePhysics() { + /* This function makes all the calculations to apply phisycs on all visible floatingObjects in this layer. */ + + try { + DEBUG.variable1 = 'Invisible Floating Objets: ' + invisibleFloatingObjects.length + DEBUG.variable2 = 'Visible Floating Objets: ' + visibleFloatingObjects.length + + if (UI.projects.superalgos.spaces.floatingSpace.settings.physics !== true) {return} + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + let floatingObject = visibleFloatingObjects[i] + if (floatingObject.isFrozen === true) { continue } + + /* From here on, only if they are not too far. */ + if (UI.projects.superalgos.spaces.floatingSpace.isItFar(floatingObject.payload)) { continue } + + if (floatingObject.positionLocked === false) { + floatingObject.container.frame.position.x = floatingObject.container.frame.position.x + floatingObject.currentSpeed.x + floatingObject.container.frame.position.y = floatingObject.container.frame.position.y + floatingObject.currentSpeed.y + } + + if (floatingObject.friction < floatingObject.targetFriction) { + floatingObject.friction = floatingObject.friction + 0.00001 + } else { + floatingObject.friction = floatingObject.friction - 0.00001 + } + + floatingObject.currentSpeed.x = floatingObject.currentSpeed.x * floatingObject.friction // Desaceleration factor. + floatingObject.currentSpeed.y = floatingObject.currentSpeed.y * floatingObject.friction // Desaceleration factor. + + let payload = { + position: undefined, + visible: false + } + + switch (floatingObject.type) { + case 'UI Object': { + payload.targetPosition = floatingObject.payload.targetPosition + payload.visible = floatingObject.payload.visible + break + } + default: { + break + } + } + + if (floatingObject.container.frame.position.x < payload.targetPosition.x) { + floatingObject.currentSpeed.x = floatingObject.currentSpeed.x + 0.005 + } else { + floatingObject.currentSpeed.x = floatingObject.currentSpeed.x - 0.005 + } + + if (floatingObject.container.frame.position.y < payload.targetPosition.y) { + floatingObject.currentSpeed.y = floatingObject.currentSpeed.y + 0.005 + } else { + floatingObject.currentSpeed.y = floatingObject.currentSpeed.y - 0.005 + } + + const MAX_SPEED = 10 + + if (floatingObject.currentSpeed.x > MAX_SPEED) { + floatingObject.currentSpeed.x = MAX_SPEED + } + + if (floatingObject.currentSpeed.y > MAX_SPEED) { + floatingObject.currentSpeed.y = MAX_SPEED + } + + if (floatingObject.currentSpeed.x < -MAX_SPEED) { + floatingObject.currentSpeed.x = -MAX_SPEED + } + + if (floatingObject.currentSpeed.y < -MAX_SPEED) { + floatingObject.currentSpeed.y = -MAX_SPEED + } + + // We let the Floating Object animate the physics loops by itself. + checkBoundaries(floatingObject) + + /* Collision Control */ + + for (let k = i + 1; k < visibleFloatingObjects.length; k++) { + if (colliding(visibleFloatingObjects[i], visibleFloatingObjects[k])) { + resolveCollision(visibleFloatingObjects[k], visibleFloatingObjects[i]) + } + } + + /* Calculate repulsion force produced by all other floatingObjects */ + + repulsionForceBetweenFloatingObjects(i) + + targetRepulsionForce(i) + + gravityForce(floatingObject, payload) + + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] physics -> applyPhysics -> err= ' + err.stack) } + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] physics -> err= ' + err.stack) } + } + } + + function checkBoundaries(floatingObject) { + if (floatingObject.container.frame.position.x - floatingObject.container.frame.radius < 0) { + floatingObject.container.frame.position.x = floatingObject.container.frame.radius + floatingObject.currentSpeed.x = -floatingObject.currentSpeed.x + } + + if (floatingObject.container.frame.position.x + floatingObject.container.frame.radius > UI.projects.superalgos.spaces.floatingSpace.container.frame.width) { + floatingObject.container.frame.position.x = UI.projects.superalgos.spaces.floatingSpace.container.frame.width - floatingObject.container.frame.radius + floatingObject.currentSpeed.x = -floatingObject.currentSpeed.x + } + + if (floatingObject.container.frame.position.y - floatingObject.container.frame.radius < 0) { + floatingObject.container.frame.position.y = floatingObject.container.frame.radius + floatingObject.currentSpeed.y = -floatingObject.currentSpeed.y + } + + if (floatingObject.container.frame.position.y + floatingObject.container.frame.radius > UI.projects.superalgos.spaces.floatingSpace.container.frame.height) { + floatingObject.container.frame.position.y = UI.projects.superalgos.spaces.floatingSpace.container.frame.height - floatingObject.container.frame.radius + floatingObject.currentSpeed.y = -floatingObject.currentSpeed.y + } + } + + function gravityForce(floatingObject, payload) { + try { + /* We simulate a kind of gravity towards the target point of each floatingObject. This force will make the floatingObject to keep pushing to reach that point. */ + + const coulomb = 0.00001 + const minForce = 0.01 + + let d = Math.sqrt(Math.pow(payload.targetPosition.x - floatingObject.container.frame.position.x, 2) + Math.pow(payload.targetPosition.y - floatingObject.container.frame.position.y, 2)) // ... we calculate the distance ... + + let force = coulomb * d * d / floatingObject.currentMass // In this case the mass of the floatingObject affects the gravity force that it receives, that gives priority to target position to bigger floatingObjects. + + if (force < minForce) { // We need this attraction force to overcome the friction we imposed to the system. + force = minForce + } + + let pos1 = { + x: floatingObject.container.frame.position.x, + y: floatingObject.container.frame.position.y + } + + let pos2 = { + x: payload.targetPosition.x, + y: payload.targetPosition.y + } + + let posDiff = { // Next we need the vector resulting from the 2 positions. + x: pos2.x - pos1.x, + y: pos2.y - pos1.y + } + + if (d !== 0) { + let unitVector = { // To find the unit vector, we divide each component by the magnitude of the vector. + x: posDiff.x / d, + y: posDiff.y / d + } + + let forceVector = { + x: unitVector.x * force, + y: unitVector.y * force + } + + /* We add the force vector to the speed vector */ + if (isNaN(forceVector.x) === false && isNaN(forceVector.y) === false) { + floatingObject.currentSpeed.x = floatingObject.currentSpeed.x + forceVector.x + floatingObject.currentSpeed.y = floatingObject.currentSpeed.y + forceVector.y + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] gravityForce -> err= ' + err.stack) } + } + } + + function repulsionForceBetweenFloatingObjects(currentFloatingObject) { + try { + /* We generate a repulsion force between floatingObjects, that prevents them to be collisioning so often. */ + + const coulomb = 2 + + let floatingObject1 = visibleFloatingObjects[currentFloatingObject] + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + // The force to be applied is considering all other floatingObjects... + + if (i !== currentFloatingObject) { + // ... except for the current one. + + let floatingObject2 = visibleFloatingObjects[i] // So, for each floatingObject... + + let d = Math.sqrt(Math.pow(floatingObject2.container.frame.position.x - floatingObject1.container.frame.position.x, 2) + Math.pow(floatingObject2.container.frame.position.y - floatingObject1.container.frame.position.y, 2)) // ... we calculate the distance ... + + let force = coulomb * floatingObject2.currentMass / (d * d) // ... and with it the repulsion force. + + /* We need to put a hard limit to this force, in order to to eject very little floatingObjects to the infinite and beyond. */ + + if (force > 1) { + force = 1 + } + + let pos1 = { + x: floatingObject1.container.frame.position.x, + y: floatingObject1.container.frame.position.y + } + + let pos2 = { + x: floatingObject2.container.frame.position.x, + y: floatingObject2.container.frame.position.y + } + + let posDiff = { // Next we need the vector resulting from the 2 positions. + x: pos2.x - pos1.x, + y: pos2.y - pos1.y + } + + let unitVector = { // To find the unit vector, we divide each component by the magnitude of the vector. + x: posDiff.x / d, + y: posDiff.y / d + } + + let forceVector = { + x: unitVector.x * force, + y: unitVector.y * force + } + + /* We substract the force vector to the speed vector of the current floatingObject */ + if (isNaN(forceVector.x) === false && isNaN(forceVector.y) === false) { + floatingObject1.currentSpeed.x = floatingObject1.currentSpeed.x - forceVector.x + floatingObject1.currentSpeed.y = floatingObject1.currentSpeed.y - forceVector.y + } + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] repulsionForceBetweenFloatingObjects -> err= ' + err.stack) } + } + } + + function proximityBetweenFloatingObjects(currentFloatingObject) { + try { + let floatingObject1 = visibleFloatingObjects[currentFloatingObject] + floatingObject1.nearbyFloatingObjects = [] + for (let i = 0; i < visibleFloatingObjects.length; i++) { + if (i !== currentFloatingObject) { + let floatingObject2 = visibleFloatingObjects[i] // So, for each floatingObject... + + let d = Math.sqrt(Math.pow(floatingObject2.container.frame.position.x - floatingObject1.container.frame.position.x, 2) + Math.pow(floatingObject2.container.frame.position.y - floatingObject1.container.frame.position.y, 2)) // ... we calculate the distance ... + + if (floatingObject1.nearbyFloatingObjects.length === 0) { + floatingObject1.nearbyFloatingObjects.push([d, floatingObject2]) + } else { + let recordAdded = false + for (let j = 0; j < floatingObject1.nearbyFloatingObjects.length; j++) { + let recordedDistance = floatingObject1.nearbyFloatingObjects[j][0] + if (d < recordedDistance) { + floatingObject1.nearbyFloatingObjects.splice(j, 0, [d, floatingObject2]) + recordAdded = true + break + } + } + if (recordAdded === false) { + floatingObject1.nearbyFloatingObjects.push([d, floatingObject2]) + } + } + floatingObject1.nearbyFloatingObjects.splice(10, floatingObject1.nearbyFloatingObjects.length) + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] proximityBetweenFloatingObjects -> err= ' + err.stack) } + } + } + + function targetRepulsionForce(currentFloatingObject) { + try { + /* We generate a repulsion force between floatingObjects, that prevents them to be collisioning so often. */ + + const coulomb = 2 + + let floatingObject1 = visibleFloatingObjects[currentFloatingObject] + + for (let i = 0; i < visibleFloatingObjects.length; i++) { + // The force to be applied is considering all other floatingObjects... + + let floatingObject2 = visibleFloatingObjects[i] // So, for each floatingObject... + + let payload = { + position: undefined + } + + switch (floatingObject2.type) { + case 'UI Object': { + payload.targetPosition = floatingObject2.payload.targetPosition + break + } + default: { + break + } + } + + let d = Math.sqrt(Math.pow(payload.targetPosition.x - floatingObject1.container.frame.position.x, 2) + Math.pow(payload.targetPosition.y - floatingObject1.container.frame.position.y, 2)) // ... we calculate the distance ... + + let force = coulomb * floatingObject2.currentMass / (d * d) // ... and with it the repulsion force. + + /* We need to put a hard limit to this force, in order to to eject very little floatingObjects to the infinite and beyond. */ + + if (force > maxTargetRepulsionForce) { + force = maxTargetRepulsionForce + } + + let pos1 = { + x: floatingObject1.container.frame.position.x, + y: floatingObject1.container.frame.position.y + } + + let pos2 = { + x: payload.targetPosition.x, + y: payload.targetPosition.y + } + + let posDiff = { // Next we need the vector resulting from the 2 positions. + x: pos2.x - pos1.x, + y: pos2.y - pos1.y + } + + if (d !== 0) { + let unitVector = { // To find the unit vector, we divide each component by the magnitude of the vector. + x: posDiff.x / d, + y: posDiff.y / d + } + + let forceVector = { + x: unitVector.x * force, + y: unitVector.y * force + } + + /* We substract the force vector to the speed vector of the current floatingObject */ + + if (isNaN(forceVector.x) === false && isNaN(forceVector.y) === false) { + floatingObject1.currentSpeed.x = floatingObject1.currentSpeed.x - forceVector.x + floatingObject1.currentSpeed.y = floatingObject1.currentSpeed.y - forceVector.y + } + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] targetRepulsionForce -> err= ' + err.stack) } + } + } + + function colliding(floatingObject1, floatingObject2) { + try { + /* This function detects weather 2 floatingObjects collide with each other. */ + + let r1 = floatingObject1.container.frame.radius + let r2 = floatingObject2.container.frame.radius + + let distance = Math.sqrt(Math.pow(floatingObject2.container.frame.position.x - floatingObject1.container.frame.position.x, 2) + Math.pow(floatingObject2.container.frame.position.y - floatingObject1.container.frame.position.y, 2)) + + if (distance > (r1 + r2)) { + // No solutions, the circles are too far apart. + return false + } else if (distance <= r1 + r2) { + // One circle contains the other. + return true + } else if ((distance === 0) && (r1 === r2)) { + // the circles coincide. + return true + } else return true + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] colliding -> err= ' + err.stack) } + } + } + + function distance(x1, y1, x2, y2) { + return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2)) + } + + function resolveCollision(floatingObject1, floatingObject2) { + try { + /* This function changes speed and position of floatingObjects that are in collision */ + + let collisionision_angle = Math.atan2((floatingObject2.container.frame.position.y - floatingObject1.container.frame.position.y), (floatingObject2.container.frame.position.x - floatingObject1.container.frame.position.x)) + + let speed1 = Math.sqrt(floatingObject1.currentSpeed.x * floatingObject1.currentSpeed.x + floatingObject1.currentSpeed.y * floatingObject1.currentSpeed.y) // Magnitude of Speed Vector for floatingObject 1 + let speed2 = Math.sqrt(floatingObject2.currentSpeed.x * floatingObject2.currentSpeed.x + floatingObject2.currentSpeed.y * floatingObject2.currentSpeed.y) // Magnitude of Speed Vector for floatingObject 2 + + let direction_1 = Math.atan2(floatingObject1.currentSpeed.y, floatingObject1.currentSpeed.x) + let direction_2 = Math.atan2(floatingObject2.currentSpeed.y, floatingObject2.currentSpeed.x) + + let new_xspeed_1 = speed1 * Math.cos(direction_1 - collisionision_angle) + let new_yspeed_1 = speed1 * Math.sin(direction_1 - collisionision_angle) + let new_xspeed_2 = speed2 * Math.cos(direction_2 - collisionision_angle) + let new_yspeed_2 = speed2 * Math.sin(direction_2 - collisionision_angle) + + let final_xspeed_1 = ((floatingObject1.currentMass - floatingObject2.currentMass) * new_xspeed_1 + (floatingObject2.currentMass + floatingObject2.currentMass) * new_xspeed_2) / (floatingObject1.currentMass + floatingObject2.currentMass) + let final_xspeed_2 = ((floatingObject1.currentMass + floatingObject1.currentMass) * new_xspeed_1 + (floatingObject2.currentMass - floatingObject1.currentMass) * new_xspeed_2) / (floatingObject1.currentMass + floatingObject2.currentMass) + let final_yspeed_1 = new_yspeed_1 + let final_yspeed_2 = new_yspeed_2 + + let cosAngle = Math.cos(collisionision_angle) + let sinAngle = Math.sin(collisionision_angle) + + floatingObject1.currentSpeed.x = cosAngle * final_xspeed_1 - sinAngle * final_yspeed_1 + floatingObject1.currentSpeed.y = sinAngle * final_xspeed_1 + cosAngle * final_yspeed_1 + + floatingObject2.currentSpeed.x = cosAngle * final_xspeed_2 - sinAngle * final_yspeed_2 + floatingObject2.currentSpeed.y = sinAngle * final_xspeed_2 + cosAngle * final_yspeed_2 + + let pos1 = { + x: floatingObject1.container.frame.position.x, + y: floatingObject1.container.frame.position.y + } + + let pos2 = { + x: floatingObject2.container.frame.position.x, + y: floatingObject2.container.frame.position.y + } + + // get the mtd + let posDiff = { + x: pos1.x - pos2.x, + y: pos1.y - pos2.y + } + + let d = Math.sqrt(Math.pow(floatingObject2.container.frame.position.x - floatingObject1.container.frame.position.x, 2) + Math.pow(floatingObject2.container.frame.position.y - floatingObject1.container.frame.position.y, 2)) + + // minimum translation distance to push floatingObjects apart after intersecting + let scalar = (((floatingObject1.container.frame.radius + floatingObject2.container.frame.radius) - d) / d) + + let minTD = { + x: posDiff.x * scalar, + y: posDiff.y * scalar + } + + // resolve intersection -- + // computing inverse mass quantities + let im1 = 1 / floatingObject1.currentMass + let im2 = 1 / floatingObject2.currentMass + + // push-pull them apart based off their mass + + pos1.x = pos1.x + minTD.x * (im1 / (im1 + im2)) + pos1.y = pos1.y + minTD.y * (im1 / (im1 + im2)) + pos2.x = pos2.x - minTD.x * (im2 / (im1 + im2)) + pos2.y = pos2.y - minTD.y * (im2 / (im1 + im2)) + + if (floatingObject1.positionLocked === false && isNaN(pos1.x) === false && isNaN(pos1.y) === false) { + floatingObject1.container.frame.position = pos1 + } + if (floatingObject2.positionLocked === false && isNaN(pos2.x) === false && isNaN(pos2.y) === false) { + floatingObject2.container.frame.position = pos2 + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] resolveCollision -> err= ' + err.stack) } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingObject.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingObject.js new file mode 100644 index 000000000..6ec2ca682 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingObject.js @@ -0,0 +1,829 @@ + +function newFloatingObject() { + const MODULE_NAME = 'Floating Object' + const ERROR_LOG = true + + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + fitFunction: undefined, + container: undefined, + positionLocked: false, + isOnFocus: false, + payload: undefined, // This is a reference to an object controlled by a Plotter. The plotter can change its internal value and we will see them from here. + type: undefined, // Currently there are two types of Floating Objects: Profile Balls, and Notes. + currentSpeed: 0, // This is the current speed of the floating object. + currentMass: 0, // This is the current mass of the floating object, including its zoom applied. + friction: 0, // This is a factor that will ultimatelly desacelerate the floating object. + targetFriction: 0, + rawMass: 0, // This is the mass value without zoom. + rawRadius: 0, // This is the radius of this floating object without zoom. + targetRadius: 0, // This is the target radius of the floating object with zoom applied. It should be animated until reaching this value. + isPinned: false, + isFrozen: false, + angleToParent: undefined, + distanceToParent: undefined, + arrangementStyle: ARRANGEMENT_STYLE.CONCAVE, + isCollapsed: false, + isParentCollapsed: false, + frozenManually: false, + collapsedManually: false, + typeStrokeStyle: undefined, + nameStrokeStyle: undefined, + forceFocus: forceFocus, + removeForceFocus: removeForceFocus, + setFocus: setFocus, + removeFocus: removeFocus, + unCollapseParent: unCollapseParent, + getPinStatus: getPinStatus, + getFreezeStatus: getFreezeStatus, + getCollapseStatus: getCollapseStatus, + getAngleToParent: getAngleToParent, + getDistanceToParent: getDistanceToParent, + getArrangementStyle: getArrangementStyle, + nearbyFloatingObjects: [], + setPosition: setPosition, + pinToggle: pinToggle, + freezeToggle: freezeToggle, + collapseToggle: collapseToggle, + angleToParentToggle: angleToParentToggle, + distanceToParentToggle: distanceToParentToggle, + arrangementStyleToggle: arrangementStyleToggle, + physics: physics, + invisiblePhysics: invisiblePhysics, + initializeMass: initializeMass, + initializeRadius: initializeRadius, + initializeImageSize: initializeImageSize, + initializeFontSize: initializeFontSize, + initializeCurrentPosition: initializeCurrentPosition, + radomizeCurrentSpeed: radomizeCurrentSpeed, + drawBackground: drawBackground, + drawMiddleground: drawMiddleground, + drawForeground: drawForeground, + drawOnFocus: drawOnFocus, + updateMass: updateMass, // Function to update the mass when the zoom level changed. + updateRadius: updateRadius, // Function to update the radius when the zoom level changed. + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME, 'Circle') + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = true + thisObject.container.detectMouseOver = true + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + thisObject.typeStrokeStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + thisObject.nameStrokeStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + + let selfMouseOverEventSubscriptionId + let selfMouseClickEventSubscriptionId + let spaceMouseOverEventSubscriptionId + let spaceFocusAquiredEventSubscriptionId + let lastParentAngle + let forcedFocusCounter = 0 + + const ON_FOCUS_RADIUS_FACTOR = 6 + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(selfMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseClickEventSubscriptionId) + UI.projects.superalgos.spaces.floatingSpace.container.eventHandler.stopListening(spaceMouseOverEventSubscriptionId) + UI.projects.superalgos.spaces.floatingSpace.container.eventHandler.stopListening(spaceFocusAquiredEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.fitFunction = undefined + thisObject.nearbyFloatingObjects = undefined + } + + function initialize(type, payload) { + thisObject.payload = payload + thisObject.type = type + + selfMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + selfMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + + /* To consider that this object lost the focus, we monitor the space for 2 key events */ + spaceMouseOverEventSubscriptionId = UI.projects.superalgos.spaces.floatingSpace.container.eventHandler.listenToEvent('onMouseOver', mouseOverFlotingSpace) + spaceFocusAquiredEventSubscriptionId = UI.projects.superalgos.spaces.floatingSpace.container.eventHandler.listenToEvent('onFocusAquired', someoneAquiredFocus) + + /* Assign a position and speed */ + + thisObject.initializeCurrentPosition(thisObject.payload.targetPosition) + thisObject.radomizeCurrentSpeed() + } + + function getContainer(point) { + + + if (thisObject.payload === undefined) { return } + if ((thisObject.isCollapsed === true && thisObject.collapsedManually === false) || thisObject.isParentCollapsed === true) { return } + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { return } + + let container + + container = thisObject.payload.uiObject.getContainer(point) + if (container !== undefined) { return container } + + if (thisObject.container.frame.isThisScreenPointHere(point) === true) { + return thisObject.container + } else { + return undefined + } + } + + function getPinStatus() { + return thisObject.isPinned + } + + function pinToggle() { + if (thisObject.isPinned !== true) { + thisObject.isPinned = true + thisObject.positionLocked = true + } else { + thisObject.isPinned = false + } + return thisObject.isPinned + } + + function getFreezeStatus() { + return thisObject.isFrozen + } + + function getCollapseStatus() { + return thisObject.isCollapsed + } + + function unCollapseParent() { + if (thisObject.payload !== undefined) { + if (thisObject.payload.parentNode !== undefined) { + if (thisObject.payload.parentNode.payload !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject.isCollapsed === true) { + thisObject.payload.parentNode.payload.floatingObject.isCollapsed = false + thisObject.payload.parentNode.payload.floatingObject.collapsedManually = false + thisObject.payload.parentNode.payload.floatingObject.unCollapseParent() + } + } + } + } + } + } + + function getAngleToParent() { + return thisObject.angleToParent + } + + function getDistanceToParent() { + return thisObject.distanceToParent + } + + function getArrangementStyle() { + return thisObject.arrangementStyle + } + + function freezeToggle() { + if (thisObject.isFrozen !== true) { + thisObject.isFrozen = true + thisObject.frozenManually = true + } else { + thisObject.isFrozen = false + thisObject.frozenManually = false + } + return thisObject.isFrozen + } + + function collapseToggle() { + if (thisObject.isCollapsed !== true) { + thisObject.isCollapsed = true + thisObject.collapsedManually = true + } else { + thisObject.isCollapsed = false + thisObject.collapsedManually = false + } + return thisObject.isCollapsed + } + + function angleToParentToggle() { + switch (thisObject.angleToParent) { + case ANGLE_TO_PARENT.NOT_FIXED: + thisObject.angleToParent = ANGLE_TO_PARENT.RANGE_360 + break + case ANGLE_TO_PARENT.RANGE_360: + thisObject.angleToParent = ANGLE_TO_PARENT.RANGE_180 + break + case ANGLE_TO_PARENT.RANGE_180: + thisObject.angleToParent = ANGLE_TO_PARENT.RANGE_90 + break + case ANGLE_TO_PARENT.RANGE_90: + thisObject.angleToParent = ANGLE_TO_PARENT.RANGE_45 + break + case ANGLE_TO_PARENT.RANGE_45: + thisObject.angleToParent = ANGLE_TO_PARENT.NOT_FIXED + break + } + + return thisObject.angleToParent + } + + function distanceToParentToggle() { + switch (thisObject.distanceToParent) { + case DISTANCE_TO_PARENT.NOT_FIXED: + thisObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_025X + break + case DISTANCE_TO_PARENT.PARENT_025X: + thisObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_050X + break + case DISTANCE_TO_PARENT.PARENT_050X: + thisObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_100X + break + case DISTANCE_TO_PARENT.PARENT_100X: + thisObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_150X + break + case DISTANCE_TO_PARENT.PARENT_150X: + thisObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_200X + break + case DISTANCE_TO_PARENT.PARENT_200X: + thisObject.distanceToParent = DISTANCE_TO_PARENT.NOT_FIXED + break + } + + return thisObject.distanceToParent + } + + function arrangementStyleToggle() { + switch (thisObject.arrangementStyle) { + case ARRANGEMENT_STYLE.CONCAVE: + thisObject.arrangementStyle = ARRANGEMENT_STYLE.CONVEX + break + case ARRANGEMENT_STYLE.CONVEX: + thisObject.arrangementStyle = ARRANGEMENT_STYLE.VERTICAL_RIGHT + break + case ARRANGEMENT_STYLE.VERTICAL_RIGHT: + thisObject.arrangementStyle = ARRANGEMENT_STYLE.VERTICAL_LEFT + break + case ARRANGEMENT_STYLE.VERTICAL_LEFT: + thisObject.arrangementStyle = ARRANGEMENT_STYLE.HORIZONTAL_BOTTOM + break + case ARRANGEMENT_STYLE.HORIZONTAL_BOTTOM: + thisObject.arrangementStyle = ARRANGEMENT_STYLE.HORIZONTAL_TOP + break + case ARRANGEMENT_STYLE.HORIZONTAL_TOP: + thisObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + break + } + + return thisObject.arrangementStyle + } + + function invisiblePhysics() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.uiObject === undefined) { return } + + if (thisObject.payload.uiObject !== undefined) { + thisObject.payload.uiObject.invisiblePhysics() + } + } + + function physics() { + frozenPhysics() + /* From here on, only if they are not too far. */ + if (UI.projects.superalgos.spaces.floatingSpace.isItFar(thisObject.payload)) { return } + thisObjectPhysics() + if (thisObject.payload.uiObject !== undefined) { + thisObject.payload.uiObject.physics() + } + positionContraintsPhysics() + focusPhysics() + syncStylePhysics() + } + + function syncStylePhysics() { + if (thisObject.rawFontSize !== UI.projects.superalgos.spaces.floatingSpace.style.node.fontSize) { + thisObject.rawFontSize = UI.projects.superalgos.spaces.floatingSpace.style.node.fontSize + thisObject.targetFontSize = thisObject.rawFontSize * 1 + } + if (thisObject.rawImageSize !== UI.projects.superalgos.spaces.floatingSpace.style.node.imageSize) { + thisObject.rawImageSize = UI.projects.superalgos.spaces.floatingSpace.style.node.imageSize + thisObject.targetImageSize = thisObject.rawImageSize * 1 + } + thisObject.typeStrokeStyle = 'rgba(' + UI.projects.superalgos.spaces.floatingSpace.style.node.type.fontColor + ', 1)' + thisObject.nameStrokeStyle = 'rgba(' + UI.projects.superalgos.spaces.floatingSpace.style.node.name.fontColor + ', 1)' + } + + function focusPhysics() { + forcedFocusCounter-- + if (forcedFocusCounter < 0) { + forcedFocusCounter = 0 + } + } + + function frozenPhysics() { + if (thisObject.frozenManually !== false) { return } + if (thisObject.payload === undefined) { return } + let parent = thisObject.payload.chainParent + if (parent !== undefined) { + if (parent.payload !== undefined) { + if (parent.payload.floatingObject !== undefined) { + thisObject.isFrozen = parent.payload.floatingObject.isFrozen + } + } + } + } + + function positionContraintsPhysics() { + const MAX_DISTANCE_TO_PARENT = 5000 + const MIN_DISTANCE_TO_PARENT = 100 + const DEFAULT_NODE_TO_NODE_DISTANCE = 500 * UI.projects.superalgos.spaces.floatingSpace.settings.node.distancePercentage / 100 + + if (thisObject.angleToParent !== ANGLE_TO_PARENT.NOT_FIXED && thisObject.isOnFocus !== true) { + let parent = thisObject.payload.chainParent + let distanceToParent + let parentChildren + let parentDistanceToGarndParent + if (parent === undefined) { + parentDistanceToGarndParent = DEFAULT_NODE_TO_NODE_DISTANCE + distanceToParent = DEFAULT_NODE_TO_NODE_DISTANCE + parentChildren = 1 + } else { + if (parent.payload === undefined) { return } + if (parent.payload.position === undefined) { return } + distanceToParent = Math.sqrt(Math.pow(parent.payload.position.x - thisObject.container.frame.position.x, 2) + Math.pow(parent.payload.position.y - thisObject.container.frame.position.y, 2)) // ... we calculate the distance ... + parentChildren = UI.projects.superalgos.spaces.designSpace.workspace.nodeChildren.childrenCount(parent, thisObject.payload.node) + parentDistanceToGarndParent = parent.payload.distance + } + + let axisCount = parentChildren.childrenCount + let axisIndex = parentChildren.childIndex + let baseAngle = 0 + let angleToParentAngle + + if (parentDistanceToGarndParent !== undefined) { + switch (thisObject.distanceToParent) { + case DISTANCE_TO_PARENT.PARENT_025X: + distanceToParent = parentDistanceToGarndParent / 4 + break + case DISTANCE_TO_PARENT.PARENT_050X: + distanceToParent = parentDistanceToGarndParent / 2 + break + case DISTANCE_TO_PARENT.PARENT_100X: + distanceToParent = parentDistanceToGarndParent + break + case DISTANCE_TO_PARENT.PARENT_150X: + distanceToParent = parentDistanceToGarndParent * 1.5 + break + case DISTANCE_TO_PARENT.PARENT_200X: + distanceToParent = parentDistanceToGarndParent * 2 + break + } + } + + if (distanceToParent > MAX_DISTANCE_TO_PARENT) { + distanceToParent = MAX_DISTANCE_TO_PARENT + } + + if (distanceToParent < MIN_DISTANCE_TO_PARENT) { + distanceToParent = MIN_DISTANCE_TO_PARENT + } + + switch (thisObject.angleToParent) { + case ANGLE_TO_PARENT.RANGE_360: + angleToParentAngle = 360 + break + case ANGLE_TO_PARENT.RANGE_180: + angleToParentAngle = 180 + break + case ANGLE_TO_PARENT.RANGE_90: + angleToParentAngle = 90 + break + case ANGLE_TO_PARENT.RANGE_45: + angleToParentAngle = 45 + break + } + + if (axisIndex === undefined) { + axisCount = 1 + axisIndex = axisCount + } + + if (parent !== undefined) { + if (parent.payload.chainParent !== undefined && parent.payload.angle !== undefined) { + axisCount++ + axisIndex++ + baseAngle = parent.payload.angle + 180 + lastParentAngle = parent.payload.angle + } else { + if (lastParentAngle !== undefined) { + axisCount++ + axisIndex++ + baseAngle = lastParentAngle + 180 + } + } + } + + let separatorAngle = (360 - angleToParentAngle) / 2 + let angleStep = angleToParentAngle / axisCount + + thisObject.payload.angle = baseAngle + separatorAngle + (axisIndex - 1) * angleStep + if (thisObject.payload.angle >= 360) { + thisObject.payload.angle = thisObject.payload.angle - 360 + } + + thisObject.payload.distance = distanceToParent + + if (thisObject.isPinned === true) { + /* When an object is pinned, its angle to its parent needs to be calculated since it depends on where the user places the node. */ + /* + if (thisObject.payload.chainParent !== undefined) { + let x = thisObject.container.frame.position.x - thisObject.payload.chainParent.payload.floatingObject.container.frame.position.x + let y = thisObject.container.frame.position.y - thisObject.payload.chainParent.payload.floatingObject.container.frame.position.y + + thisObject.payload.angle = Math.atan(y / x) + } + */ + return + } + + let newPosition + if (parent !== undefined) { + newPosition = { + x: parent.payload.position.x, + y: parent.payload.position.y + } + } else { + return + } + + let displacement + + switch (thisObject.arrangementStyle) { + case ARRANGEMENT_STYLE.CONCAVE: { + displacement = { + x: distanceToParent * Math.cos(toRadians(thisObject.payload.angle)), + y: distanceToParent * Math.sin(toRadians(thisObject.payload.angle)) + } + + break + } + case ARRANGEMENT_STYLE.CONVEX: { + displacement = { + x: 2 * distanceToParent * Math.cos(toRadians(lastParentAngle)) + distanceToParent * Math.cos(toRadians(thisObject.payload.angle + 180)), + y: 2 * distanceToParent * Math.sin(toRadians(lastParentAngle)) + distanceToParent * Math.sin(toRadians(thisObject.payload.angle + 180)) + } + break + } + case ARRANGEMENT_STYLE.HORIZONTAL_BOTTOM: { + displacement = { + x: distanceToParent * Math.tan(toRadians(thisObject.payload.angle)), + y: distanceToParent + } + break + } + case ARRANGEMENT_STYLE.HORIZONTAL_TOP: { + displacement = { + x: -distanceToParent * Math.tan(toRadians(thisObject.payload.angle)), + y: -distanceToParent + } + break + } + case ARRANGEMENT_STYLE.VERTICAL_RIGHT: { + displacement = { + x: distanceToParent, + y: distanceToParent * Math.tan(toRadians(thisObject.payload.angle)) + } + break + } + case ARRANGEMENT_STYLE.VERTICAL_LEFT: { + displacement = { + x: -distanceToParent, + y: -distanceToParent * Math.tan(toRadians(thisObject.payload.angle)) + } + break + } + } + + if (displacement.x > MAX_DISTANCE_TO_PARENT) { + displacement.x = MAX_DISTANCE_TO_PARENT + } + if (displacement.y > MAX_DISTANCE_TO_PARENT) { + displacement.y = MAX_DISTANCE_TO_PARENT + } + + newPosition.x = newPosition.x + displacement.x + newPosition.y = newPosition.y + displacement.y + + if (isNaN(newPosition.x) === false) { + thisObject.container.frame.position.x = newPosition.x + } + if (isNaN(newPosition.y) === false) { + thisObject.container.frame.position.y = newPosition.y + } + } + } + + function thisObjectPhysics() { + + const ANIMATION_STEPS = UI.projects.superalgos.spaces.floatingSpace.settings.node.animationSteps + + imageSizePhysics() + fontSizePhysics() + radiusPhysics() + + /* Floating object position in screen coordinates */ + thisObject.payload.position.x = thisObject.container.frame.position.x + thisObject.payload.position.y = thisObject.container.frame.position.y + + function imageSizePhysics() { + // The imageSize also have a target. + let imageSizeStep = (thisObject.targetImageSize - thisObject.currentImageSize) / ANIMATION_STEPS + thisObject.currentImageSize = thisObject.currentImageSize + imageSizeStep + if (thisObject.isOnFocus === true) { + if (thisObject.currentImageSize > thisObject.targetImageSize) { + thisObject.currentImageSize = thisObject.targetImageSize + } + } else { + if (thisObject.currentImageSize < thisObject.targetImageSize) { + thisObject.currentImageSize = thisObject.targetImageSize + } + } + } + + function fontSizePhysics() { + // The fontSize also have a target. + let fontSizeStep = (thisObject.targetFontSize - thisObject.currentFontSize) / ANIMATION_STEPS + thisObject.currentFontSize = thisObject.currentFontSize + fontSizeStep + if (thisObject.isOnFocus === true) { + if (thisObject.currentFontSize > thisObject.targetFontSize) { + thisObject.currentFontSize = thisObject.targetFontSize + } + } else { + if (thisObject.currentFontSize < thisObject.targetFontSize) { + thisObject.currentFontSize = thisObject.targetFontSize + } + } + } + + function radiusPhysics() { + let animationStepSize + + if (thisObject.isOnFocus === true) { + animationStepSize = (targetRadiusWithFocus() - targetRadiusWithoutFocus()) / ANIMATION_STEPS + if (thisObject.container.frame.radius >= targetRadiusWithFocus()) { return } + thisObject.container.frame.radius = thisObject.container.frame.radius + animationStepSize + if (thisObject.container.frame.radius >= targetRadiusWithFocus()) { + thisObject.container.frame.radius = targetRadiusWithFocus() + } + thisObject.container.eventHandler.raiseEvent('Dimmensions Changed', event) + } else { + animationStepSize = (targetRadiusWithoutFocus() - targetRadiusWithFocus()) / ANIMATION_STEPS + if (thisObject.container.frame.radius <= targetRadiusWithoutFocus()) { return } + thisObject.container.frame.radius = thisObject.container.frame.radius + animationStepSize + if (thisObject.container.frame.radius <= targetRadiusWithoutFocus()) { + thisObject.container.frame.radius = targetRadiusWithoutFocus() + } + thisObject.container.eventHandler.raiseEvent('Dimmensions Changed', event) + } + + } + } + + function onMouseOver(point) { + if (thisObject.isOnFocus === false) { + setFocus(point) + } + } + + function mouseOverFlotingSpace(point) { + removeFocus() + } + + function someoneAquiredFocus(floatingObject) { + if (floatingObject === undefined || floatingObject.container === undefined) { + return + } + if (thisObject.container === undefined) { + return + } + if (floatingObject.container.id !== thisObject.container.id) { + removeFocus() + } + } + + function forceFocus() { + forcedFocusCounter = 10 + setFocus() + } + + function removeForceFocus() { + forcedFocusCounter = 0 + removeFocus() + } + + function targetRadiusWithFocus() { + return thisObject.rawRadius * ON_FOCUS_RADIUS_FACTOR * UI.projects.superalgos.spaces.floatingSpace.settings.node.radiusPercentage / 100 + } + + function targetRadiusWithoutFocus() { + return thisObject.rawRadius * 1 * UI.projects.superalgos.spaces.floatingSpace.settings.node.radiusPercentage / 100 + } + + function setFocus(point) { + thisObject.targetRadius = targetRadiusWithFocus() + thisObject.currentMass = thisObject.rawMass * UI.projects.superalgos.spaces.floatingSpace.settings.node.massPercentage / 100 + thisObject.targetImageSize = thisObject.rawImageSize * 2.0 + thisObject.targetFontSize = thisObject.rawFontSize * 2.0 + + thisObject.payload.uiObject.container.eventHandler.raiseEvent('onFocus', point) + + thisObject.positionLocked = true + + UI.projects.superalgos.spaces.floatingSpace.container.eventHandler.raiseEvent('onFocusAquired', thisObject) + thisObject.isOnFocus = true + } + + function removeFocus() { + if (forcedFocusCounter > 0) { + return + } + if (thisObject.payload === undefined) { return } + if (thisObject.isOnFocus === true) { + thisObject.targetRadius = targetRadiusWithoutFocus() + thisObject.currentMass = thisObject.rawMass * UI.projects.superalgos.spaces.floatingSpace.settings.node.massPercentage / 100 + thisObject.targetImageSize = thisObject.rawImageSize * 1 + thisObject.targetFontSize = thisObject.rawFontSize * 1 + + thisObject.payload.uiObject.container.eventHandler.raiseEvent('onNotFocus', thisObject.container) + + if (thisObject.isPinned !== true) { + thisObject.positionLocked = false + } + thisObject.isOnFocus = false + } + } + + function onMouseClick(pPoint) { + let event = { + point: pPoint, + parent: thisObject + } + thisObject.payload.uiObject.container.eventHandler.raiseEvent('onMouseClick', event) + } + + function drawBackground() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.uiObject === undefined) { return } + + if (UI.projects.superalgos.spaces.floatingSpace.isItFar(thisObject.payload) && thisObject.payload.uiObject.isShowing === false) { return } + if ((thisObject.isCollapsed === true && thisObject.collapsedManually === false) || thisObject.isParentCollapsed === true) { + if (thisObject.payload.uiObject.isShowing !== true) { + return + } + } + thisObject.payload.uiObject.drawBackground() + } + + function drawMiddleground() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.uiObject === undefined) { return } + + if (UI.projects.superalgos.spaces.floatingSpace.isItFar(thisObject.payload) && thisObject.payload.uiObject.isShowing === false) { return } + if ((thisObject.isCollapsed === true && thisObject.collapsedManually === false) || thisObject.isParentCollapsed === true) { + if (thisObject.payload.uiObject.isShowing !== true) { + return + } + } + thisObject.payload.uiObject.drawMiddleground() + } + + function drawForeground() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.uiObject === undefined) { return } + + if (UI.projects.superalgos.spaces.floatingSpace.isItFar(thisObject.payload) && thisObject.payload.uiObject.isShowing === false) { return } + if ((thisObject.isCollapsed === true && thisObject.collapsedManually === false) || thisObject.isParentCollapsed === true) { + if (thisObject.payload.uiObject.isShowing !== true) { + return + } + } + thisObject.payload.uiObject.drawForeground() + } + + function drawOnFocus() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.uiObject === undefined) { return } + + thisObject.payload.uiObject.drawOnFocus() + } + + function initializeMass(suggestedValue) { + let mass = suggestedValue + if (mass < 0.1) { + mass = 0.1 + } + + thisObject.rawMass = mass + thisObject.currentMass = mass + } + + function initializeRadius(suggestedValue) { + let radius = suggestedValue + if (radius < 2) { + radius = 2 + } + + thisObject.rawRadius = radius + thisObject.targetRadius = radius * UI.projects.superalgos.spaces.floatingSpace.settings.node.radiusPercentage / 100 + thisObject.container.frame.radius = radius + + thisObject.container.eventHandler.raiseEvent('Dimmensions Changed', event) + } + + function initializeImageSize(suggestedValue) { + let size = suggestedValue + if (size < 2) { + size = 2 + } + + thisObject.rawImageSize = size + thisObject.targetImageSize = size + thisObject.currentImageSize = size / 3 + } + + function initializeFontSize(suggestedValue) { + let size = suggestedValue + if (size < 3) { + size = 3 + } + + thisObject.rawFontSize = size + thisObject.targetFontSize = size + thisObject.currentFontSize = size / 3 + } + + function initializeCurrentPosition(arroundPoint) { + let position = {} + + if (thisObject.payload.position === undefined) { + position = { + x: Math.floor((Math.random() * (200) - 100)) + arroundPoint.x, + y: Math.floor((Math.random() * (200) - 100)) + arroundPoint.y + } + } else { + position = { + x: thisObject.payload.position.x, + y: thisObject.payload.position.y + } + } + + setPosition(position) + } + + function setPosition(position) { + thisObject.container.frame.position.x = position.x + thisObject.container.frame.position.y = position.y + + thisObject.payload.position = {} + thisObject.payload.position.x = position.x + thisObject.payload.position.y = position.y + } + + function radomizeCurrentSpeed() { + var initialXDirection + var randomX = Math.floor((Math.random() * 10) + 1) + if (randomX > 5) { + initialXDirection = 1 + } else { + initialXDirection = -1 + } + + var initialYDirection + var randomY = Math.floor((Math.random() * 10) + 1) + if (randomY > 5) { + initialYDirection = 1 + } else { + initialYDirection = -1 + } + + var velocity = { + x: initialXDirection * Math.floor((Math.random() * 300) + 1) / 100, + y: initialYDirection * Math.floor((Math.random() * 300) + 1) / 100 + } + + thisObject.currentSpeed = velocity + } + + function updateMass() { + + } + + function updateRadius() { + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingSpace.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingSpace.js new file mode 100644 index 000000000..d6dd7e284 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FloatingSpace.js @@ -0,0 +1,574 @@ + +function newSuperalgosFloatingSpace() { + const MODULE_NAME = 'Floating Space' + const ERROR_LOG = true + /* + The Floating Space is the place where floating elements like floatingObjects, live and are rendered. + This space has its own physics which helps with the animation of these objects and also preventing them to overlap. + */ + + let thisObject = { + floatingLayer: undefined, // This is the array of floatingObjects being displayed + uiObjectConstructor: undefined, + container: undefined, + inMapMode: false, + drawReferenceLines: false, + drawChainLines: true, + style: undefined, + settings: undefined, + toggleDrawChainLines: toggleDrawChainLines, + toggleDrawReferenceLines: toggleDrawReferenceLines, + toggleMapMode: toggleMapMode, + enterMapMode: enterMapMode, + exitMapMode: exitMapMode, + transformPointToMap: transformPointToMap, + transformRadiusToMap: transformRadiusToMap, + transformImagesizeToMap: transformImagesizeToMap, + oneScreenUp: oneScreenUp, + oneScreenDown: oneScreenDown, + oneScreenLeft: oneScreenLeft, + oneScreenRight: oneScreenRight, + positionAtNode: positionAtNode, + fitIntoVisibleArea: fitIntoVisibleArea, + isThisPointVisible: isThisPointVisible, + isItFar: isItFar, + makeVisible: makeVisible, + makeInvisible: makeInvisible, + draw: draw, + physics: physics, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = true + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + thisObject.container.isClickeable = false + thisObject.container.frame.radius = 0 + + let devicePixelRatio = window.devicePixelRatio + const SPACE_SIZE = 50000 + + thisObject.container.frame.width = SPACE_SIZE + thisObject.container.frame.height = SPACE_SIZE + thisObject.container.frame.position.x = browserCanvas.width / 2 - thisObject.container.frame.width / 2 + thisObject.container.frame.position.y = browserCanvas.height / 2 - thisObject.container.frame.height / 2 + + let visible = false + let floatingObjetOnFocus + + const PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT = 25 + let onDragStartedEventSubscriptionId + let spaceFocusAquiredEventSubscriptionId + + /* Default Style */ + thisObject.style = { + backgroundColor: UI_COLOR.BLACK, + node: { + imageSize: 96, + fontSize: 18, + type: { + fontColor: UI_COLOR.WHITE + }, + name: { + fontColor: UI_COLOR.WHITE + }, + menuItem: { + backgroundColor: UI_COLOR.BLACK, + fontColor: UI_COLOR.WHITE, + fontSize: 15, + imageSize: 50 + } + } + } + + /* Default Settings */ + thisObject.settings = { + node: { + distancePercentage: 100, + radiusPercentage: 100, + massPercentage: 100, + animationSteps: 2, + menuItem: { + widthPercentage: 100, + heightPercentage: 100, + radiusPercentage: 100 + } + }, + physics: true, + } + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onDragStartedEventSubscriptionId) + UI.projects.superalgos.spaces.floatingSpace.container.eventHandler.stopListening(spaceFocusAquiredEventSubscriptionId) + + thisObject.floatingLayer.finalize() + thisObject.uiObjectConstructor.finalize() + thisObject.container.finalize() + + thisObject.floatingLayer = undefined + thisObject.uiObjectConstructor = undefined + thisObject.container = undefined + + floatingObjetOnFocus = undefined + } + + function initialize(callBackFunction) { + thisObject.floatingLayer = newFloatingLayer() + thisObject.floatingLayer.initialize() + + thisObject.uiObjectConstructor = newUiObjectConstructor() + thisObject.uiObjectConstructor.initialize(thisObject.floatingLayer) + + onDragStartedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onDragStarted', onDragStarted) + spaceFocusAquiredEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onFocusAquired', someoneAquiredFocus) + } + + function someoneAquiredFocus(floatingObject) { + if (floatingObject === undefined || floatingObject.container === undefined) { + return + } + floatingObjetOnFocus = floatingObject + } + + function toggleDrawReferenceLines() { + if (thisObject.drawReferenceLines === true) { + thisObject.drawReferenceLines = false + } else { + thisObject.drawReferenceLines = true + } + } + + function toggleDrawChainLines() { + if (thisObject.drawChainLines === true) { + thisObject.drawChainLines = false + } else { + thisObject.drawChainLines = true + } + } + + function transformPointToMap(point) { + let returnPoint = { + x: point.x - thisObject.container.frame.position.x, + y: point.y - thisObject.container.frame.position.y + } + returnPoint.x = returnPoint.x / SPACE_SIZE * browserCanvas.width + returnPoint.y = returnPoint.y / SPACE_SIZE * browserCanvas.height + return returnPoint + } + + function transformRadiusToMap(radius) { + const RADIUS_REDUCTION_FACTOR = 2 + return radius / RADIUS_REDUCTION_FACTOR + } + + function transformImagesizeToMap(imageSize) { + const IMAGE_REDUCTION_FACTOR = 3 + return imageSize / IMAGE_REDUCTION_FACTOR + } + + function enterMapMode() { + thisObject.inMapMode = true + } + + function exitMapMode() { + thisObject.inMapMode = false + } + + function toggleMapMode() { + if (thisObject.inMapMode === false) { + enterMapMode() + } else { + exitMapMode() + } + } + + function onDragStarted(event) { + if (thisObject.inMapMode === false) { + if (event.buttons !== 2) { return } + event.candelDragging = true + enterMapMode() + } else { + if (event.buttons !== 1) { + event.candelDragging = true + return + } + + let mousePosition = { + x: event.x, + y: event.y + } + + let mouseAtSpace = { + x: mousePosition.x / browserCanvas.width * SPACE_SIZE, + y: mousePosition.y / browserCanvas.height * SPACE_SIZE + } + /* Let's see if we can snap to some of the root nodes that are Hierarchy Head and Project Heads */ + let snapCandidateNodes = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeads() + snapCandidateNodes = snapCandidateNodes.concat(UI.projects.superalgos.spaces.designSpace.workspace.getProjectsHeads()) + + for (let i = 0; i < snapCandidateNodes.length; i++) { + let node = snapCandidateNodes[i] + let nodePosition = { + x: node.payload.floatingObject.container.frame.position.x, + y: node.payload.floatingObject.container.frame.position.y + } + // nodePosition = node.payload.floatingObject.container.frame.frameThisPoint(nodePosition) + + let distance = Math.sqrt(Math.pow(mouseAtSpace.x - nodePosition.x, 2) + Math.pow(mouseAtSpace.y - nodePosition.y, 2)) + const SNAP_THREASHOLD = 1000 + if (distance < SNAP_THREASHOLD) { + mousePosition.x = nodePosition.x / SPACE_SIZE * browserCanvas.width + mousePosition.y = nodePosition.y / SPACE_SIZE * browserCanvas.height + } + } + + /* Movign the Floating Space to where the mouse is. */ + thisObject.container.frame.position.x = -mousePosition.x / browserCanvas.width * SPACE_SIZE + browserCanvas.width / 2 + thisObject.container.frame.position.y = -mousePosition.y / browserCanvas.height * SPACE_SIZE + browserCanvas.height / 2 + + exitMapMode() + } + } + + function isItFar(payload, dontCheckParent) { + /* If for any reason the paylaod is undefined we return false */ + if (payload === undefined) { return false } + if (thisObject.inMapMode === true) { return false } + + let radarFactor = 2 // How big is the margin + + /* If the chain parent is not far, they we dont consither this far. */ + if (dontCheckParent !== true) { + if (payload.chainParent !== undefined) { + if (isItFar(payload.chainParent.payload, true) === false) { return false } + } + } + + /* Exceptions that are never considered far. */ + if ( + payload.node.type === 'Trading System' || + payload.node.type === 'Network' || + payload.node.type === 'Crypto Ecosystem' || + payload.node.type === 'Charting Space' || + payload.node.type === 'Data Mine' || + payload.node.type === 'Super Scripts' + ) { + return false + } + + /* Another exception are the ones who have reference parents */ + if (payload.referenceParent !== undefined) { return false } + + /* Here we will check the position of a floatingobject to see if it is outside the screen, with a margin of one screen around. */ + let point = thisObject.container.frame.frameThisPoint(payload.position) + + if (point.x > browserCanvas.width + browserCanvas.width * radarFactor) { + return true + } + + if (point.x < 0 - browserCanvas.width * radarFactor) { + return true + } + + let bottom = COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT + let heightDiff = browserCanvas.height - bottom + if (point.y < bottom - heightDiff * radarFactor) { + return true + } + + if (point.y > browserCanvas.height + heightDiff * radarFactor) { + return true + } + return false + } + + function fitIntoVisibleArea(point) { + /* Here we check the boundaries of the resulting points, so they dont go out of the visible area. */ + + let returnPoint = { + x: point.x, + y: point.y + } + + if (point.x > browserCanvas.width) { + returnPoint.x = browserCanvas.width + } + + if (point.x < 0) { + returnPoint.x = 0 + } + + if (point.y < COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT) { + returnPoint.y = COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT + } + + if (point.y > browserCanvas.height) { + returnPoint.y = browserCanvas.height + } + + return returnPoint + } + + function oneScreenUp() { + if (visible === false) { return } + let displaceVector = { + x: 0, + y: browserCanvas.height * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100 + } + + thisObject.container.displace(displaceVector) + return displaceVector + } + + function oneScreenDown() { + if (visible === false) { return } + let displaceVector = { + x: 0, + y: -browserCanvas.height * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100 + } + + thisObject.container.displace(displaceVector) + return displaceVector + } + + function oneScreenLeft() { + if (visible === false) { return } + let displaceVector = { + x: browserCanvas.width * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100, + y: 0 + } + + thisObject.container.displace(displaceVector) + return displaceVector + } + + function oneScreenRight() { + if (visible === false) { return } + let displaceVector = { + x: -browserCanvas.width * PERCENTAGE_OF_SCREEN_FOR_DISPLACEMENT / 100, + y: 0 + } + + thisObject.container.displace(displaceVector) + return displaceVector + } + + function positionAtNode(node) { + let position = thisObject.container.frame.frameThisPoint(node.payload.position) + + let displaceVector = { + x: browserCanvas.width / 2 - position.x, + y: (COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT) + (browserCanvas.height - (COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT)) / 2 - position.y + } + + thisObject.container.displace(displaceVector) + } + + function isThisPointVisible(point) { + if (point.x > browserCanvas.width) { + return false + } + + if (point.x < 0) { + return false + } + + if (point.y < COCKPIT_SPACE_POSITION + COCKPIT_SPACE_HEIGHT) { + return false + } + + if (point.y > browserCanvas.height) { + return false + } + + return true + } + + function makeVisible() { + visible = true + } + + function makeInvisible() { + visible = false + } + + function getContainer(point) { + if (visible === false) { return } + + let container + + if (floatingObjetOnFocus !== undefined) { + container = floatingObjetOnFocus.getContainer(point) + if (container !== undefined) { + container.space = 'Floating Space' + return container + } + } + + container = thisObject.floatingLayer.getContainer(point) + if (container !== undefined) { + container.space = 'Floating Space' + return container + } + + if (visible === true) { + thisObject.container.space = 'Floating Space' + return thisObject.container + } + } + + function physics() { + if (visible === false) { return } + syncStylePhysics() + syncSettingsPhysics() + browserZoomPhysics() + positionContraintsPhysics() + thisObject.floatingLayer.physics() + } + + function syncStylePhysics() { + if (UI.projects.superalgos.spaces.designSpace === undefined) { return } + if (UI.projects.superalgos.spaces.designSpace.workspace === undefined) { return } + let designSpaceNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsByType('Design Space') + if (designSpaceNode === undefined) { return } + if (designSpaceNode.spaceStyle === undefined) { return } + let configStyle + try { + configStyle = JSON.parse(designSpaceNode.spaceStyle.config) + } catch (err) { + if (thisObject.payload !== undefined) { + thisObject.payload.uiObject.setErrorMessage(err.message) + } + return + } + if (configStyle.backgroundColor !== undefined) { + thisObject.style.backgroundColor = eval(configStyle.backgroundColor) + } + if (configStyle.node !== undefined) { + if (configStyle.node.fontSize !== undefined) { + thisObject.style.node.fontSize = configStyle.node.fontSize + } + if (configStyle.node.imageSize !== undefined) { + thisObject.style.node.imageSize = configStyle.node.imageSize + } + if (configStyle.node.name !== undefined) { + if (configStyle.node.name.fontColor !== undefined) { + thisObject.style.node.name.fontColor = eval(configStyle.node.name.fontColor) + } + } + if (configStyle.node.type !== undefined) { + if (configStyle.node.type.fontColor !== undefined) { + thisObject.style.node.type.fontColor = eval(configStyle.node.type.fontColor) + } + } + if (configStyle.node.menuItem !== undefined) { + if (configStyle.node.menuItem.fontColor !== undefined) { + thisObject.style.node.menuItem.fontColor = eval(configStyle.node.menuItem.fontColor) + } + if (configStyle.node.menuItem.backgroundColor !== undefined) { + thisObject.style.node.menuItem.backgroundColor = eval(configStyle.node.menuItem.backgroundColor) + } + if (configStyle.node.menuItem.fontSize !== undefined) { + thisObject.style.node.menuItem.fontSize = configStyle.node.menuItem.fontSize + } + if (configStyle.node.menuItem.imageSize !== undefined) { + thisObject.style.node.menuItem.imageSize = configStyle.node.menuItem.imageSize + } + } + } + } + + function syncSettingsPhysics() { + if (UI.projects.superalgos.spaces.designSpace === undefined) { return } + if (UI.projects.superalgos.spaces.designSpace.workspace === undefined) { return } + let designSpaceNode = UI.projects.superalgos.spaces.designSpace.workspace.getHierarchyHeadsByType('Design Space') + if (designSpaceNode === undefined) { return } + if (designSpaceNode.spaceSettings === undefined) { return } + let configSettings + try { + configSettings = JSON.parse(designSpaceNode.spaceSettings.config) + } catch (err) { + if (thisObject.payload !== undefined) { + thisObject.payload.uiObject.setErrorMessage(err.message) + } + return + } + if (configSettings.node !== undefined) { + if (configSettings.node.distancePercentage !== undefined) { + thisObject.settings.node.distancePercentage = configSettings.node.distancePercentage + } + if (configSettings.node.radiusPercentage !== undefined) { + thisObject.settings.node.radiusPercentage = configSettings.node.radiusPercentage + } + if (configSettings.node.massPercentage !== undefined) { + thisObject.settings.node.massPercentage = configSettings.node.massPercentage + } + if (configSettings.node.animationSteps !== undefined) { + thisObject.settings.node.animationSteps = configSettings.node.animationSteps + } + if (configSettings.node.menuItem !== undefined) { + if (configSettings.node.menuItem.widthPercentage !== undefined) { + thisObject.settings.node.menuItem.widthPercentage = configSettings.node.menuItem.widthPercentage + } + if (configSettings.node.menuItem.heightPercentage !== undefined) { + thisObject.settings.node.menuItem.heightPercentage = configSettings.node.menuItem.heightPercentage + } + if (configSettings.node.menuItem.radiusPercentage !== undefined) { + thisObject.settings.node.menuItem.radiusPercentage = configSettings.node.menuItem.radiusPercentage + } + } + } + + if (configSettings.physics !== undefined) { + thisObject.settings.physics = configSettings.physics + } + } + + function positionContraintsPhysics() { + if (thisObject.container.frame.position.x > 0) { + thisObject.container.frame.position.x = 0 + } + if (thisObject.container.frame.position.y > 0) { + thisObject.container.frame.position.y = 0 + } + if (thisObject.container.frame.position.x + thisObject.container.frame.width < browserCanvas.width) { + thisObject.container.frame.position.x = browserCanvas.width - thisObject.container.frame.width + } + if (thisObject.container.frame.position.y + thisObject.container.frame.height < browserCanvas.height) { + thisObject.container.frame.position.y = browserCanvas.height - thisObject.container.frame.height + } + } + + function browserZoomPhysics() { + if (devicePixelRatio !== window.devicePixelRatio) { + devicePixelRatio = window.devicePixelRatio + + thisObject.container.frame.position.x = browserCanvas.width / 2 - thisObject.container.frame.width / 2 + thisObject.container.frame.position.y = browserCanvas.height / 2 - thisObject.container.frame.height / 2 + } + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + if (visible === false) { return } + drawBackground() + thisObject.floatingLayer.draw() + } + + function drawBackground() { + browserCanvasContext.beginPath() + + browserCanvasContext.rect(thisObject.container.frame.position.x, thisObject.container.frame.position.y, thisObject.container.frame.width, thisObject.container.frame.height) + browserCanvasContext.fillStyle = 'rgba(' + thisObject.style.backgroundColor + ', 1)' + + browserCanvasContext.closePath() + browserCanvasContext.fill() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/FormulaEditor.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FormulaEditor.js new file mode 100644 index 000000000..9d8d1c031 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/FormulaEditor.js @@ -0,0 +1,199 @@ + +function newFormulaEditor() { + const MODULE_NAME = 'Formula Editor' + + let thisObject = { + isVisibleFunction: undefined, + visible: false, + imagePathOK: undefined, + imagePathNOT_OK: undefined, + rawRadius: undefined, + targetRadius: undefined, + currentRadius: undefined, + container: undefined, + payload: undefined, + deactivate: deactivate, + activate: activate, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = false + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let isMouseOver = false + let SIZE_FACTOR = 1.0 + + return thisObject + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.isVisibleFunction = undefined + } + + function initialize() { + + } + + function deactivate() { + if (thisObject.visible === true) { + thisObject.visible = false + let textArea = document.getElementById('textArea') + textArea.style.display = 'none' + thisObject.payload.node.code = textArea.value + } + EDITOR_ON_FOCUS = false + } + + function activate(action) { + thisObject.visible = true + thisObject.payload = action.node.payload + thisObject.rawRadius = 8 + thisObject.targetRadius = thisObject.container.frame.radius + thisObject.currentRadius = 0 + thisObject.payload.uiObject.resetErrorMessage() + + let textArea = document.getElementById('textArea') + textArea.value = thisObject.payload.node.code + textArea.style = 'resize: none;' + + ' border: none;' + + ' outline: none;' + + 'box-shadow: none;' + + 'overflow:hidden;' + + 'font-family: ' + UI_FONT.PRIMARY + ';' + + 'font-size: 14px;' + + 'background-color: rgb(' + UI_COLOR.MANGANESE_PURPLE + ');' + + 'color:rgb(255, 255, 255);' + + 'width: ' + thisObject.container.frame.width + 'px;' + + 'height: ' + thisObject.container.frame.height + 'px' + textArea.style.display = 'block' + textArea.focus() + EDITOR_ON_FOCUS = true + } + + function getContainer(point) { + let container + if (thisObject.visible === true) { + if (point.x === VERY_LARGE_NUMBER) { + /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that + anyone can react. In our case, the code editor has a text area that is not part of the canvas, so the event is + triggered. We compensate recognizing this coordinate and returning our container. */ + return thisObject.container + } + + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + return thisObject.container + } else { + return undefined + } + } + } + + function physics() { + if (Math.abs(thisObject.currentRadius - thisObject.targetRadius) >= 0.5) { + if (thisObject.currentRadius < thisObject.targetRadius) { + thisObject.currentRadius = thisObject.currentRadius + 0.5 + } else { + thisObject.currentRadius = thisObject.currentRadius - 0.5 + } + } + + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + thisObject.container.frame.width = thisObject.container.frame.radius * 1 * SIZE_FACTOR + thisObject.container.frame.height = thisObject.container.frame.radius * 1 * SIZE_FACTOR + + let textAreaPosition = { + x: 0 - thisObject.container.frame.width / 2, + y: 0 - thisObject.container.frame.height * 3 / 7 + CURRENT_TOP_MARGIN + } + + textAreaPosition = thisObject.container.frame.frameThisPoint(textAreaPosition) + if (thisObject.visible === true) { + let checkPosition = { + x: textAreaPosition.x, + y: textAreaPosition.y - CURRENT_TOP_MARGIN + } + if (thisObject.isVisibleFunction(checkPosition) === false) { + deactivate() + } + } + if (thisObject.visible === true) { + let textAreaDiv = document.getElementById('textAreaDiv') + textAreaDiv.style = 'position:fixed; top:' + textAreaPosition.y + 'px; left:' + textAreaPosition.x + 'px; z-index:1; ' + } + } + + function drawBackground() { + if (thisObject.visible === true) { + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + let radius = thisObject.container.frame.radius * SIZE_FACTOR + + if (radius > 0.5) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius + 3, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(position.x, position.y, radius , 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.MANGANESE_PURPLE + ', ' + 1 + ')' + browserCanvasContext.fill() + } + } + } + function drawForeground() { + let iconPosition = { + x: 0, + y: thisObject.currentRadius * 1.5 + } + + iconPosition = thisObject.container.frame.frameThisPoint(iconPosition) + + /* Menu Item */ + + if (thisObject.canDrawIcon === true && thisObject.currentRadius > 1) { + browserCanvasContext.drawImage(thisObject.icon, iconPosition.x - thisObject.currentRadius, iconPosition.y - thisObject.currentRadius, thisObject.currentRadius * 2, thisObject.currentRadius * 2) + + /* Menu Label */ + + let label = 'Code looks good!' + let labelPoint + let fontSize = 10 + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + if (thisObject.currentRadius >= thisObject.targetRadius) { + labelPoint = { + x: iconPosition.x + thisObject.currentRadius + 10, + y: iconPosition.y + fontSize * FONT_ASPECT_RATIO + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/Picker.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/Picker.js new file mode 100644 index 000000000..594696d90 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/Picker.js @@ -0,0 +1,216 @@ + +function newPicker() { + const MODULE_NAME = 'Picker' + + let thisObject = { + container: undefined, + selectedItem: 0, + onParentChanged: onParentChanged, + getSelected: getSelected, + setSelected: setSelected, + physics: physics, + drawBackground: drawBackground, + drawForeground: drawForeground, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + thisObject.container.frame.width = 150 + thisObject.container.frame.height = 60 + + let isMouseOver + let optionsList + let parent + let current + let parentSelected = 0 + let propertyName + let selected = 0 + let lastSelected = 0 + let onMouseWheelEventSubscriptionId + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + optionsList = undefined + parent = undefined + current = undefined + } + + function initialize(pOptionsList, pCurrent, pParent, pPropertyName) { + optionsList = pOptionsList + current = pCurrent + parent = pParent + propertyName = pPropertyName + onMouseWheelEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + } + + function onMouseOver(event) { + isMouseOver = true + } + + function onMouseNotOver() { + isMouseOver = false + } + + function setSelected(pOptionsList, pCurrent, pParent, pSelected) { + if (pOptionsList !== undefined) { + optionsList = pOptionsList + } + if (pCurrent !== undefined) { + current = pCurrent + } + if (pParent !== undefined) { + parent = pParent + } + if (pSelected !== undefined) { + selected = pSelected + } + raiseEventParentChanged() + } + + function onParentChanged(event) { + if (event.parent !== undefined) { + parent = event.parent + } + parentSelected = event.selected + + selected = 0 + let parentKeys + + parentKeys = Object.keys(parent) + checkPropertyName() + + function checkPropertyName() { + if (propertyName === undefined) { + current = parent[parentKeys[parentSelected]] + checkArray() + } else { + current = parent[parentKeys[parentSelected]] + current = current[propertyName] + checkArray() + } + } + + function checkArray() { + if (Array.isArray(current) === false) { + optionsList = Object.keys(current) + } else { + optionsList = current + } + } + + raiseEventParentChanged() + } + + function getContainer(point) { + let container + + if (thisObject.container.frame.isThisPointHere(point, true, false) === true) { + return thisObject.container + } + } + + function getSelected() { + return optionsList[selected] + } + + function onMouseWheel() { + delta = event.wheelDelta + if (delta > 0) { + delta = -1 + } else { + delta = 1 + } + + selected = selected + delta + if (selected < 0) { selected = 0 } + if (selected > optionsList.length - 1) { + selected = optionsList.length - 1 + } + + if (selected !== lastSelected) { + lastSelected = selected + raiseEventParentChanged() + } + } + + function raiseEventParentChanged() { + let event = { + selected: selected, + parent: current, + propertyName: propertyName + } + thisObject.container.eventHandler.raiseEvent('onParentChanged', event) + } + + function physics() { + + } + + function drawBackground() { + + } + + function drawForeground() { + const FONT_SIZE = 15 + const VISIBLE_LABELS = 5 + let fontSize + let fontColor + let opacity + + for (let i = 0; i < VISIBLE_LABELS; i++) { + let index = i - 2 + selected + let label = '' + if (index >= 0 && index < optionsList.length) { + label = optionsList[index] + } + fontColor = UI_COLOR.LIGHT_GREY + switch (i) { + case 0: + fontSize = FONT_SIZE - 5 + opacity = 0.4 + break + case 1: + fontSize = FONT_SIZE - 3 + opacity = 0.5 + break + case 2: + fontSize = FONT_SIZE - 0 + fontColor = UI_COLOR.BLACK + opacity = 1 + break + case 3: + fontSize = FONT_SIZE - 3 + opacity = 0.5 + break + case 4: + fontSize = FONT_SIZE - 5 + opacity = 0.4 + break + } + + if (i === 2 || isMouseOver === true) { + UI.projects.superalgos.utilities.drawPrint.drawLabel(label, 1 / 2, i / VISIBLE_LABELS, 0, 0, fontSize, thisObject.container, fontColor, undefined, undefined, opacity) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObject.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObject.js new file mode 100644 index 000000000..4885605e9 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObject.js @@ -0,0 +1,2239 @@ + +function newUiObject() { + const MODULE_NAME = 'UI Object' + const ERROR_LOG = true + + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + fitFunction: undefined, + isVisibleFunction: undefined, + type: undefined, + menu: undefined, + isOnFocus: false, + container: undefined, + payload: undefined, + codeEditor: undefined, + configEditor: undefined, + conditionEditor: undefined, + formulaEditor: undefined, + uiObjectTitle: undefined, + circularProgressBar: undefined, + isRunning: undefined, + isPlaying: undefined, + shortcutKey: undefined, + isShowing: undefined, + hasError: undefined, + hasWarning: undefined, + hasInfo: undefined, + valueAtAngle: true, + run: run, + stop: stop, + heartBeat: heartBeat, + getReadyToChainAttach: getReadyToChainAttach, + showAvailabilityToChainAttach: showAvailabilityToChainAttach, + getReadyToReferenceAttach: getReadyToReferenceAttach, + showAvailabilityToReferenceAttach: showAvailabilityToReferenceAttach, + highlight: highlight, + runningAtBackend: runningAtBackend, + setErrorMessage: setErrorMessage, + resetErrorMessage: resetErrorMessage, + setWarningMessage: setWarningMessage, + resetWarningMessage: resetWarningMessage, + setInfoMessage: setInfoMessage, + resetInfoMessage: resetInfoMessage, + setValue: setValue, + resetValue: resetValue, + setPercentage: setPercentage, + resetPercentage: resetPercentage, + setStatus: setStatus, + resetStatus: resetStatus, + physics: physics, + invisiblePhysics: invisiblePhysics, + drawBackground: drawBackground, + drawMiddleground: drawMiddleground, + drawForeground: drawForeground, + drawOnFocus: drawOnFocus, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME, 'Circle') + thisObject.container.isClickeable = false + thisObject.container.isDraggeable = false + thisObject.container.frame.radius = 0 + thisObject.container.frame.position.x = 0 + thisObject.container.frame.position.y = 0 + + let icon + let executingIcon + + let selfFocusEventSubscriptionId + let selfNotFocusEventSubscriptionId + let selfDisplaceEventSubscriptionId + let selfDragStartedEventSubscriptionId + let selfDragFinishedEventSubscriptionId + + let isHighlighted + let highlightCounter = 0 + + let isRunningAtBackend + let runningAtBackendCounter = 0 + + let errorMessageCounter = 0 + let warningMessageCounter = 0 + let infoMessageCounter = 0 + + let hasValue + let valueCounter = 0 + + let hasPercentage + let percentageCounter = 0 + + let hasStatus + let statusCounter = 0 + + let previousDistanceToChainParent + let readyToChainAttachDisplayCounter = 5 + let readyToChainAttachDisplayIncrement = 0.1 + let readyToChainAttachCounter = 0 + let isReadyToChainAttach + let availableToChainAttachCounter = 0 + let isAvailableToChainAttach + let isChainAttaching = false + let chainAttachToNode + + let previousDistanceToReferenceParent + let readyToReferenceAttachDisplayCounter = 5 + let readyToReferenceAttachDisplayIncrement = 0.1 + let readyToReferenceAttachCounter = 0 + let isReadyToReferenceAttach + let availableToReferenceAttachCounter = 0 + let isAvailableToReferenceAttach + let isReferenceAttaching = false + let referenceAttachToNode + + let isDragging = false + + let errorMessage = '' + let warningMessage = '' + let infoMessage = '' + + let currentValue = 0 + let valueMinDecimals = undefined + let currentPercentage = '' + let currentStatus = '' + let rightDragging = false + + let eventSubscriptionIdOnError + let eventSubscriptionIdOnWarning + let eventSubscriptionIdOnInfo + let eventSubscriptionIdOnRunning + let eventSubscriptionIdOnStopped + + let lastHeartBeat + let onRunningCallBackFunction + let onRunningCallBackFunctionWasCalled = false + + let newUiObjectCounter = 25 + let referenceLineCounter = 0 + let chainLineCounter = 0 + + let eventsServerClient + + return thisObject + + function finalize() { + finalizeEventsServerClient() + + thisObject.container.eventHandler.stopListening(selfFocusEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfNotFocusEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfDisplaceEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfDragStartedEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfDragFinishedEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.menu.finalize() + thisObject.menu = undefined + thisObject.uiObjectTitle.finalize() + thisObject.uiObjectTitle = undefined + thisObject.fitFunction = undefined + thisObject.isVisibleFunction = undefined + + if (thisObject.codeEditor !== undefined) { + thisObject.codeEditor.finalize() + thisObject.codeEditor = undefined + } + + if (thisObject.configEditor !== undefined) { + thisObject.configEditor.finalize() + thisObject.configEditor = undefined + } + + if (thisObject.conditionEditor !== undefined) { + thisObject.conditionEditor.finalize() + thisObject.conditionEditor = undefined + } + + if (thisObject.formulaEditor !== undefined) { + thisObject.formulaEditor.finalize() + thisObject.formulaEditor = undefined + } + + icon = undefined + chainAttachToNode = undefined + referenceAttachToNode = undefined + lastHeartBeat = undefined + + onRunningCallBackFunction = undefined + } + + function finalizeEventsServerClient() { + if (eventsServerClient !== undefined) { + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + + if (eventSubscriptionIdOnRunning !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnRunning, 'UiObject') + } + if (eventSubscriptionIdOnStopped !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnStopped, 'UiObject') + } + if (eventSubscriptionIdOnError !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnError, 'UiObject') + } + if (eventSubscriptionIdOnWarning !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnWarning, 'UiObject') + } + if (eventSubscriptionIdOnInfo !== undefined) { + eventsServerClient.stopListening(key, eventSubscriptionIdOnInfo, 'UiObject') + } + } + } + + function initialize(payload, menuItemsInitialValues) { + thisObject.payload = payload + + /* Initialize the Menu */ + + thisObject.menu = newCircularMenu() + thisObject.menu.initialize(menuItemsInitialValues, thisObject.payload) + thisObject.menu.container.connectToParent(thisObject.container, false, false, true, true, false, false, true, true) + + /* Initialize UI Object Title */ + + thisObject.uiObjectTitle = newUiObjectTitle() + thisObject.uiObjectTitle.isVisibleFunction = thisObject.isVisibleFunction + thisObject.uiObjectTitle.container.connectToParent(thisObject.container, false, false, true, true, false, false, true, true) + thisObject.uiObjectTitle.fitFunction = thisObject.fitFunction + thisObject.uiObjectTitle.initialize(thisObject.payload) + + /* Load UI Object Image */ + + iconPhysics() + + selfFocusEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onFocus', onFocus) + selfNotFocusEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onNotFocus', onNotFocus) + selfDisplaceEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onDisplace', onDisplace) + selfDragStartedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onDragStarted', onDragStarted) + selfDragFinishedEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onDragFinished', onDragFinished) + } + + function getContainer(point) { + + let container + + if (isDragging === false && thisObject.isOnFocus === true) { + if (thisObject.codeEditor !== undefined) { + container = thisObject.codeEditor.getContainer(point) + if (container !== undefined) { return container } + } + + if (thisObject.configEditor !== undefined) { + container = thisObject.configEditor.getContainer(point) + if (container !== undefined) { return container } + } + + if (thisObject.conditionEditor !== undefined) { + container = thisObject.conditionEditor.getContainer(point) + if (container !== undefined) { return container } + } + + if (thisObject.formulaEditor !== undefined) { + container = thisObject.formulaEditor.getContainer(point) + if (container !== undefined) { return container } + } + + let getitle = true + + if (thisObject.codeEditor !== undefined) { + if (thisObject.codeEditor.visible === true) { + getitle = false + } + } + if (thisObject.configEditor !== undefined) { + if (thisObject.configEditor.visible === true) { + getitle = false + } + } + if (thisObject.conditionEditor !== undefined) { + if (thisObject.conditionEditor.visible === true) { + getitle = false + } + } + if (thisObject.formulaEditor !== undefined) { + if (thisObject.formulaEditor.visible === true) { + getitle = false + } + } + + if (getitle === true) { + container = thisObject.uiObjectTitle.getContainer(point) + if (container !== undefined) { return container } + } + + container = thisObject.menu.getContainer(point) + if (container !== undefined) { return container } + } + + if (thisObject.container.frame.isThisPointHere(point, true, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function invisiblePhysics() { + thisObject.menu.invisiblePhysics() + childrenRunningPhysics() + thisObject.uiObjectTitle.physics() + } + + function physics() { + thisObject.menu.physics() + thisObject.uiObjectTitle.physics() + + if (thisObject.codeEditor !== undefined) { + thisObject.codeEditor.physics() + } + + if (thisObject.configEditor !== undefined) { + thisObject.configEditor.physics() + } + + if (thisObject.conditionEditor !== undefined) { + thisObject.conditionEditor.physics() + } + + if (thisObject.formulaEditor !== undefined) { + thisObject.formulaEditor.physics() + } + + if (thisObject.payload.chainParent === undefined || thisObject.payload.chainParent.payload === undefined) { + thisObject.payload.targetPosition.x = thisObject.payload.position.x, + thisObject.payload.targetPosition.y = thisObject.payload.position.y + } else { + if (thisObject.payload.chainParent.payload.position !== undefined) { + thisObject.payload.targetPosition.x = thisObject.payload.chainParent.payload.position.x + thisObject.payload.targetPosition.y = thisObject.payload.chainParent.payload.position.y + } + } + + if (thisObject.circularProgressBar !== undefined) { + thisObject.circularProgressBar.physics() + } + + iconPhysics() + highlightPhisycs() + runningAtBackendPhisycs() + errorMessagePhisycs() + warningMessagePhisycs() + infoMessagePhisycs() + valuePhisycs() + percentagePhisycs() + statusPhisycs() + chainDetachingPhysics() + chainAttachingPhysics() + referenceDetachingPhysics() + referenceAttachingPhysics() + childrenRunningPhysics() + newObjectPhysics() + referenceLinePhysics() + chainLinePhysics() + } + + function referenceLinePhysics() { + referenceLineCounter = referenceLineCounter + 2 + if (referenceLineCounter > 500) { + referenceLineCounter = 0 + } + } + + function chainLinePhysics() { + chainLineCounter = chainLineCounter - 5 + if (chainLineCounter < 0) { + chainLineCounter = 500 + } + } + + function newObjectPhysics() { + newUiObjectCounter-- + if (newUiObjectCounter < 0) { + newUiObjectCounter = 0 + } + } + + function heartBeatPhysics() { + if (lastHeartBeat !== undefined) { + const ONE_MIN = 60000 + nowTimestamp = (new Date()).valueOf() + if (nowTimestamp - lastHeartBeat.valueOf() > ONE_MIN) { + lastHeartBeat = undefined + thisObject.isRunning = false + valueCounter = 0 + } + } else { + thisObject.isRunning = false + } + } + + function childrenRunningPhysics() { + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition.properties === undefined) { return } + let monitorChildrenRunning = false + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + if (property.monitorChildrenRunning === true) { + let children = thisObject.payload.node[property.name] + if (children === undefined) { continue } + let totalRunning = 0 + for (let j = 0; j < children.length; j++) { + let child = children[j] + if (child.payload !== undefined) { + if (child.payload.uiObject.isRunning === true) { + totalRunning++ + } + } + } + if (totalRunning > 0) { + setStatus(totalRunning + ' / ' + children.length + ' Running') + thisObject.isRunning = true + } else { + thisObject.isRunning = false + valueCounter = 0 + } + monitorChildrenRunning = true + return + } + } + if (monitorChildrenRunning === false) { + heartBeatPhysics() + } + } + + function chainAttachingPhysics() { + chainAttacchingCounters() + + if (thisObject.isOnFocus !== true) { return } + if (isDragging !== true) { return } + if (rightDragging !== true) { return } + if (thisObject.payload.chainParent !== undefined) { return } + + let nearbyFloatingObjects = thisObject.payload.floatingObject.nearbyFloatingObjects + let compatibleTypes + + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.chainAttachesTo !== undefined) { + compatibleTypes = nodeDefinition.chainAttachesTo.compatibleTypes + } else { + return + } + } else { + return + } + + let foundCompatible = false + chainAttachToNode = undefined + isChainAttaching = false + + for (let i = 0; i < nearbyFloatingObjects.length; i++) { + let nearby = nearbyFloatingObjects[i] + let distance = nearby[0] + let floatingObject = nearby[1] + let nearbyNode = floatingObject.payload.node + if (compatibleTypes.indexOf('->' + nearbyNode.type + '->') >= 0) { + /* Discard App Schema defined objects with busy coonection ports */ + nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition !== undefined) { + let mustContinue = false + let parentNodeDefinition = getNodeDefinition(nearbyNode) + if (parentNodeDefinition !== undefined) { + if (parentNodeDefinition.properties !== undefined) { + for (let j = 0; j < parentNodeDefinition.properties.length; j++) { + let property = parentNodeDefinition.properties[j] + if (nodeDefinition.propertyNameAtParent === property.name) { + switch (property.type) { + case 'node': { + if (nearbyNode[property.name] !== undefined) { + mustContinue = true + } + } + break + case 'array': { + /* Nothing to worry about since an array can take more than one element. */ + } + break + } + } + } + } + } + if (mustContinue === true) { continue } + } + + /* Discard Phases without partent */ + if (thisObject.payload.node.type === 'Phase' && nearbyNode.type === 'Phase' && nearbyNode.payload.parentNode === undefined) { continue } + /* Control maxPhases */ + if (thisObject.payload.node.type === 'Phase') { + if (nearbyNode.maxPhases !== undefined) { + if (nearbyNode.phases.length >= nearbyNode.maxPhases) { + continue + } + } + } + if (thisObject.payload.node.type === 'Phase' && nearbyNode.type === 'Phase') { + if (nearbyNode.payload.parentNode.maxPhases !== undefined) { + if (nearbyNode.payload.parentNode.phases.length >= nearbyNode.payload.parentNode.maxPhases) { + continue + } + } + } + if (foundCompatible === false) { + if (distance < thisObject.payload.floatingObject.container.frame.radius * 1.5 + floatingObject.container.frame.radius * 1.5) { + nearbyNode.payload.uiObject.getReadyToChainAttach() + isChainAttaching = true + chainAttachToNode = nearbyNode + foundCompatible = true + } + } + nearbyNode.payload.uiObject.showAvailabilityToChainAttach() + } + } + } + + function chainAttacchingCounters() { + if (readyToChainAttachDisplayCounter > 15) { + readyToChainAttachDisplayIncrement = -0.25 + } + + if (readyToChainAttachDisplayCounter < 5) { + readyToChainAttachDisplayIncrement = 0.25 + } + + readyToChainAttachDisplayCounter = readyToChainAttachDisplayCounter + readyToChainAttachDisplayIncrement + + readyToChainAttachCounter-- + if (readyToChainAttachCounter <= 0) { + readyToChainAttachCounter = 0 + isReadyToChainAttach = false + } else { + isReadyToChainAttach = true + } + + availableToChainAttachCounter-- + if (availableToChainAttachCounter <= 0) { + availableToChainAttachCounter = 0 + isAvailableToChainAttach = false + } else { + isAvailableToChainAttach = true + } + } + + function getReadyToChainAttach() { + readyToChainAttachCounter = 10 + } + + function showAvailabilityToChainAttach() { + availableToChainAttachCounter = 10 + } + + function chainDetachingPhysics() { + if (isDragging !== true) { return } + if (rightDragging === false) { return } + + let distanceToChainParent = Math.sqrt(Math.pow(thisObject.payload.position.x - thisObject.payload.targetPosition.x, 2) + Math.pow(thisObject.payload.position.y - thisObject.payload.targetPosition.y, 2)) + let ratio = distanceToChainParent / previousDistanceToChainParent + if (previousDistanceToChainParent === 0) { + previousDistanceToChainParent = distanceToChainParent + return + } + previousDistanceToChainParent = distanceToChainParent + + if (thisObject.isOnFocus !== true) { return } + if (thisObject.payload.chainParent === undefined) { return } + if (thisObject.payload.parentNode === undefined) { return } + + let THRESHOLD = 1.15 + + if (ratio > THRESHOLD) { + UI.projects.superalgos.spaces.designSpace.workspace.executeAction({ node: thisObject.payload.node, name: 'Parent Detach', project: 'Superalgos' }) + } + } + + function referenceAttachingPhysics() { + referenceAttacchingCounters() + + if (thisObject.isOnFocus !== true) { return } + if (isDragging !== true) { return } + if (rightDragging !== true) { return } + if (thisObject.payload.referenceParent !== undefined) { return } + + let nearbyFloatingObjects = thisObject.payload.floatingObject.nearbyFloatingObjects + let compatibleTypes + + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.referenceAttachesTo !== undefined) { + compatibleTypes = nodeDefinition.referenceAttachesTo.compatibleTypes + } else { + return + } + } else { + return + } + + let foundCompatible = false + referenceAttachToNode = undefined + isReferenceAttaching = false + + for (let i = 0; i < nearbyFloatingObjects.length; i++) { + let nearby = nearbyFloatingObjects[i] + let distance = nearby[0] + let floatingObject = nearby[1] + let nearbyNode = floatingObject.payload.node + if (compatibleTypes.indexOf('->' + nearbyNode.type + '->') >= 0 || compatibleTypes === "->*Any Node*->") { + if (nodeDefinition.referenceAttachesTo.incompatibleTypes !== undefined) { + if (nodeDefinition.referenceAttachesTo.incompatibleTypes.indexOf('->' + nearbyNode.type + '->') >= 0) { + continue + } + } + if (foundCompatible === false) { + if (distance < thisObject.payload.floatingObject.container.frame.radius * 1.5 + floatingObject.container.frame.radius * 1.5) { + nearbyNode.payload.uiObject.getReadyToReferenceAttach() + isReferenceAttaching = true + referenceAttachToNode = nearbyNode + foundCompatible = true + } + } + nearbyNode.payload.uiObject.showAvailabilityToReferenceAttach() + } + } + } + + function referenceAttacchingCounters() { + if (readyToReferenceAttachDisplayCounter > 15) { + readyToReferenceAttachDisplayIncrement = -0.25 + } + + if (readyToReferenceAttachDisplayCounter < 5) { + readyToReferenceAttachDisplayIncrement = 0.25 + } + + readyToReferenceAttachDisplayCounter = readyToReferenceAttachDisplayCounter + readyToReferenceAttachDisplayIncrement + + readyToReferenceAttachCounter-- + if (readyToReferenceAttachCounter <= 0) { + readyToReferenceAttachCounter = 0 + isReadyToReferenceAttach = false + } else { + isReadyToReferenceAttach = true + } + + availableToReferenceAttachCounter-- + if (availableToReferenceAttachCounter <= 0) { + availableToReferenceAttachCounter = 0 + isAvailableToReferenceAttach = false + } else { + isAvailableToReferenceAttach = true + } + } + + function getReadyToReferenceAttach() { + readyToReferenceAttachCounter = 10 + } + + function showAvailabilityToReferenceAttach() { + availableToReferenceAttachCounter = 10 + } + + function referenceDetachingPhysics() { + if (isDragging !== true) { return } + if (thisObject.payload === undefined) { return } + if (thisObject.payload.floatingObject.isFrozen === true) { return } + if (rightDragging === false) { return } + + if (thisObject.payload.referenceParent === undefined) { return } + + let distanceToReferenceParent = Math.sqrt(Math.pow(thisObject.payload.position.x - thisObject.payload.referenceParent.payload.position.x, 2) + Math.pow(thisObject.payload.position.y - thisObject.payload.referenceParent.payload.position.y, 2)) + let ratio = distanceToReferenceParent / previousDistanceToReferenceParent + if (previousDistanceToReferenceParent === 0) { + previousDistanceToReferenceParent = distanceToReferenceParent + return + } + previousDistanceToReferenceParent = distanceToReferenceParent + + if (thisObject.isOnFocus !== true) { return } + + let THRESHOLD = 1.15 + + if (ratio > THRESHOLD) { + UI.projects.superalgos.spaces.designSpace.workspace.executeAction({ node: thisObject.payload.node, name: 'Reference Detach', project: 'Superalgos' }) + } + } + + function highlightPhisycs() { + highlightCounter-- + if (highlightCounter < 0) { + highlightCounter = 0 + isHighlighted = false + } + } + + function runningAtBackendPhisycs() { + runningAtBackendCounter-- + if (runningAtBackendCounter < 0) { + runningAtBackendCounter = 0 + isRunningAtBackend = false + } + } + + function errorMessagePhisycs() { + errorMessageCounter-- + if (errorMessageCounter < 0) { + errorMessageCounter = 0 + thisObject.hasError = false + } + } + + function warningMessagePhisycs() { + warningMessageCounter-- + if (warningMessageCounter < 0) { + warningMessageCounter = 0 + thisObject.hasWarning = false + } + } + + function infoMessagePhisycs() { + infoMessageCounter-- + if (infoMessageCounter < 0) { + infoMessageCounter = 0 + thisObject.hasInfo = false + } + } + + function valuePhisycs() { + valueCounter-- + if (valueCounter < 0) { + valueCounter = 0 + hasValue = false + } + } + + function percentagePhisycs() { + percentageCounter-- + if (percentageCounter < 0) { + percentageCounter = 0 + hasPercentage = false + } + } + + function statusPhisycs() { + statusCounter-- + if (statusCounter < 0) { + statusCounter = 0 + hasStatus = false + } + } + + function highlight(counter) { + isHighlighted = true + if (counter !== undefined) { + highlightCounter = counter + } else { + highlightCounter = 30 + } + } + + function runningAtBackend() { + isRunningAtBackend = true + runningAtBackendCounter = 5 + } + + function setErrorMessage(message, duration) { + if (message !== undefined) { + errorMessage = message + thisObject.hasError = true + if (duration === undefined) { duration = 2 } + errorMessageCounter = 100 * duration + + /* + Next, we are going to try to inform the parent that this + node has an error, as a way to show the end user where the + node with error is. This is useful to detect errors in nodes + that are located at braches that are collapsed. + */ + + if (thisObject.payload !== undefined) { + if (thisObject.payload.parentNode !== undefined) { + if (thisObject.payload.parentNode.payload !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject !== undefined) { + if (thisObject.payload.floatingObject.isCollapsed === true || thisObject.payload.floatingObject.isParentCollapsed === true) { + if (thisObject.payload.parentNode.payload.uiObject.hasError !== true) { + thisObject.payload.parentNode.payload.uiObject.setErrorMessage('Error Inside this Branch') + } + } + } + } + } + } + } + } + + function resetErrorMessage() { + errorMessage = undefined + thisObject.hasError = false + } + + function setWarningMessage(message, duration) { + if (message !== undefined) { + warningMessage = message + thisObject.hasWarning = true + if (duration === undefined) { duration = 1 } + warningMessageCounter = 100 * duration + + /* + Next, we are going to try to inform the parent that this + node has an warning, as a way to show the end user where the + node with warning is. This is useful to detect warnings in nodes + that are located at braches that are collapsed. + */ + + if (thisObject.payload !== undefined) { + if (thisObject.payload.parentNode !== undefined) { + if (thisObject.payload.parentNode.payload !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject.isCollapsed === true || thisObject.payload.parentNode.payload.floatingObject.isParentCollapsed === true) { + if (thisObject.payload.parentNode.payload.uiObject !== true) { + thisObject.payload.parentNode.payload.uiObject.setWarningMessage('Warning Inside this Branch') + } + } + } + } + } + } + } + } + + function resetWarningMessage() { + warningMessage = undefined + thisObject.hasWarning = false + } + + function setInfoMessage(message, duration) { + if (message !== undefined) { + infoMessage = message + thisObject.hasInfo = true + if (duration === undefined) { duration = 1 } + infoMessageCounter = 100 * duration + + /* + Next, we are going to try to inform the parent that this + node has an info, as a way to show the end user where the + node with info is. This is useful to detect infos in nodes + that are located at braches that are collapsed. + */ + + if (thisObject.payload !== undefined) { + if (thisObject.payload.parentNode !== undefined) { + if (thisObject.payload.parentNode.payload !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject !== undefined) { + if (thisObject.payload.parentNode.payload.floatingObject.isCollapsed === true || thisObject.payload.parentNode.payload.floatingObject.isParentCollapsed === true) { + if (thisObject.payload.parentNode.payload.uiObject !== true) { + thisObject.payload.parentNode.payload.uiObject.setInfoMessage('Info Inside this Branch') + } + } + } + } + } + } + } + } + + function resetInfoMessage() { + infoMessage = undefined + thisObject.hasInfo = false + } + + function setValue(value, counter, minDecimals) { + if (value !== undefined) { + currentValue = value + hasValue = true + valueMinDecimals = minDecimals + if (counter !== undefined) { + valueCounter = counter + } else { + valueCounter = 100 + } + } + } + + function resetValue() { + currentValue = undefined + hasValue = false + valueCounter = 0 + } + + function setPercentage(percentage, counter) { + if (percentage !== undefined) { + currentPercentage = percentage + hasPercentage = true + if (counter !== undefined) { + percentageCounter = counter + } else { + percentageCounter = 100 + } + } + } + + function resetPercentage() { + currentPercentage = undefined + hasPercentage = false + percentageCounter = 0 + } + + function setStatus(status, counter) { + if (status !== undefined) { + currentStatus = status + hasStatus = true + if (counter !== undefined) { + statusCounter = counter + } else { + statusCounter = 100 + } + } + } + + function resetStatus() { + currentStatus = undefined + hasStatus = false + statusCounter = 0 + } + + function heartBeat() { + lastHeartBeat = new Date() + thisObject.isRunning = true + + if (onRunningCallBackFunctionWasCalled === false) { + onRunningCallBackFunctionWasCalled = true + if (onRunningCallBackFunction !== undefined) { + onRunningCallBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) + } + } + } + + function run(pEventsServerClient, callBackFunction) { + finalizeEventsServerClient() + resetErrorMessage() + resetWarningMessage() + resetInfoMessage() + resetPercentage() + resetValue() + resetStatus() + eventsServerClient = pEventsServerClient + + /* We setup the circular progress bar. */ + if (thisObject.circularProgressBar !== undefined) { + thisObject.circularProgressBar.finalize() + } + + thisObject.circularProgressBar = newCircularProgressBar() + thisObject.circularProgressBar.initialize(thisObject.payload, eventsServerClient) + thisObject.circularProgressBar.fitFunction = thisObject.fitFunction + thisObject.circularProgressBar.container = thisObject.container + + setupRunningEventListener(callBackFunction) + setupErrorEventListener(callBackFunction) + setupWarningEventListener() + setupInfoEventListener() + + function setupRunningEventListener(callBackFunction) { + /* We will wait to hear the Running event in order to confirm the execution was really started */ + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Running', undefined, 'UiObject', onResponse, onRunning) + + onRunningCallBackFunction = callBackFunction + + function onResponse(message) { + eventSubscriptionIdOnRunning = message.eventSubscriptionId + } + + function onRunning() { + + if (thisObject.payload === undefined) { return } + + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.stopListening(key, eventSubscriptionIdOnRunning, 'UiObject') + + thisObject.isRunning = true + + if (callBackFunction !== undefined) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) + onRunningCallBackFunctionWasCalled = true + } + } + + /* + While it is running, it can happen that it naturally stops or is stopped not from the UI but from other means. + In those cases, the stop function would never be called (from the UI). So what we will do is to call it from + here with and event, and passing our own callBackFunction. In case there is an external source stopping this, + this will produce an execution of the callback with our event, which will produce that the menu item is restored + to its default stage. + + If on the other side, it is executed from the UI, then we will be processing the Stopped event twice, which in + both cases will reset the menu item to its default state. + */ + + let event = { + type: 'Secondary Action Already Executed' + } + stop(callBackFunction, event) + } + + function setupErrorEventListener(callBackFunction) { + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Error', undefined, key, onResponse, onError) + + function onResponse(message) { + eventSubscriptionIdOnError = message.eventSubscriptionId + } + + async function onError(message) { + + let uiObject = await getTargetUiObject(message) + uiObject.setErrorMessage(message.event.errorMessage, 10) + + let event = { + type: 'Secondary Action Already Executed' + } + completeStop(callBackFunction, event) + } + } + + function setupWarningEventListener() { + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Warning', undefined, key, onResponse, onWarning) + + function onResponse(message) { + eventSubscriptionIdOnWarning = message.eventSubscriptionId + } + + async function onWarning(message) { + let uiObject = await getTargetUiObject(message) + uiObject.setWarningMessage(message.event.warningMessage, 10) + } + } + + function setupInfoEventListener() { + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Info', undefined, key, onResponse, onInfo) + + function onResponse(message) { + eventSubscriptionIdOnInfo = message.eventSubscriptionId + } + + async function onInfo(message) { + let uiObject = await getTargetUiObject(message) + uiObject.setInfoMessage(message.event.infoMessage, 10) + } + } + } + + async function getTargetUiObject(message) { + let uiObject = thisObject + if (message.event.nodeId !== undefined) { + let targetNode = await UI.projects.superalgos.spaces.designSpace.workspace.getNodeById(message.event.nodeId) + if (targetNode !== undefined) { + if (targetNode.payload !== undefined) { + if (targetNode.payload.uiObject !== undefined) { + uiObject = targetNode.payload.uiObject + } + } + } + } + return uiObject + } + + function stop(callBackFunction, event) { + /* We will wait to the event that the execution was terminated in order to call back the menu item */ + let key = thisObject.payload.node.name + '-' + thisObject.payload.node.type + '-' + thisObject.payload.node.id + eventsServerClient.listenToEvent(key, 'Stopped', undefined, 'UiObject', onResponse, onStopped) + + function onResponse(message) { + eventSubscriptionIdOnStopped = message.eventSubscriptionId + } + + function onStopped() { + completeStop(callBackFunction, event) + } + } + + function completeStop(callBackFunction, event) { + if (thisObject.payload === undefined) { return } + if (callBackFunction !== undefined) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE, event) + } + + if (thisObject.circularProgressBar !== undefined) { + thisObject.circularProgressBar.finalize() + thisObject.circularProgressBar = undefined + } + thisObject.isRunning = false + hasValue = false + hasPercentage = false + hasStatus = false + lastHeartBeat = undefined + + finalizeEventsServerClient() + } + + function iconPhysics() { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.project, thisObject.payload.node.type) + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition.alternativeIcons !== undefined) { + let nodeToUse = thisObject.payload.node + if (nodeDefinition.alternativeIcons === 'Use Reference Parent') { + if (thisObject.payload.node.payload.referenceParent !== undefined) { + nodeToUse = thisObject.payload.node.payload.referenceParent + } + } + if (nodeDefinition.alternativeIcons === 'Use Reference Grandparent') { + if (thisObject.payload.node.payload.referenceParent !== undefined) { + if (thisObject.payload.node.payload.referenceParent.payload !== undefined) { + if (thisObject.payload.node.payload.referenceParent.payload.referenceParent !== undefined) { + nodeToUse = thisObject.payload.node.payload.referenceParent.payload.referenceParent + } + } + } + } + nodeDefinition = getNodeDefinition(nodeToUse) + let config = nodeToUse.config + try { + config = JSON.parse(config) + let alternativeIcon + let iconName + for (let i = 0; i < nodeDefinition.alternativeIcons.length; i++) { + alternativeIcon = nodeDefinition.alternativeIcons[i] + if (alternativeIcon.codeName === config.codeName) { + iconName = alternativeIcon.iconName + } + } + let newIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', iconName) + if (newIcon !== undefined) { + icon = newIcon + } + } catch (err) { + // Nothing to do if JSON is baddly formated. + } + } + + executingIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName('Superalgos', 'bitcoin') + } + + function onFocus() { + thisObject.isOnFocus = true + + if (thisObject.payload !== undefined && thisObject.isOnFocus === true && thisObject.payload.referenceParent !== undefined) { + if (thisObject.payload.referenceParent.payload !== undefined) { + if (thisObject.payload.referenceParent.payload.uiObject !== undefined) { + thisObject.payload.referenceParent.payload.uiObject.isShowing = true + } + } + } + } + + function onNotFocus() { + thisObject.isOnFocus = false + if (thisObject.codeEditor !== undefined) { + thisObject.codeEditor.deactivate() + } + if (thisObject.configEditor !== undefined) { + thisObject.configEditor.deactivate() + } + if (thisObject.conditionEditor !== undefined) { + thisObject.conditionEditor.deactivate() + } + if (thisObject.formulaEditor !== undefined) { + thisObject.formulaEditor.deactivate() + } + + if (thisObject.payload !== undefined && + thisObject.isOnFocus === false && + thisObject.payload.referenceParent !== undefined && + thisObject.payload.referenceParent.payload !== undefined && + thisObject.payload.referenceParent.payload.uiObject !== undefined) { + thisObject.payload.referenceParent.payload.uiObject.isShowing = false + } + } + + function onDisplace(event) { + thisObject.uiObjectTitle.exitEditMode() + } + + function onDragStarted(event) { + thisObject.uiObjectTitle.exitEditMode() + if (thisObject.codeEditor !== undefined) { + thisObject.codeEditor.deactivate() + } + if (thisObject.configEditor !== undefined) { + thisObject.configEditor.deactivate() + } + if (thisObject.conditionEditor !== undefined) { + thisObject.conditionEditor.deactivate() + } + if (thisObject.formulaEditor !== undefined) { + thisObject.formulaEditor.deactivate() + } + isDragging = true + if (event.button === 2) { + rightDragging = true + } else { + rightDragging = false + } + + previousDistanceToReferenceParent = undefined + previousDistanceToChainParent = undefined + } + + function onDragFinished(event) { + if (isChainAttaching === true) { + UI.projects.superalgos.spaces.designSpace.workspace.executeAction({ node: thisObject.payload.node, name: 'Parent Attach', project: 'Superalgos', relatedNode: chainAttachToNode }) + chainAttachToNode = undefined + isChainAttaching = false + } + if (isReferenceAttaching === true) { + UI.projects.superalgos.spaces.designSpace.workspace.executeAction({ node: thisObject.payload.node, name: 'Reference Attach', project: 'Superalgos', relatedNode: referenceAttachToNode }) + referenceAttachToNode = undefined + isReferenceAttaching = false + } + isDragging = false + } + + function drawBackground() { + if (thisObject.isOnFocus === false) { + drawReferenceLine() + drawChainLine() + + if (isDragging === false && thisObject.isOnFocus === true) { + thisObject.menu.drawBackground() + } + } + + if (thisObject.circularProgressBar !== undefined) { + thisObject.circularProgressBar.drawBackground() + } + } + + function drawMiddleground() { + if (thisObject.isOnFocus === false) { + drawErrorMessage() + drawWarningMessage() + drawInfoMessage() + drawValue() + drawPercentage() + drawStatus() + thisObject.uiObjectTitle.draw() + drawText() + } + } + + function drawForeground() { + if (thisObject.isOnFocus === false) { + drawBodyAndPicture() + if (isDragging === false) { + thisObject.menu.drawForeground() + } + } + + if (thisObject.circularProgressBar !== undefined) { + thisObject.circularProgressBar.drawForeground() + } + } + + function drawOnFocus() { + if (thisObject.isOnFocus === true) { + drawReferenceLine() + drawChainLine() + + if (thisObject.codeEditor !== undefined) { + thisObject.codeEditor.drawBackground() + thisObject.codeEditor.drawForeground() + } + if (thisObject.configEditor !== undefined) { + thisObject.configEditor.drawBackground() + thisObject.configEditor.drawForeground() + } + if (thisObject.conditionEditor !== undefined) { + thisObject.conditionEditor.drawBackground() + thisObject.conditionEditor.drawForeground() + } + if (thisObject.formulaEditor !== undefined) { + thisObject.formulaEditor.drawBackground() + thisObject.formulaEditor.drawForeground() + } + + let drawMenu = true + let drawTitle = true + + if (thisObject.codeEditor !== undefined) { + if (thisObject.codeEditor.visible === true) { + drawMenu = false + drawTitle = false + } + } + if (thisObject.configEditor !== undefined) { + if (thisObject.configEditor.visible === true) { + drawMenu = false + drawTitle = false + } + } + if (thisObject.conditionEditor !== undefined) { + if (thisObject.conditionEditor.visible === true) { + drawMenu = false + drawTitle = false + } + } + if (thisObject.formulaEditor !== undefined) { + if (thisObject.formulaEditor.visible === true) { + drawMenu = false + drawTitle = false + } + } + + if (drawMenu === true) { + drawBodyAndPicture() + if (isDragging === false) { + thisObject.menu.drawBackground() + thisObject.menu.drawForeground() + } + } + + drawErrorMessage() + drawWarningMessage() + drawInfoMessage() + drawValue() + drawPercentage() + drawStatus() + drawText() + + if (drawTitle === true) { + thisObject.uiObjectTitle.draw() + } + } + } + + function drawChainLine() { + if (UI.projects.superalgos.spaces.floatingSpace.drawChainLines === false) { return } + if (thisObject.payload.chainParent === undefined) { return } + + let targetPoint = { + x: thisObject.payload.chainParent.payload.floatingObject.container.frame.position.x, + y: thisObject.payload.chainParent.payload.floatingObject.container.frame.position.y + } + + let position = { + x: 0, + y: 0 + } + + targetPoint = UI.projects.superalgos.spaces.floatingSpace.container.frame.frameThisPoint(targetPoint) + position = thisObject.container.frame.frameThisPoint(position) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + targetPoint = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(targetPoint) + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + if (thisObject.payload.floatingObject.container.frame.radius > 1) { + let LINE_STYLE = UI_COLOR.TITANIUM_YELLOW + if (thisObject.payload.floatingObject.angleToParent !== ANGLE_TO_PARENT.NOT_FIXED) { + LINE_STYLE = UI_COLOR.GOLDEN_ORANGE + } + if (thisObject.payload.floatingObject.isFrozen === true) { + LINE_STYLE = UI_COLOR.TURQUOISE + } + if (newUiObjectCounter > 0) { + LINE_STYLE = UI_COLOR.GREY + } + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(position.x, position.y) + browserCanvasContext.lineTo(targetPoint.x, targetPoint.y) + browserCanvasContext.strokeStyle = 'rgba(' + LINE_STYLE + ', 1)' + browserCanvasContext.setLineDash([0, chainLineCounter, 2, 3, 0, 500 - chainLineCounter]) + browserCanvasContext.lineWidth = 2 + browserCanvasContext.stroke() + browserCanvasContext.setLineDash([0, 0]) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(position.x, position.y) + browserCanvasContext.lineTo(targetPoint.x, targetPoint.y) + browserCanvasContext.strokeStyle = 'rgba(' + LINE_STYLE + ', 1)' + browserCanvasContext.setLineDash([3, 47]) + browserCanvasContext.lineWidth = 3 + browserCanvasContext.stroke() + browserCanvasContext.setLineDash([0, 0]) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(position.x, position.y) + browserCanvasContext.lineTo(targetPoint.x, targetPoint.y) + browserCanvasContext.strokeStyle = 'rgba(' + LINE_STYLE + ', 1)' + browserCanvasContext.setLineDash([1, 9]) + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + browserCanvasContext.setLineDash([0, 0]) + } + + if (thisObject.payload.floatingObject.container.frame.radius > 0.5) { + let radius = 1 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(targetPoint.x, targetPoint.y, radius, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.fill() + } + } + + function drawReferenceLine() { + if (UI.projects.superalgos.spaces.floatingSpace.drawReferenceLines === false && thisObject.isOnFocus === false) { return } + if (thisObject.payload.referenceParent === undefined) { return } + if (thisObject.payload.referenceParent.payload === undefined) { return } + if (thisObject.payload.referenceParent.payload.floatingObject === undefined) { return } + if (thisObject.payload.referenceParent.payload.floatingObject.isParentCollapsed === true && thisObject.isOnFocus === false) { return } + + let targetPoint = { + x: thisObject.payload.referenceParent.payload.position.x, + y: thisObject.payload.referenceParent.payload.position.y + } + + let position = { + x: 0, + y: 0 + } + + targetPoint = UI.projects.superalgos.spaces.floatingSpace.container.frame.frameThisPoint(targetPoint) + position = thisObject.container.frame.frameThisPoint(position) + + if (thisObject.isOnFocus === true) { + targetPoint = thisObject.fitFunction(targetPoint) + } + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + targetPoint = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(targetPoint) + } + + let LINE_STYLE = UI_COLOR.GREY + + if (thisObject.payload.floatingObject.container.frame.radius > 1) { + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(position.x, position.y) + browserCanvasContext.lineTo(targetPoint.x, targetPoint.y) + browserCanvasContext.strokeStyle = 'rgba(' + LINE_STYLE + ', 1)' + browserCanvasContext.setLineDash([1, 9]) + browserCanvasContext.lineWidth = 0.75 + browserCanvasContext.stroke() + browserCanvasContext.setLineDash([0, 0]) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(position.x, position.y) + browserCanvasContext.lineTo(targetPoint.x, targetPoint.y) + browserCanvasContext.strokeStyle = 'rgba(' + LINE_STYLE + ', 1)' + browserCanvasContext.setLineDash([0, referenceLineCounter, 1, 9, 2, 8, 3, 7, 4, 6, 0, 500 - referenceLineCounter]) + browserCanvasContext.lineWidth = 4 + browserCanvasContext.stroke() + browserCanvasContext.setLineDash([0, 0]) + } + + if (thisObject.payload.floatingObject.container.frame.radius > 0.5) { + let radius = 1 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(targetPoint.x, targetPoint.y, radius, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.fill() + } + } + + function isEditorVisible() { + if (thisObject.codeEditor !== undefined) { + if (thisObject.codeEditor.visible === true) { return true } + } + + if (thisObject.configEditor !== undefined) { + if (thisObject.configEditor.visible === true) { return true } + } + + if (thisObject.conditionEditor !== undefined) { + if (thisObject.conditionEditor.visible === true) { return true } + } + + if (thisObject.formulaEditor !== undefined) { + if (thisObject.formulaEditor.visible === true) { return true } + } + } + + function drawText() { + if (isEditorVisible() === true) { return } + /* Text Follows */ + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + let radius = thisObject.payload.floatingObject.container.frame.radius + /* Label Text */ + let labelPoint + let fontSize = thisObject.payload.floatingObject.currentFontSize + let lineSeparator = thisObject.payload.floatingObject.currentFontSize * 1.2 + let label + + if (radius > 6) { + const MAX_LABEL_LENGTH = 30 + + label = thisObject.payload.subTitle + label = addIndexNumber(label) + + if (label !== undefined) { + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.isHierarchyHead !== true && nodeDefinition.isProjectHead !== true) { + return + } + } + } + + if (label.length > MAX_LABEL_LENGTH) { + label = label.substring(0, MAX_LABEL_LENGTH) + '...' + } + + if (thisObject.isOnFocus === true) { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 5, + y: position.y + radius * 2 / 3 + lineSeparator * 1 + 30 + } + } else { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 5, + y: position.y + thisObject.payload.floatingObject.currentImageSize / 2 + lineSeparator * 1 + } + } + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + labelPoint.y = position.y + 50 / 2 + lineSeparator * 2 + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = thisObject.payload.floatingObject.nameStrokeStyle + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } + + function drawErrorMessage() { + if (thisObject.hasError === false) { return } + drawMessage(errorMessage, UI_COLOR.RUSTED_RED) + } + + function drawWarningMessage() { + if (thisObject.hasError === true) { return } + if (thisObject.hasWarning !== true) { return } + drawMessage(warningMessage, UI_COLOR.TITANIUM_YELLOW) + } + + function drawInfoMessage() { + if (thisObject.hasError === true) { return } + if (thisObject.hasWarning === true) { return } + if (thisObject.hasInfo !== true) { return } + drawMessage(infoMessage, UI_COLOR.TURQUOISE) + } + + function drawMessage(message, textColor) { + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + return + } + + /* Text Follows */ + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + let radius = thisObject.payload.floatingObject.container.frame.radius + /* Label Text */ + let labelPoint + let fontSize = thisObject.payload.floatingObject.currentFontSize * 3 / 4 + let lineSeparator = thisObject.payload.floatingObject.currentFontSize * 1.2 + let label + + if (radius > 6) { + const IDEAL_LABEL_LENGTH = 80 + + label = message + + if (label !== undefined && label !== null) { + if (label.length > IDEAL_LABEL_LENGTH) { + if (label.length > IDEAL_LABEL_LENGTH * 3) { + label = label.substring(0, IDEAL_LABEL_LENGTH * 3) + '...' + } + fontSize = thisObject.payload.floatingObject.currentFontSize * 2 / 4 + } + + if (thisObject.isOnFocus === true) { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 5, + y: position.y + radius * 2 / 3 + lineSeparator * 6 + 30 + } + } else { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 5, + y: position.y + thisObject.payload.floatingObject.currentImageSize / 2 + lineSeparator * 5 + } + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + browserCanvasContext.fillStyle = 'rgba(' + textColor + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } + + function drawValue() { + if (currentValue === null) { return } + if (hasValue === false) { return } + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { return } + if (thisObject.payload === undefined) { return } + if ((thisObject.payload.floatingObject.isCollapsed === true && thisObject.payload.floatingObject.collapsedManually === false) || thisObject.payload.floatingObject.isParentCollapsed === true) { return } + + // if (currentValue === undefined || currentValue === '') { return } + + /* Text Follows */ + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + let radius = thisObject.payload.floatingObject.container.frame.radius + /* Label Text */ + let labelPoint + let fontSize = thisObject.payload.floatingObject.currentFontSize + let lineSeparator = thisObject.payload.floatingObject.currentFontSize * 1.2 + let label + + if (radius > 6) { + const MAX_LABEL_LENGTH = 65 + + label = currentValue + if (!isNaN(currentValue)) { + if (currentValue.toFixed !== undefined) { + label = dynamicDecimals(currentValue, valueMinDecimals) + } + } + + if (label !== undefined) { + if (label.length > MAX_LABEL_LENGTH) { + label = label.substring(0, MAX_LABEL_LENGTH) + '...' + } + + if (label.length > 30) { + fontSize = fontSize * 2 / 3 + } + + if (thisObject.valueAtAngle === true && thisObject.payload.angle !== undefined) { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 10, + y: position.y + } + labelPoint.x = labelPoint.x + radius * 7 / 3 * Math.cos(toRadians(thisObject.payload.angle)) + labelPoint.y = labelPoint.y + radius * 7 / 3 * Math.sin(toRadians(thisObject.payload.angle)) + } else { + if (thisObject.isOnFocus === true) { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 10, + y: position.y + radius * 2 / 3 + lineSeparator * 4 + 30 + } + } else { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 10, + y: position.y + thisObject.payload.floatingObject.currentImageSize / 2 + lineSeparator * 3 + } + } + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TURQUOISE + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } + + function drawPercentage() { + if (hasPercentage === false) { return } + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { return } + if (thisObject.payload === undefined) { return } + if ((thisObject.payload.floatingObject.isCollapsed === true && thisObject.payload.floatingObject.collapsedManually === false) || thisObject.payload.floatingObject.isParentCollapsed === true) { return } + + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + let radius = thisObject.payload.floatingObject.container.frame.radius + /* Label Text */ + let labelPoint + let fontSize = thisObject.payload.floatingObject.currentFontSize + let lineSeparator = thisObject.payload.floatingObject.currentFontSize * 1.2 + let label + + if (radius > 6) { + const MAX_LABEL_LENGTH = 65 + + label = Number(currentPercentage).toFixed(0) + '%' + + if (label.length > MAX_LABEL_LENGTH) { + label = label.substring(0, MAX_LABEL_LENGTH) + '...' + } + + if (thisObject.isOnFocus === true) { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 5, + y: position.y + radius * 2 / 3 + lineSeparator * 5 + 30 + } + } else { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 10, + y: position.y + thisObject.payload.floatingObject.currentImageSize / 2 + lineSeparator * 4 + } + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TURQUOISE + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + + } + } + + function drawStatus() { + if (hasStatus === false) { return } + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { return } + + let position = { + x: 0, + y: 0 + } + + position = thisObject.container.frame.frameThisPoint(position) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + let radius = thisObject.payload.floatingObject.container.frame.radius + /* Label Text */ + let labelPoint + let fontSize = thisObject.payload.floatingObject.currentFontSize * 6 / 4 / 2 + let lineSeparator = thisObject.payload.floatingObject.currentFontSize * 1.2 + let label + + if (radius > 6) { + const MAX_LABEL_LENGTH = 100 + + label = currentStatus + + if (label !== undefined) { + if (label.length > MAX_LABEL_LENGTH) { + label = label.substring(0, MAX_LABEL_LENGTH) + '...' + } + + if (thisObject.isOnFocus === true) { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 10, + y: position.y + radius * 2 / 3 + lineSeparator * 3 + 30 + } + } else { + labelPoint = { + x: position.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO - 10, + y: position.y + thisObject.payload.floatingObject.currentImageSize / 2 + lineSeparator * 2 + } + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } + + function addIndexNumber(label) { + switch (thisObject.payload.node.type) { + case 'Phase': { + let parent = thisObject.payload.parentNode + if (parent !== undefined) { + let granParent = parent.payload.parentNode + if (granParent !== undefined) { + if (granParent.type === 'Initial Definition') { + return label + ' #' + 0 + } + } + for (let i = 0; i < parent.phases.length; i++) { + let phase = parent.phases[i] + if (phase.id === thisObject.payload.node.id) { + label = label + ' #' + (i + 1) + return label + } + } + } else { + return label + } + + break + } + case 'Situation': { + let parent = thisObject.payload.parentNode + if (parent !== undefined) { + for (let i = 0; i < parent.situations.length; i++) { + let situation = parent.situations[i] + if (situation.id === thisObject.payload.node.id) { + label = label + ' #' + (i + 1) + return label + } + } + } else { + return label + } + + break + } + case 'Condition': { + let parent = thisObject.payload.parentNode + if (parent !== undefined) { + for (let i = 0; i < parent.conditions.length; i++) { + let condition = parent.conditions[i] + if (condition.id === thisObject.payload.node.id) { + label = label + ' #' + (i + 1) + return label + } + } + } else { + return label + } + + break + } + default: { + return label + } + } + } + + function drawBodyAndPicture() { + let position = { + x: thisObject.container.frame.position.x, + y: thisObject.container.frame.position.y + } + + position = thisObject.container.frame.frameThisPoint(position) + + if (thisObject.isShowing === true) { + position = thisObject.fitFunction(position) + } + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + position = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(position) + } + + let radius = thisObject.payload.floatingObject.container.frame.radius + + if (radius > 0.5) { + let VISIBLE_RADIUS = 5 + + let visiblePosition = { + x: thisObject.container.frame.position.x, + y: thisObject.container.frame.position.y + } + + visiblePosition = thisObject.container.frame.frameThisPoint(visiblePosition) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + visiblePosition = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(visiblePosition) + radius = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(radius) + VISIBLE_RADIUS = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(VISIBLE_RADIUS) + } else { + visiblePosition = thisObject.fitFunction(visiblePosition) + } + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + + browserCanvasContext.fillStyle = thisObject.payload.floatingObject.fillStyle + + browserCanvasContext.fill() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS - 2, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + + browserCanvasContext.fillStyle = 'rgba(' + UI.projects.superalgos.spaces.floatingSpace.style.backgroundColor + ', 1)' + + browserCanvasContext.fill() + + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition === undefined) { + console.log('Node ' + thisObject.payload.node + ' without Node Definition at APP SCHEMA.') + return + } + + if (thisObject.isOnFocus === true) { + /* Black Translucent Background when node is in focus */ + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.fillStyle = 'rgba(' + UI.projects.superalgos.spaces.floatingSpace.style.backgroundColor + ', 0.70)' + browserCanvasContext.fill() + /* Border when node is in focus */ + if ( + thisObject.hasError !== true && + thisObject.WarningError !== true && + thisObject.hasInfo !== true && + nodeDefinition.isHierarchyHead !== true && + thisObject.circularProgressBar === undefined + ) { + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + 1 + ')' + browserCanvasContext.lineWidth = 3 + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS - 1, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + 1 + ')' + browserCanvasContext.lineWidth = 2 + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke() + + } + } + + /* Hierarchy Head Ring */ + if (nodeDefinition.isHierarchyHead === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + VISIBLE_RADIUS = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(VISIBLE_RADIUS) + } + let OPACITY = 0.5 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.LIGHT_GREY + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([1, 10]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.LIGHT_GREY + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 1 + if (thisObject.payload.node.isPlugin === true) { + browserCanvasContext.setLineDash([0, 0]) + } else { + browserCanvasContext.setLineDash([10, 20]) + } + browserCanvasContext.stroke() + } + + /* Project Head Ring */ + if (nodeDefinition.isProjectHead === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + VISIBLE_RADIUS = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(VISIBLE_RADIUS) + } + let OPACITY = 0.5 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + OPACITY + ')' + + if (thisObject.payload.floatingObject.isOnFocus === true) { + browserCanvasContext.lineWidth = 30 + browserCanvasContext.setLineDash([0, 0]) + } else { + browserCanvasContext.lineWidth = 30 + browserCanvasContext.setLineDash([1, 3]) + + } + browserCanvasContext.stroke() + } + + /* Info Ring */ + if (thisObject.hasInfo === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + VISIBLE_RADIUS = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(VISIBLE_RADIUS) + } + let OPACITY = infoMessageCounter / 30 + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.DARK_TURQUOISE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 5 + browserCanvasContext.setLineDash([5, 10]) + browserCanvasContext.stroke() + } + + /* Warning Ring */ + if (thisObject.hasWarning === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + VISIBLE_RADIUS = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(VISIBLE_RADIUS) + } + let OPACITY = warningMessageCounter / 30 + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 5 + browserCanvasContext.setLineDash([5, 10]) + browserCanvasContext.stroke() + } + + /* Error Ring */ + if (thisObject.hasError === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + VISIBLE_RADIUS = UI.projects.superalgos.spaces.floatingSpace.transformRadiusToMap(VISIBLE_RADIUS) + } + let OPACITY = errorMessageCounter / 30 + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.RED + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 5 + browserCanvasContext.setLineDash([5, 10]) + browserCanvasContext.stroke() + } + + if (isHighlighted === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius + let OPACITY = highlightCounter / 10 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([4, 20]) + browserCanvasContext.stroke() + } + + if (isReadyToChainAttach === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius * 2.5 + readyToChainAttachDisplayCounter - readyToChainAttachDisplayCounter / 2 + let OPACITY = readyToChainAttachCounter / 10 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([10, 90]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 5 + browserCanvasContext.setLineDash([5, 45]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.setLineDash([2, 8]) + browserCanvasContext.stroke() + } + + if (isAvailableToChainAttach === true && isReadyToChainAttach === false) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius * 2.5 + let OPACITY = availableToChainAttachCounter / 10 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TURQUOISE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([8, 32]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.TURQUOISE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 2 + browserCanvasContext.setLineDash([3, 5]) + browserCanvasContext.stroke() + } + + if (isReadyToReferenceAttach === true) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius * 2.5 + readyToReferenceAttachDisplayCounter - readyToReferenceAttachDisplayCounter / 2 + let OPACITY = readyToReferenceAttachCounter / 10 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GOLDEN_ORANGE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([10, 90]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GOLDEN_ORANGE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 5 + browserCanvasContext.setLineDash([5, 45]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GOLDEN_ORANGE + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.setLineDash([2, 8]) + browserCanvasContext.stroke() + } + + if (isAvailableToReferenceAttach === true && isReadyToReferenceAttach === false) { + VISIBLE_RADIUS = thisObject.payload.floatingObject.container.frame.radius * 2.5 + let OPACITY = availableToReferenceAttachCounter / 10 + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 10 + browserCanvasContext.setLineDash([8, 32]) + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.arc(visiblePosition.x, visiblePosition.y, VISIBLE_RADIUS, 0, Math.PI * 2, true) + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + OPACITY + ')' + browserCanvasContext.lineWidth = 2 + browserCanvasContext.setLineDash([3, 5]) + browserCanvasContext.stroke() + } + } + + /* Image */ + + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + let additionalImageSize = 0 + if (isRunningAtBackend === true || isReadyToReferenceAttach === true || isReadyToChainAttach === true) { additionalImageSize = 20 } + let totalImageSize = additionalImageSize + thisObject.payload.floatingObject.currentImageSize + + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition === undefined) { return } + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + if (nodeDefinition.isHierarchyHead === true || nodeDefinition.isProjectHead === true ) { + totalImageSize = 50 + } else { + totalImageSize = UI.projects.superalgos.spaces.floatingSpace.transformImagesizeToMap(totalImageSize) + } + } + + if (thisObject.isShowing === true) { + totalImageSize = 50 + } + + browserCanvasContext.drawImage( + icon, position.x - totalImageSize / 2, + position.y - totalImageSize / 2, + totalImageSize, + totalImageSize) + } + } + + if (executingIcon !== undefined) { + if (executingIcon.canDrawIcon === true) { + if (isRunningAtBackend === true) { + const DISTANCE_FROM_CENTER = thisObject.payload.floatingObject.container.frame.radius / 3 + 50 + const EXECUTING_ICON_SIZE = 20 + thisObject.payload.floatingObject.container.frame.radius / 6 + + browserCanvasContext.drawImage( + executingIcon, position.x - EXECUTING_ICON_SIZE / 2, + position.y - EXECUTING_ICON_SIZE / 2 - DISTANCE_FROM_CENTER, + EXECUTING_ICON_SIZE, + EXECUTING_ICON_SIZE) + } + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObjectConstructor.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObjectConstructor.js new file mode 100644 index 000000000..914e93cce --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObjectConstructor.js @@ -0,0 +1,642 @@ + +function newUiObjectConstructor() { + const MODULE_NAME = 'UI Object Constructor' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + createUiObject: createUiObject, + destroyUiObject: destroyUiObject, + initialize: initialize, + finalize: finalize + } + + let floatingLayer + + return thisObject + + function finalize() { + floatingLayer = undefined + } + + function initialize(pFloatingLayer) { + floatingLayer = pFloatingLayer + } + + function createUiObject(userAddingNew, payload) { + let floatingObject = newFloatingObject() + floatingObject.fitFunction = UI.projects.superalgos.spaces.floatingSpace.fitIntoVisibleArea + floatingObject.container.connectToParent(UI.projects.superalgos.spaces.floatingSpace.container, false, false, false, false, false, false, false, false) + floatingObject.initialize('UI Object', payload) + payload.floatingObject = floatingObject + + /* + When this object is created based on a backup, share or clone, we will have a savedPayload that we will use to set the initial properties. + If it is a new object being created out of the user interface, we jusst continue with the construction process. + */ + if (userAddingNew === false && payload.node.type !== 'Workspace') { + let position = { + x: 0, + y: 0 + } + + position = { + x: payload.node.savedPayload.position.x, + y: payload.node.savedPayload.position.y + } + + floatingObject.setPosition(position) + payload.node.savedPayload.position = undefined + if (payload.node.savedPayload.floatingObject.isPinned === true) { + floatingObject.pinToggle() + } + if (payload.node.savedPayload.floatingObject.isFrozen === true) { + floatingObject.freezeToggle() + } + if (payload.node.savedPayload.floatingObject.isCollapsed === true) { + floatingObject.collapseToggle() + } + if (payload.node.savedPayload.floatingObject.angleToParent !== undefined) { + floatingObject.angleToParent = payload.node.savedPayload.floatingObject.angleToParent + } + if (payload.node.savedPayload.floatingObject.distanceToParent !== undefined) { + floatingObject.distanceToParent = payload.node.savedPayload.floatingObject.distanceToParent + } + if (payload.node.savedPayload.floatingObject.arrangementStyle !== undefined) { + floatingObject.arrangementStyle = payload.node.savedPayload.floatingObject.arrangementStyle + } + } + + /* + For brand new objects being created directly by the user, we will make them inherit some properties + from their closest siblings, and if they don't have, from their parents. + */ + + if (userAddingNew === true) { + let definition = getNodeDefinition(payload.parentNode) + if (definition.properties !== undefined) { + for (let i = 0; i < definition.properties.length; i++) { + let property = definition.properties[i] + if (property.childType === payload.node.type) { + if (property.type === 'array') { + let parentNode = payload.parentNode + let parentNodeArray = parentNode[property.name] + if (parentNodeArray.length > 1) { // the new node was already added + let closestSibling = parentNodeArray[parentNodeArray.length - 2] + if (closestSibling !== undefined) { + floatingObject.angleToParent = closestSibling.payload.floatingObject.angleToParent + floatingObject.distanceToParent = closestSibling.payload.floatingObject.distanceToParent + floatingObject.arrangementStyle = closestSibling.payload.floatingObject.arrangementStyle + break + } + } + } + if (floatingObject.angleToParent === undefined) { + for (let j = i - 1; j >= 0; j--) { + let siblingProperty = definition.properties[j] + let parentNode = payload.parentNode + let parentNodeProperty = parentNode[siblingProperty.name] + if (parentNodeProperty !== undefined) { + if (siblingProperty.type === 'array') { + if (parentNodeProperty.length > 0) { + let closestSibling = parentNodeProperty[parentNodeProperty.length - 1] + if (closestSibling !== undefined) { + floatingObject.angleToParent = closestSibling.payload.floatingObject.angleToParent + floatingObject.distanceToParent = closestSibling.payload.floatingObject.distanceToParent + floatingObject.arrangementStyle = closestSibling.payload.floatingObject.arrangementStyle + break + } + } + } else { + let closestSibling = parentNodeProperty + if (closestSibling !== undefined) { + floatingObject.angleToParent = closestSibling.payload.floatingObject.angleToParent + floatingObject.distanceToParent = closestSibling.payload.floatingObject.distanceToParent + floatingObject.arrangementStyle = closestSibling.payload.floatingObject.arrangementStyle + break + } + } + } + } + for (let j = i + 1; j < definition.properties.length; j++) { + let siblingProperty = definition.properties[j] + let parentNode = payload.parentNode + let parentNodeProperty = parentNode[siblingProperty.name] + if (parentNodeProperty !== undefined) { + if (siblingProperty.type === 'array') { + if (parentNodeProperty.length > 0) { + let closestSibling = parentNodeProperty[parentNodeProperty.length - 1] + if (closestSibling !== undefined) { + floatingObject.angleToParent = closestSibling.payload.floatingObject.angleToParent + floatingObject.distanceToParent = closestSibling.payload.floatingObject.distanceToParent + floatingObject.arrangementStyle = closestSibling.payload.floatingObject.arrangementStyle + break + } + } + } else { + let closestSibling = parentNodeProperty + if (closestSibling !== undefined) { + floatingObject.angleToParent = closestSibling.payload.floatingObject.angleToParent + floatingObject.distanceToParent = closestSibling.payload.floatingObject.distanceToParent + floatingObject.arrangementStyle = closestSibling.payload.floatingObject.arrangementStyle + break + } + } + } + } + } + } + } + } + + if (floatingObject.angleToParent === undefined) { + floatingObject.angleToParent = payload.parentNode.payload.floatingObject.angleToParent + floatingObject.distanceToParent = payload.parentNode.payload.floatingObject.distanceToParent + floatingObject.arrangementStyle = payload.parentNode.payload.floatingObject.arrangementStyle + } + } + + /* Default Values in case there was no way to set a value previous to this. */ + if (floatingObject.angleToParent === undefined) { + floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_360 + } + if (floatingObject.distanceToParent === undefined) { + floatingObject.distanceToParent = DISTANCE_TO_PARENT.PARENT_100X + } + if (floatingObject.arrangementStyle === undefined) { + floatingObject.arrangementStyle = ARRANGEMENT_STYLE.CONCAVE + } + + let uiObject = newUiObject() + payload.uiObject = uiObject + uiObject.fitFunction = UI.projects.superalgos.spaces.floatingSpace.fitIntoVisibleArea + uiObject.isVisibleFunction = UI.projects.superalgos.spaces.floatingSpace.isThisPointVisible + let menuItemsInitialValues = getMenuItemsInitialValues(uiObject, floatingObject, payload) + + let schemaNode = getNodeDefinition(payload.node) + if (schemaNode === undefined) { + console.log(payload.node.type + ' node found at App Schema. This node will not be created. ') + return + } + let menuItems = schemaNode.menuItems + for (let j = 0; j < menuItems.length; j++) { + let item = menuItems[j] + item.angle = undefined + if (item.action !== undefined) { + if (item.action.indexOf('Add Missing Children') >= 0) { + item.action = 'Add Missing Children' + } + } + if (item.actionFunction === undefined) { + // item.actionFunction = 'payload.executeAction' + } + } + + uiObject.initialize(payload, menuItemsInitialValues) + uiObject.container.connectToParent(floatingObject.container, false, false, true, true, false, false, true, true, true, true, true) + + setFloatingObjectBasicProperties(floatingObject, payload) + + if (payload.node.savedPayload !== undefined) { + if (payload.node.savedPayload.uiObject !== undefined) { + payload.uiObject.shortcutKey = payload.node.savedPayload.uiObject.shortcutKey + } + } + + floatingLayer.addFloatingObject(floatingObject) + + return + } + + function addLeftIcons(menuItemsInitialValues, floatingObject, isPersonalData) { + menuItemsInitialValues.push( + { + action: 'Pin / Unpin', + actionFunction: floatingObject.pinToggle, + actionStatus: floatingObject.getPinStatus, + currentStatus: false, + label: undefined, + visible: true, + iconPathOn: 'pinned', + iconPathOff: 'unpinned', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 1 + } + ) + menuItemsInitialValues.push( + { + action: 'Change Tension Level', + actionFunction: floatingObject.angleToParentToggle, + actionStatus: floatingObject.getAngleToParent, + currentStatus: true, + label: undefined, + visible: true, + icons: ['angle-to-parent-000', 'angle-to-parent-360', 'angle-to-parent-180', 'angle-to-parent-090', 'angle-to-parent-045'], + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 1 + } + ) + menuItemsInitialValues.push( + { + action: 'Change Distance to Paarent', + actionFunction: floatingObject.distanceToParentToggle, + actionStatus: floatingObject.getDistanceToParent, + currentStatus: true, + label: undefined, + visible: true, + icons: ['distance-to-parent-000', 'distance-to-parent-025', 'distance-to-parent-050', 'distance-to-parent-100', 'distance-to-parent-150', 'distance-to-parent-200'], + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 1 + } + ) + menuItemsInitialValues.push( + { + action: 'Change Arrangement Style', + actionFunction: floatingObject.arrangementStyleToggle, + actionStatus: floatingObject.getArrangementStyle, + currentStatus: true, + label: undefined, + visible: true, + icons: ['arrangement-concave', 'arrangement-convex', 'arrangement-vertical-right', 'arrangement-vertical-left', 'arrangement-horizontal-bottom', 'arrangement-horizontal-top'], + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 1 + } + ) + menuItemsInitialValues.push( + { + action: 'Freeze / Unfreeze', + actionFunction: floatingObject.freezeToggle, + actionStatus: floatingObject.getFreezeStatus, + currentStatus: true, + label: undefined, + visible: true, + iconPathOn: 'unfreeze', + iconPathOff: 'freeze', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 1 + } + ) + menuItemsInitialValues.push( + { + action: 'Collapse / Uncollapse', + actionFunction: floatingObject.collapseToggle, + actionStatus: floatingObject.getCollapseStatus, + currentStatus: false, + label: undefined, + visible: true, + iconPathOn: 'expand', + iconPathOff: 'collapse', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 1 + } + ) + menuItemsInitialValues.push( + { + action: 'Backup', + actionFunction: floatingObject.payload.executeAction, + actionProject: 'Superalgos', + label: undefined, + visible: true, + iconPathOn: 'backup', + iconPathOff: 'backup', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 2 + } + ) + menuItemsInitialValues.push( + { + action: 'Clone', + actionFunction: floatingObject.payload.executeAction, + actionProject: 'Superalgos', + label: undefined, + visible: true, + iconPathOn: 'clone', + iconPathOff: 'clone', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 2 + } + ) + if (isPersonalData !== true) { + menuItemsInitialValues.push( + { + action: 'Share', + actionFunction: floatingObject.payload.executeAction, + actionProject: 'Superalgos', + label: undefined, + visible: true, + iconPathOn: 'share', + iconPathOff: 'share', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 2 + } + ) + } + menuItemsInitialValues.push( + { + action: 'Parent Detach', + actionFunction: floatingObject.payload.executeAction, + actionProject: 'Superalgos', + label: undefined, + visible: true, + iconPathOn: 'detach', + iconPathOff: 'detach', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 3 + } + ) + menuItemsInitialValues.push( + { + action: 'Reference Detach', + actionFunction: floatingObject.payload.executeAction, + actionProject: 'Superalgos', + label: undefined, + visible: true, + iconPathOn: 'delink', + iconPathOff: 'delink', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 3 + } + ) + + menuItemsInitialValues.push( + { + action: 'Open Documentation', + actionFunction: floatingObject.payload.executeAction, + actionProject: 'Superalgos', + label: undefined, + visible: true, + iconPathOn: 'help', + iconPathOff: 'help', + rawRadius: 12, + targetRadius: 0, + currentRadius: 0, + ring: 4 + } + ) + } + + function getMenuItemsInitialValues(uiObject, floatingObject, payload) { + let menuItemsInitialValues = [] + + let nodeDefinition = getNodeDefinition(payload.node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.editors !== undefined) { + if (nodeDefinition.editors.config === true) { + uiObject.configEditor = newConfigEditor() + uiObject.configEditor.isVisibleFunction = uiObject.isVisibleFunction + uiObject.configEditor.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false) + uiObject.configEditor.initialize() + } + if (nodeDefinition.editors.code === true) { + uiObject.codeEditor = newCodeEditor() + uiObject.codeEditor.isVisibleFunction = uiObject.isVisibleFunction + uiObject.codeEditor.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false) + uiObject.codeEditor.initialize() + } + if (nodeDefinition.editors.formula === true) { + uiObject.formulaEditor = newFormulaEditor() + uiObject.formulaEditor.isVisibleFunction = uiObject.isVisibleFunction + uiObject.formulaEditor.container.connectToParent(uiObject.container, false, false, true, true, false, false, false, false) + uiObject.formulaEditor.initialize() + } + if (nodeDefinition.editors.condition === true) { + uiObject.conditionEditor = newConditionEditor() + uiObject.conditionEditor.isVisibleFunction = uiObject.isVisibleFunction + uiObject.conditionEditor.container.connectToParent(uiObject.container, false, false, false, true, false, false, false, false) + uiObject.conditionEditor.initialize() + } + } + if (nodeDefinition.addLeftIcons === true) { + addLeftIcons(menuItemsInitialValues, floatingObject, nodeDefinition.isPersonalData) + } + if (nodeDefinition.isPinned === true) { + floatingObject.isPinned = true + } + if (nodeDefinition.positionLocked === true) { + floatingObject.positionLocked = true + } + + for (let i = 0; i < nodeDefinition.menuItems.length; i++) { + let menutItemDefinition = nodeDefinition.menuItems[i] + let newMenuItem = JSON.parse(JSON.stringify(menutItemDefinition)) + + /* We need to reference the real function based on its name */ + if (menutItemDefinition.actionFunction !== undefined) { + try { + newMenuItem.actionFunction = eval(menutItemDefinition.actionFunction) + } catch (err) { + console.log('Error at Menu Item Action Function: ' + menutItemDefinition.actionFunction + ' ' + err.stack) + continue + } + } + + /* Adding default values */ + if (newMenuItem.visible === undefined) { + newMenuItem.visible = true + } + + if (newMenuItem.rawRadius === undefined) { + newMenuItem.rawRadius = 12 + } + + if (newMenuItem.targetRadius === undefined) { + newMenuItem.targetRadius = 0 + } + + if (newMenuItem.currentRadius === undefined) { + newMenuItem.currentRadius = 0 + } + + if (newMenuItem.actionProject === undefined) { + newMenuItem.actionProject = 'Superalgos' + } + + menuItemsInitialValues.push(newMenuItem) + } + } else { + if (ERROR_LOG === true) { logger.write('[ERROR] getMenuItemsInitialValues -> UI Object Type not Recognized -> type = ' + payload.node.type) } + } + + return menuItemsInitialValues + } + + function setFloatingObjectBasicProperties(floatingObject, payload) { + const FRICTION = 0.95 + const INITIAL_FRICTION = 0.97 + const INITIAL_FONT_SIZE = 12 * 1.5 + const INITIAL_RADIOUS = 70 + const INITIAL_IMAGE_SIZE = 80 * 1.2 + + let nodeDefinition = getNodeDefinition(payload.node) + if (nodeDefinition === undefined) { + console.log('[WARN] Set up of the object ' + payload.node.name + ' of type ' + payload.node.type + ' can not be completed becasue its definition can not be found at the APP SCHEMA.') + return + } + + if (nodeDefinition.isProjectHead === true) { + level_0() + floatingObject.angleToParent = ANGLE_TO_PARENT.RANGE_360 + return + } + + switch (nodeDefinition.level) { + case 0: { + level_0() + break + } + case 1: { + level_1() + break + } + case 2: { + level_2() + break + } + case 3: { + level_3() + break + } + case 4: { + level_4() + break + } + case 5: { + level_5() + break + } + } + + function level_0() { + floatingObject.targetFriction = 0.93 + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(500) + floatingObject.initializeRadius(INITIAL_RADIOUS) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + } + function level_1() { + floatingObject.targetFriction = 0.94 + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(600) + floatingObject.initializeRadius(INITIAL_RADIOUS) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + + if (payload.node.savedPayload === undefined) { + // floatingObject.angleToParentToggle() + } + } + function level_2() { + floatingObject.targetFriction = FRICTION + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(300) + floatingObject.initializeRadius(INITIAL_RADIOUS - 1) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE - 10) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.GREEN + ', 1)' + + if (payload.node.savedPayload === undefined) { + // floatingObject.angleToParentToggle() + } + } + function level_3() { + floatingObject.targetFriction = FRICTION + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(150) + floatingObject.initializeRadius(INITIAL_RADIOUS - 2) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE - 20) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', 1)' + + if (payload.node.savedPayload === undefined) { + // floatingObject.angleToParentToggle() + } + } + function level_4() { + floatingObject.targetFriction = FRICTION + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(75) + floatingObject.initializeRadius(INITIAL_RADIOUS - 3) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE - 30) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.TITANIUM_YELLOW + ', 1)' + + if (payload.node.savedPayload === undefined) { + // floatingObject.angleToParentToggle() + } + } + function level_5() { + floatingObject.targetFriction = FRICTION + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(50) + floatingObject.initializeRadius(INITIAL_RADIOUS - 4) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE - 40) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.RED + ', 1)' + } + function level_6() { + floatingObject.targetFriction = FRICTION + floatingObject.friction = INITIAL_FRICTION + + floatingObject.initializeMass(25) + floatingObject.initializeRadius(INITIAL_RADIOUS - 5) + floatingObject.initializeImageSize(INITIAL_IMAGE_SIZE - 50) + floatingObject.initializeFontSize(INITIAL_FONT_SIZE) + + floatingObject.fillStyle = 'rgba(' + UI_COLOR.RED + ', 1)' + } + } + + function destroyUiObject(payload) { + if (payload === undefined) { return } + floatingLayer.removeFloatingObject(payload.floatingObject.handle) + + payload.floatingObject.finalize() + payload.floatingObject = undefined + + payload.uiObject.finalize() + payload.uiObject = undefined + + payload.referenceParent = undefined + payload.parent = undefined + payload.chainParent = undefined + payload.node.savedPayload = undefined + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObjectTitle.js b/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObjectTitle.js new file mode 100644 index 000000000..6dd6d2cd4 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Floating-Space/UiObjectTitle.js @@ -0,0 +1,366 @@ + +function newUiObjectTitle() { + const MODULE_NAME = 'UI Object Title' + const ERROR_LOG = true + + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + fitFunction: undefined, + isVisibleFunction: undefined, + allwaysVisible: undefined, + editMode: undefined, + container: undefined, + payload: undefined, + isDefault: undefined, + enterEditMode: enterEditMode, + exitEditMode: exitEditMode, + physics: physics, + draw: draw, + getContainer: getContainer, + finalize: finalize, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME) + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.frame.radius = 0 + + let selfFocusEventSubscriptionId + let selfNotFocuskEventSubscriptionId + let selfMouseClickEventSubscriptionId + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(selfFocusEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfNotFocuskEventSubscriptionId) + thisObject.container.eventHandler.stopListening(selfMouseClickEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.payload = undefined + thisObject.isVisibleFunction = undefined + thisObject.fitFunction = undefined + } + + function initialize(payload) { + thisObject.payload = payload + + thisObject.allwaysVisible = false // Default value + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.isTitleAllwaysVisible === true) { + thisObject.allwaysVisible = true + } + } + + selfFocusEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onFocus', onFocus) + selfNotFocuskEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onNotFocus', onNotFocus) + selfMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + } + + function getContainer(point) { + if (AT_FULL_SCREEN_MODE === true) { return } // Fullscreen mode does not allow INPUT elements + + let container + + if (thisObject.editMode === true) { + if (point.x === VERY_LARGE_NUMBER) { + /* The the mouse leaves the canvas, and event of mouse over with a ridiculous coordinate is triggered so that + anyone can react. In our case, this object has an html input element that is not part of the canvas, so the event is + triggered. We compensate recognizing this coordinate and returning our container. */ + return thisObject.container + } + } + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function physics() { + inheritancePhysics() + defaultPhysics() + } + + function defaultPhysics() { + if (thisObject.payload.title !== 'New ' + thisObject.payload.node.type && thisObject.payload.title !== 'My ' + thisObject.payload.node.type) { + thisObject.isDefault = false + } else { + thisObject.isDefault = true + } + } + + function inheritancePhysics() { + if (thisObject.payload.title === undefined) { return } + + /* It is possible to override the default title by setting the APP SCHEMA property 'title' */ + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition.title !== undefined) { + thisObject.payload.title = '' + thisObject.payload.node.name = '' + let separator = '' + for (let i = 0; i < nodeDefinition.title.length; i++) { + let titleReference = nodeDefinition.title[i] + switch (titleReference) { + case 'Use Parent': { + let nodeToUse = thisObject.payload.parentNode + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + thisObject.payload.title = thisObject.payload.title + separator + nodeToUse.payload.title + thisObject.payload.node.name = thisObject.payload.node.name + separator + nodeToUse.payload.node.name + } + break + } + case 'Use Reference Parent': { + let nodeToUse = thisObject.payload.node.payload.referenceParent + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + thisObject.payload.title = thisObject.payload.title + separator + nodeToUse.payload.title + thisObject.payload.node.name = thisObject.payload.node.name + separator + nodeToUse.payload.node.name + } + break + } + case 'Use Reference Parent Type': { + let nodeToUse = thisObject.payload.node.payload.referenceParent + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + thisObject.payload.title = thisObject.payload.title + separator + nodeToUse.type + thisObject.payload.node.name = thisObject.payload.node.name + separator + nodeToUse.type + } + break + } + case 'Use Reference Grandparent': { + let nodeToUse = thisObject.payload.node.payload.referenceParent + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + nodeToUse = thisObject.payload.node.payload.referenceParent.payload.referenceParent + if (nodeToUse !== undefined) { + thisObject.payload.title = thisObject.payload.title + separator + nodeToUse.payload.title + thisObject.payload.node.name = thisObject.payload.node.name + separator + nodeToUse.payload.node.name + } + } + break + } + case 'Use Reference Parent Parent': { + let nodeToUse = thisObject.payload.node.payload.referenceParent + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + nodeToUse = thisObject.payload.node.payload.referenceParent.payload.parentNode + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + thisObject.payload.title = thisObject.payload.title + separator + nodeToUse.payload.title + thisObject.payload.node.name = thisObject.payload.node.name + separator + nodeToUse.payload.node.name + } + } + break + } + case 'Use Reference Parent Parent Parent': { + let nodeToUse = thisObject.payload.node.payload.referenceParent + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + nodeToUse = thisObject.payload.node.payload.referenceParent.payload.parentNode + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + nodeToUse = thisObject.payload.node.payload.referenceParent.payload.parentNode.payload.parentNode + if (nodeToUse !== undefined && nodeToUse.payload !== undefined) { + thisObject.payload.title = thisObject.payload.title + separator + nodeToUse.payload.title + thisObject.payload.node.name = thisObject.payload.node.name + separator + nodeToUse.payload.node.name + } + } + } + break + } + default: { + if (titleReference.indexOf('Use Child @') === 0) { + let propertyName = titleReference.substring(titleReference.indexOf('@') + 1, titleReference.length) + let childNode = thisObject.payload.node[propertyName] + if (childNode === undefined) { break } + thisObject.payload.title = thisObject.payload.title + separator + childNode.name + thisObject.payload.node.name = thisObject.payload.node.name + separator + childNode.name + } + } + } + separator = ' ' + } + } + + /* The name becomes the title. */ + if (thisObject.payload.node.name !== undefined) { + thisObject.payload.title = thisObject.payload.node.name + } + + let title = trimTitle(thisObject.payload.title) + + /* Here we set the dimensions and position of this object */ + const FRAME_HEIGHT = 25 + const Y_OFFSET = -20 + const FRAME_WIDTH = (title.length + 2) / 2 * thisObject.payload.floatingObject.currentFontSize * FONT_ASPECT_RATIO * 1.2 * 2 + + if (thisObject.isOnFocus === true) { + thisObject.container.frame.position.x = 0 - FRAME_WIDTH / 2 + thisObject.container.frame.position.y = 0 - thisObject.container.frame.radius * 1 / 2 - thisObject.payload.floatingObject.currentFontSize * FONT_ASPECT_RATIO + Y_OFFSET - FRAME_HEIGHT + } else { + thisObject.container.frame.position.x = 0 - FRAME_WIDTH / 2 + thisObject.container.frame.position.y = 0 - thisObject.payload.floatingObject.currentImageSize / 2 - FRAME_HEIGHT + } + + thisObject.container.frame.width = FRAME_WIDTH + thisObject.container.frame.height = FRAME_HEIGHT + + if (thisObject.editMode === true) { + let inputPosition = { + x: 0, + y: 0 + CURRENT_TOP_MARGIN + Y_OFFSET + } + + inputPosition = thisObject.container.frame.frameThisPoint(inputPosition) + + if (inputPosition.y < CURRENT_TOP_MARGIN) { exitEditMode() } + + let inputDiv = document.getElementById('inputDiv') + inputDiv.style = 'position:absolute; top:' + inputPosition.y + 'px; left:' + inputPosition.x + 'px; z-index:1; ' + } + } + + function onFocus() { + thisObject.isOnFocus = true + } + + function onNotFocus() { + thisObject.isOnFocus = false + exitEditMode() + } + + function onMouseClick(event) { + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition.title !== undefined) { return } + + let checkPoint = { + x: 0, + y: 0 + } + checkPoint = thisObject.container.frame.frameThisPoint(checkPoint) + + if (thisObject.isVisibleFunction(checkPoint) === true) { + enterEditMode() + } + } + + function exitEditMode() { + if (thisObject.editMode === true) { + thisObject.editMode = false + let input = document.getElementById('input') + input.style.display = 'none' + if (input.value.length > 2) { + thisObject.payload.title = input.value + thisObject.payload.node.name = input.value + } + } + let inputDiv = document.getElementById('inputDiv') + inputDiv.style = 'position:absolute; top:' + '-100' + 'px; left:' + '-100' + 'px; z-index:1; ' + } + + function enterEditMode() { + const WIDTH = thisObject.container.frame.width + const HEIGHT = thisObject.container.frame.height + let fontSize = thisObject.payload.floatingObject.currentFontSize + + thisObject.editMode = true + let input = document.getElementById('input') + input.value = thisObject.payload.title + + let backgroundColor = '0, 0, 0' + if (thisObject.payload.uiObject.codeEditor !== undefined) { + if (thisObject.payload.uiObject.codeEditor.visible === true) { + backgroundColor = '204, 88, 53' + } + } + + input.style = 'resize: none; border: none; outline: none; box-shadow: none; overflow:hidden; font-family: Saira Condensed; font-size: ' + fontSize + 'px; background-color: rgb(' + backgroundColor + ');color:rgb(255, 255, 255); width: ' + WIDTH + 'px; height: ' + HEIGHT + 'px' + input.style.display = 'block' + input.focus() + } + + function draw() { + if (thisObject.isOnFocus === true || thisObject.allwaysVisible === true || thisObject.isDefault === false) { + if (thisObject.payload.uiObject.codeEditor !== undefined) { + if (thisObject.payload.uiObject.codeEditor.visible !== true) { + drawTitleBackground() + } + } else { + drawTitleBackground() + } + + drawText() + } + } + + function drawTitleBackground() { + if (thisObject.editMode !== true) { + let params = { + cornerRadius: 3, + lineWidth: 0.1, + container: thisObject.container, + borderColor: UI.projects.superalgos.spaces.floatingSpace.style.backgroundColor, + backgroundColor: UI.projects.superalgos.spaces.floatingSpace.style.backgroundColor, + castShadow: false, + opacity: 0.25 + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + } + } + + function drawText() { + if (thisObject.editMode !== true) { + let radius = thisObject.container.frame.radius + let labelPoint + let fontSize = thisObject.payload.floatingObject.currentFontSize + let label + + if (radius > 6 && (thisObject.isOnFocus === true || thisObject.allwaysVisible === true || thisObject.isDefault === false)) { + browserCanvasContext.strokeStyle = thisObject.payload.floatingObject.typeStrokeStyle + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + label = thisObject.payload.title + + if (label !== undefined) { + label = trimTitle(label) + + labelPoint = { + x: 5, + y: thisObject.container.frame.height * 0.8 + } + + labelPoint = thisObject.container.frame.frameThisPoint(labelPoint) + + if (UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + labelPoint = UI.projects.superalgos.spaces.floatingSpace.transformPointToMap(labelPoint) + labelPoint.x = labelPoint.x - label.length / 2 * fontSize * FONT_ASPECT_RATIO + labelPoint.y = labelPoint.y - 35 + + let nodeDefinition = getNodeDefinition(thisObject.payload.node) + if (nodeDefinition !== undefined) { + if (nodeDefinition.isHierarchyHead !== true && nodeDefinition.isProjectHead !== true) { + return + } + } + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = thisObject.payload.floatingObject.typeStrokeStyle + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + } + } + } + + function trimTitle(title) { + if (title === undefined) { return } + const MAX_LABEL_LENGTH = 50 + if (title.length > MAX_LABEL_LENGTH) { + title = title.substring(0, MAX_LABEL_LENGTH) + '...' + } + return title + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/Layer.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/Layer.js new file mode 100644 index 000000000..ced540286 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/Layer.js @@ -0,0 +1,700 @@ +function newLayer() { + const MODULE_NAME = 'Layer' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + container: undefined, + status: LAYER_STATUS.OFF, + fitFunction: undefined, + payload: undefined, + definition: undefined, + panels: undefined, + baseAsset: undefined, + quotedAsset: undefined, + market: undefined, + exchange: undefined, + bot: undefined, + productDefinition: undefined, + dataMine: undefined, + plotterModule: undefined, + exchangeIcon: undefined, + plotterTypeIcon: undefined, + baseAssetIcon: undefined, + quotedAssetIcon: undefined, + networkNode: undefined, + checkStatusAtShutDown: checkStatusAtShutDown, + physics: physics, + draw: draw, + turnOff: turnOff, + turnOn: turnOn, + setDatetime: setDatetime, + setTimeFrame: setTimeFrame, + onMarketFileLoaded: onMarketFileLoaded, + onDailyFileLoaded: onDailyFileLoaded, + onSingleFileLoaded: onSingleFileLoaded, + onFileSequenceLoaded: onFileSequenceLoaded, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + let LOADING_FILL_STYLE = 'rgba(234, 143, 23, @Opacity)' + let LOADED_FILL_STYLE = 'rgba(45, 232, 28, @Opacity)' + let UNLOADED_FILL_STYLE = 'rgba(226, 226, 226, @Opacity)' + + let LOADING_STROKE_STYLE = 'rgba(234, 143, 23, @Opacity)' + let LOADED_STROKE_STYLE = 'rgba(45, 232, 28, @Opacity)' + let UNLOADED_STROKE_STYLE = 'rgba(226, 226, 226, @Opacity)' + + let marketFileProgressBar = { + value: 0, + animatedValue: 0, + fillStyle: UNLOADED_FILL_STYLE, + strokeStyle: UNLOADED_STROKE_STYLE, + opacity: 0.00 + } + + let dailyFileProgressBar = { + value: 0, + animatedValue: 0, + fillStyle: UNLOADED_FILL_STYLE, + strokeStyle: UNLOADED_STROKE_STYLE, + opacity: 0.00 + } + + let singleFileProgressBar = { + value: 0, + animatedValue: 0, + fillStyle: UNLOADED_FILL_STYLE, + strokeStyle: UNLOADED_STROKE_STYLE, + opacity: 0.00 + } + + let fileSequenceProgressBar = { + value: 0, + animatedValue: 0, + fillStyle: UNLOADED_FILL_STYLE, + strokeStyle: UNLOADED_STROKE_STYLE, + opacity: 0.00 + } + + let timeFrame = INITIAL_TIME_PERIOD + let datetime = NEW_SESSION_INITIAL_DATE + + /* Add an Event Handler */ + + thisObject.eventHandler = newEventHandler() + let imagesLoaded = 0 + let onMouseClickEventSuscriptionId + let panelsVisibleButton + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseClickEventSuscriptionId) + + thisObject.container.finalize() + thisObject.eventHandler.finalize() + thisObject.container = undefined + thisObject.eventHandler = undefined + thisObject.status = undefined + thisObject.fitFunction = undefined + thisObject.payload = undefined + thisObject.definition = undefined + thisObject.panels = undefined + + timeFrame = undefined + datetime = undefined + + LOADING_FILL_STYLE = undefined + LOADED_FILL_STYLE = undefined + UNLOADED_FILL_STYLE = undefined + + LOADING_STROKE_STYLE = undefined + LOADED_STROKE_STYLE = undefined + UNLOADED_STROKE_STYLE = undefined + + marketFileProgressBar = undefined + dailyFileProgressBar = undefined + singleFileProgressBar = undefined + fileSequenceProgressBar = undefined + + panelsVisibleButton = undefined + + thisObject.baseAsset = undefined + thisObject.quotedAsset = undefined + thisObject.market = undefined + thisObject.exchange = undefined + thisObject.plotterModule = undefined + thisObject.exchangeIcon = undefined + thisObject.plotterTypeIcon = undefined + thisObject.baseAssetIcon = undefined + thisObject.quotedAssetIcon = undefined + thisObject.networkNode = undefined + thisObject.bot = undefined + thisObject.productDefinition = undefined + thisObject.dataMine = undefined + } + + function initialize(callBackFunction) { + /* Create this objects continer */ + try { + thisObject.container = newContainer() + thisObject.container.initialize(MODULE_NAME + ' ' + thisObject.payload.node.id) + thisObject.container.isDraggeable = false + thisObject.container.isClickeable = true + + /* Lets set the basic dimensions of this thisObject. */ + let position = { + x: 0, + y: 0 + } + + thisObject.container.frame.position = position + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL + thisObject.container.frame.height = 70 + + let functionLibraryProtocolNode = newProtocolNode() + let lightingPath = + '->Layer->' + + 'Data Product->' + + 'Data Product Folder->Data Product Folder->Data Product Folder->Data Product Folder->Data Product Folder->' + + 'Bot Products->Data Mine Products->Trading Mine Products->' + + 'Market Data Products->Exchange Data Products->' + + 'Session Reference->Market Trading Products->Exchange Trading Products->' + + 'Data Mines Data->Trading Mines Data->Data Storage->Network Node->' + + 'Data Storage->Network Node->' + + 'Backtesting Session->Paper Trading Session->Forward Testing Session->Live Trading Session->' + + 'Trading System Reference->Trading Engine Reference->Trading System->Trading Engine->' + + 'Market->Market Base Asset->Asset->' + + 'Market Quoted Asset->Asset->' + + 'Exchange Markets->Crypto Exchange->' + + 'Product Definition->' + + 'Product Definition Folder->Product Definition Folder->Product Definition Folder->Product Definition Folder->Product Definition Folder->' + + 'Sensor Bot->Indicator Bot->Trading Bot->' + + 'Data Mine->Trading Mine->' + + 'Dataset Definition->' + + 'Record Definition->Record Property->Formula->' + + 'Data Building Procedure->Procedure Loop->Javascript Code->Procedure Initialization->Javascript Code->' + + 'Calculations Procedure->Procedure Loop->Javascript Code->Procedure Initialization->Javascript Code->' + + 'Plotter Module->Plotter->' + + 'Shapes->' + + 'Chart Points->Point->Point Formula->' + + 'Polygon->Polygon Condition->Polygon Body->Style->Style Condition->Style->' + + 'Polygon Border->Style->Style Condition->Style->' + + 'Polygon Vertex->Point->' + + 'Image->Image Condition->Image Position->Point->' + + 'Text->Text Condition->Text Position->Point->Text Formula->Text Style->' + + 'Plotter Panel->Javascript Code->Panel Data->Data Formula->Text Style->' + + 'Nodes Highlights->Nodes Values->Nodes Errors->Nodes Warnings->Nodes Infos->Nodes Status->Nodes Progress->Nodes Running->Nodes Announcements->Record Values->' + thisObject.definition = functionLibraryProtocolNode.getProtocolNode(thisObject.payload.node, false, true, true, false, false, lightingPath) + + /* Without this I can not continue, because I can not even show error. */ + if (thisObject.payload === undefined) { + console.log(MODULE_NAME + ' ' + 'thisObject.payload === undefined') + return + } + if (thisObject.payload.uiObject === undefined) { + console.log(MODULE_NAME + ' ' + 'thisObject.payload.uiObject === undefined') + return + } + if (thisObject.payload.node === undefined) { + console.log(MODULE_NAME + ' ' + 'thisObject.payload.node === undefined') + return + } + + /* Here we validate that we have all the needed information */ + if (UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Data Product', undefined, false, true, true, true) === undefined) { + thisObject.payload.uiObject.setErrorMessage('Data Product not Found') + return + } + if (UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Market', undefined, false, true, true, true) === undefined) { + thisObject.payload.uiObject.setErrorMessage('Market not Found') + return + } + + thisObject.productDefinition = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Product Definition', undefined, false, true, true, true) + if (thisObject.productDefinition === undefined) { + thisObject.payload.uiObject.setErrorMessage('Product Definition not Found') + return + } + + thisObject.bot = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Sensor Bot', undefined, false, true, true, true) + if (thisObject.bot === undefined) { + thisObject.bot = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Indicator Bot', undefined, false, true, true, true) + } + if (thisObject.bot === undefined) { + thisObject.bot = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Trading Bot', undefined, false, true, true, true) + } + if (thisObject.bot === undefined) { + thisObject.payload.uiObject.setErrorMessage('Bot not Found') + return + } + + thisObject.dataMine = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Data Mine', undefined, false, true, true, true) + if (thisObject.dataMine === undefined) { + thisObject.dataMine = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Trading Mine', undefined, false, true, true, true) + if (thisObject.dataMine === undefined) { + thisObject.payload.uiObject.setErrorMessage('Data Mine or Trading Mine not Found') + return + } + } + + thisObject.exchange = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Crypto Exchange', undefined, false, true, true, true) + if (thisObject.exchange === undefined) { + thisObject.payload.uiObject.setErrorMessage('Crypto Exchange not Found') + return + } + + thisObject.baseAsset = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Market Base Asset', undefined, false, true, true, true) + if (thisObject.baseAsset !== undefined) { + thisObject.baseAsset = thisObject.baseAsset.referenceParent + } + if (thisObject.baseAsset === undefined) { + thisObject.payload.uiObject.setErrorMessage('Market Base Asset not Found') + return + } + + thisObject.quotedAsset = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Market Quoted Asset', undefined, false, true, true, true) + if (thisObject.quotedAsset !== undefined) { + thisObject.quotedAsset = thisObject.quotedAsset.referenceParent + } + if (thisObject.quotedAsset === undefined) { + thisObject.payload.uiObject.setErrorMessage('Market Quoted Asset not Found') + return + } + + thisObject.plotterModule = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Plotter Module', undefined, false, false, false, true) + if (thisObject.plotterModule === undefined) { + thisObject.payload.uiObject.setErrorMessage('Plotter Module not Found') + return + } + + thisObject.networkNode = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(thisObject.definition, 'Network Node', undefined, false, true, true, true) + if (thisObject.networkNode === undefined) { + thisObject.payload.uiObject.setErrorMessage('Network Node not Found') + return + } + + /* Lets listen to our own events to react when we have a Mouse Click */ + onMouseClickEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + + /* Get ready to draw this layer */ + thisObject.market = thisObject.baseAsset.config.codeName + '/' + thisObject.quotedAsset.config.codeName + + thisObject.exchangeIcon = getIcon(thisObject.exchange) + + if (thisObject.plotterModule.config.icon !== undefined) { + thisObject.plotterTypeIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', thisObject.plotterModule.config.icon) + } + + thisObject.baseAssetIcon = getIcon(thisObject.baseAsset) + thisObject.quotedAssetIcon = getIcon(thisObject.quotedAsset) + + function getIcon(node) { + let nodeDefinition = getNodeDefinition(node) + let iconName + let icon + if (nodeDefinition.alternativeIcons !== undefined) { + for (let i = 0; i < nodeDefinition.alternativeIcons.length; i++) { + let alternativeIcon = nodeDefinition.alternativeIcons[i] + if (alternativeIcon.codeName === node.config.codeName) { + iconName = alternativeIcon.iconName + } + } + } + if (iconName !== undefined) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', iconName) + } else { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', nodeDefinition.icon) + } + return icon + } + + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = ' + err.stack) } + } + } + + function checkStatusAtShutDown() { + /* Mechanism to recover a layer that was left loading the last time the browser was shut dowm. */ + let storedValue = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'status') + if (storedValue !== undefined) { + if (storedValue === LAYER_STATUS.LOADING) { + resetProgressBars() + changeStatusTo(LAYER_STATUS.LOADING) + } + } + } + + function getContainer(point) { + if (panelsVisibleButton !== undefined) { + let container = panelsVisibleButton.getContainer(point) + if (container !== undefined) { + return container + } + } + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function setDatetime(pDatetime) { + /* + When the datetime changes from one day to another, this forces cursors to potentially load more files, thus we reset this counter and + get ready to receive events on files loaded. + */ + let currentDate = Math.trunc(datetime.valueOf() / ONE_DAY_IN_MILISECONDS) + let newDate = Math.trunc(pDatetime.valueOf() / ONE_DAY_IN_MILISECONDS) + + datetime = pDatetime + + if (currentDate !== newDate) { + if (timeFrame <= _1_HOUR_IN_MILISECONDS) { + dailyFileProgressBar.animatedValue = 0 + } + } + } + + function setTimeFrame(pTimeFrame) { + /* + When the time period below or equal to 1 hour changes, this forces cursors to potentially load more files, thus we reset this counter and + get ready to receive events on files loaded. + */ + if (timeFrame !== pTimeFrame) { + timeFrame = pTimeFrame + + if (timeFrame <= _1_HOUR_IN_MILISECONDS) { + dailyFileProgressBar.animatedValue = 0 + } + } + } + + function onMarketFileLoaded(event) { + if (event.totalValue === 0) { + marketFileProgressBar.value = 100 + return + } + marketFileProgressBar.value = Math.trunc((event.currentValue + event.filesNotLoaded) * 100 / event.totalValue) + marketFileProgressBar.fillStyle = LOADING_FILL_STYLE + marketFileProgressBar.strokeStyle = LOADING_STROKE_STYLE + + if (marketFileProgressBar.value > 100) { marketFileProgressBar.value = 100 } + } + + function onDailyFileLoaded(event) { + if (event.totalValue === 0) { + dailyFileProgressBar.value = 100 + return + } + dailyFileProgressBar.value = Math.trunc(event.currentValue * 100 / event.totalValue) + dailyFileProgressBar.fillStyle = LOADING_FILL_STYLE + dailyFileProgressBar.strokeStyle = LOADING_STROKE_STYLE + + if (dailyFileProgressBar.value > 100) { dailyFileProgressBar.value = 100 } + } + + function onSingleFileLoaded(event) { + if (event.totalValue === 0) { + singleFileProgressBar.value = 100 + return + } + singleFileProgressBar.value = Math.trunc(event.currentValue * 100 / event.totalValue) + singleFileProgressBar.fillStyle = LOADING_FILL_STYLE + singleFileProgressBar.strokeStyle = LOADING_STROKE_STYLE + + if (singleFileProgressBar.value > 100) { singleFileProgressBar.value = 100 } + } + + function onFileSequenceLoaded(event) { + if (event.totalValue === 0) { + fileSequenceProgressBar.value = 100 + return + } + fileSequenceProgressBar.value = Math.trunc(event.currentValue * 100 / event.totalValue) + fileSequenceProgressBar.fillStyle = LOADING_FILL_STYLE + fileSequenceProgressBar.strokeStyle = LOADING_STROKE_STYLE + + if (fileSequenceProgressBar.value > 100) { fileSequenceProgressBar.value = 100 } + } + + function turnOff() { + if (thisObject.status !== LAYER_STATUS.OFF) { + resetProgressBars() + changeStatusTo(LAYER_STATUS.OFF) + } + } + + function turnOn() { + if (thisObject.status === LAYER_STATUS.OFF) { + resetProgressBars() + changeStatusTo(LAYER_STATUS.LOADING) + } + } + + function onMouseClick(event) { + switch (thisObject.status) { + case LAYER_STATUS.ON: + resetProgressBars() + changeStatusTo(LAYER_STATUS.OFF) + break + case LAYER_STATUS.OFF: + resetProgressBars() + changeStatusTo(LAYER_STATUS.LOADING) + break + case LAYER_STATUS.LOADING: + changeStatusTo(LAYER_STATUS.OFF) + break + } + } + + function resetProgressBars() { + marketFileProgressBar.animatedValue = 0 + marketFileProgressBar.value = 0 + marketFileProgressBar.fillStyle = UNLOADED_FILL_STYLE + marketFileProgressBar.strokeStyle = UNLOADED_STROKE_STYLE + + dailyFileProgressBar.animatedValue = 0 + dailyFileProgressBar.value = 0 + dailyFileProgressBar.fillStyle = UNLOADED_FILL_STYLE + dailyFileProgressBar.strokeStyle = UNLOADED_STROKE_STYLE + + singleFileProgressBar.animatedValue = 0 + singleFileProgressBar.value = 0 + singleFileProgressBar.fillStyle = UNLOADED_FILL_STYLE + singleFileProgressBar.strokeStyle = UNLOADED_STROKE_STYLE + + fileSequenceProgressBar.animatedValue = 0 + fileSequenceProgressBar.value = 0 + fileSequenceProgressBar.fillStyle = UNLOADED_FILL_STYLE + fileSequenceProgressBar.strokeStyle = UNLOADED_STROKE_STYLE + } + + function physics() { + statusPhysics() + childrenPhysics() + } + + function childrenPhysics() { + /* Panels Visible Button Setup */ + if (thisObject.status === LAYER_STATUS.ON) { + if (panelsVisibleButton === undefined) { + if (thisObject.panels !== undefined) { + if (thisObject.panels.length > 0) { + panelsVisibleButton = newPanelsVisibleButton() + + let storedValue = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'showPanels') + if (storedValue !== undefined) { + if (storedValue === true || storedValue === false) { + panelsVisibleButton.showPanels = storedValue + } + } + + panelsVisibleButton.initialize(thisObject.panels) + panelsVisibleButton.container.connectToParent(thisObject.container) + } + } + } else { + UI.projects.superalgos.utilities.nodeConfig.savePropertyAtNodeConfig(thisObject.payload, 'showPanels', panelsVisibleButton.showPanels) + } + } + } + + function statusPhysics() { + /* We retrieve the stored status at the config. */ + try { + let storedValue = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'status') + + if (storedValue !== undefined) { + if (storedValue !== thisObject.status) { + if (storedValue === LAYER_STATUS.ON && thisObject.status === LAYER_STATUS.OFF) { + resetProgressBars() + changeStatusTo(LAYER_STATUS.LOADING) + } + if (storedValue === LAYER_STATUS.OFF) { + resetProgressBars() + changeStatusTo(LAYER_STATUS.OFF) + } + } + } else { + resetProgressBars() + changeStatusTo(LAYER_STATUS.OFF) + } + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + + if ( + thisObject.status === LAYER_STATUS.LOADING && + marketFileProgressBar.value === 100 && + dailyFileProgressBar.value === 100 + ) { + changeStatusTo(LAYER_STATUS.ON) + } + } + + function changeStatusTo(newStatus) { + if (thisObject.status !== newStatus) { + thisObject.status = newStatus + + if (thisObject.status === LAYER_STATUS.OFF) { + if (panelsVisibleButton !== undefined) { + panelsVisibleButton.finalize() + panelsVisibleButton = undefined + } + } + + UI.projects.superalgos.utilities.nodeConfig.savePropertyAtNodeConfig(thisObject.payload, 'status', thisObject.status) + let eventData = thisObject + thisObject.container.eventHandler.raiseEvent('Status Changed', eventData) + } + } + + function draw() { + drawLayer() + if (panelsVisibleButton !== undefined) { + panelsVisibleButton.draw() + } + } + + function drawLayer() { + drawLayerDisplay() + } + + function drawProgressBar(progressBar, lineWidth, offsetY) { + const ANIMATED_INCREMENT = 5 + const OPACITY_INCREMENT = 0.01 + const OPACITY_MIN = 1 + + let point1 + let point2 + let horizontalMargin = 12 + + if (progressBar.value === 0) { + return + } + + /* Animate */ + if (progressBar.animatedValue < progressBar.value) { + progressBar.animatedValue = progressBar.animatedValue + ANIMATED_INCREMENT + progressBar.opacity = progressBar.opacity + OPACITY_INCREMENT + } + + if (progressBar.animatedValue >= 100) { + progressBar.animatedValue = 100 + + progressBar.opacity = progressBar.opacity - OPACITY_INCREMENT + if (progressBar.opacity < OPACITY_MIN) { progressBar.opacity = OPACITY_MIN } + + progressBar.strokeStyle = LOADED_STROKE_STYLE.replace('@Opacity', progressBar.opacity.toString()) + } + + point1 = { + x: 0 + horizontalMargin, + y: thisObject.container.frame.height + offsetY + } + + point2 = { + x: (thisObject.container.frame.width - horizontalMargin * 2) * progressBar.animatedValue / 100 + horizontalMargin, + y: thisObject.container.frame.height + offsetY + } + + /* Now the transformations. */ + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + + point1 = thisObject.fitFunction(point1) + point2 = thisObject.fitFunction(point2) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = progressBar.strokeStyle.replace('@Opacity', progressBar.opacity.toString()) + + browserCanvasContext.setLineDash([2, 4]) + browserCanvasContext.lineWidth = lineWidth + browserCanvasContext.stroke() + } + + function drawLayerDisplay() { + let label1 = thisObject.payload.node.name + let label2 = thisObject.exchange.name.substring(0, 15) + ' - ' + thisObject.market + let label3 = thisObject.status.toUpperCase() + + if (label1 !== undefined) { + label1 = label1.substring(0, 30) + } + + let backgroundColor = UI_COLOR.BLACK + + const RED_LINE_HIGHT = 4 + const OPACITY = 0.75 + + let params = { + cornerRadius: 0, + lineWidth: 1, + container: thisObject.container, + borderColor: UI_COLOR.RUSTED_RED, + castShadow: false, + backgroundColor: backgroundColor, + opacity: OPACITY + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + + let parentLabel1FontSize = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload.parentNode.payload, 'label1FontSize') + let parentLabel2FontSize = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload.parentNode.payload, 'label2FontSize') + let parentLabel3FontSize = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload.parentNode.payload, 'label3FontSize') + + let label1FontSize = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'label1FontSize') + let label2FontSize = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'label2FontSize') + let label3FontSize = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'label3FontSize') + + if (parentLabel1FontSize !== undefined) { + label1FontSize = parentLabel1FontSize + } + if (parentLabel2FontSize !== undefined) { + label2FontSize = parentLabel2FontSize + } + if (parentLabel3FontSize !== undefined) { + label3FontSize = parentLabel3FontSize + } + + if (label1FontSize === undefined) { + if (label1.length > 20) { + label1FontSize = 12 + } else { + label1FontSize = 15 + } + } + + if (label2FontSize === undefined) { + label2FontSize = 10 + } + + if (label3FontSize === undefined) { + label3FontSize = 9 + } + + UI.projects.superalgos.utilities.drawPrint.drawLabel(label1, 1 / 2, 5.2 / 10, -5, 0, label1FontSize, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawLabel(label2, 1 / 2, 8.2 / 10, -5, 0, label2FontSize, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawLabel(label3, 1 / 2, 9.5 / 10, -5, 0, label3FontSize, thisObject.container) + + drawProgressBar(marketFileProgressBar, 2, -45 + 18) + drawProgressBar(dailyFileProgressBar, 2, -46 + 18) + drawProgressBar(singleFileProgressBar, 2, -47 + 18) + drawProgressBar(fileSequenceProgressBar, 2, -48 + 18) + + UI.projects.superalgos.utilities.drawPrint.drawIcon(thisObject.exchangeIcon, 1 / 8, 2 / 10, 0, 0, 14, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(thisObject.plotterTypeIcon, 7 / 8, 2 / 10, 0, 0, 14, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(thisObject.baseAssetIcon, 3.4 / 8, 2 / 10, 0, 0, 14, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(thisObject.quotedAssetIcon, 4.6 / 8, 2 / 10, 0, 0, 14, thisObject.container) + } +} + diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/LayersPanel.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/LayersPanel.js new file mode 100644 index 000000000..050fa9946 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/LayersPanel.js @@ -0,0 +1,522 @@ +function newLayersPanel() { + let thisObject = { + name: 'Layers Panel', + fitFunction: undefined, + container: undefined, + layers: [], + payload: undefined, + isVisible: true, + upDownButton: undefined, + leftRightButton: undefined, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize() + thisObject.container.isDraggeable = true + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + + let isInitialized = false + let mouseOverCounter = 0 + + let layersMap = new Map() + let visibleLayers = [] + let firstVisibleLayer = 1 + + const LAYER_SEPARATION = 0 + + let headerHeight = 40 + let footerHeight = 10 + let layerHeight = 70 + let desiredVisibleLayers = 5 + let posibleVisibleLayers = 5 + let desiredPanelHeight = (layerHeight + LAYER_SEPARATION) * desiredVisibleLayers + headerHeight + footerHeight + let posiblePanelHeight = (layerHeight + LAYER_SEPARATION) * posibleVisibleLayers + headerHeight + footerHeight + + let onMouseWheelEventSuscriptionId + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + + let isMouseOver + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSuscriptionId) + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + + thisObject.upDownButton.finalize() + thisObject.upDownButton = undefined + + thisObject.leftRightButton.finalize() + thisObject.leftRightButton = undefined + + layersMap = undefined + visibleLayers = undefined + + thisObject.container.finalize() + thisObject.container = undefined + + thisObject.payload = undefined + thisObject = undefined + } + + function initialize() { + thisObject.container.name = thisObject.payload.node.name + thisObject.container.frame.containerName = thisObject.container.name + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL + thisObject.container.frame.height = headerHeight + + let position = { // Default position + x: UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.x, + y: UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y + } + + thisObject.container.frame.position = position + UI.projects.superalgos.utilities.loadSaveFrame.loadFrame(thisObject.payload, thisObject.container.frame) + + thisObject.upDownButton = newUpDownButton() + thisObject.upDownButton.parentContainer = thisObject.container + thisObject.upDownButton.container.frame.parentFrame = thisObject.container.frame + thisObject.upDownButton.fitFunction = thisObject.fitFunction + thisObject.upDownButton.initialize() + + thisObject.leftRightButton = newLeftRightButton() + thisObject.leftRightButton.parentContainer = thisObject.container + thisObject.leftRightButton.container.frame.parentFrame = thisObject.container.frame + thisObject.leftRightButton.fitFunction = thisObject.fitFunction + thisObject.leftRightButton.initialize() + + onMouseWheelEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + + readObjectState() + + isInitialized = true + } + + function onMouseOver(event) { + isMouseOver = true + } + + function onMouseNotOver() { + isMouseOver = false + } + + function saveObjectStateVisibleLayers() { + UI.projects.superalgos.utilities.nodeConfig.savePropertyAtNodeConfig(thisObject.payload, 'visibleLayers', desiredVisibleLayers) + } + + function saveObjectStatePanelLocation() { + let panelLocation = { + upOrDown: thisObject.upDownButton.status, + leftOrRight: thisObject.leftRightButton.status + } + UI.projects.superalgos.utilities.nodeConfig.savePropertyAtNodeConfig(thisObject.payload, 'panelLocation', panelLocation) + } + + function readObjectState() { + let storedValue + + storedValue = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'visibleLayers') + + if (isNaN(storedValue) || storedValue === null || storedValue === undefined) { + // not using this value + saveObjectStateVisibleLayers() // this overrides any invalid value at the config. + return + } else { + if (storedValue < 0) { storedValue = 0 } + if (storedValue > thisObject.layers.length) { + storedValue = thisObject.layers.length + } + + if (storedValue !== desiredVisibleLayers) { + desiredVisibleLayers = storedValue + desiredPanelHeight = (layerHeight + LAYER_SEPARATION) * desiredVisibleLayers + headerHeight + footerHeight + calculateVisbleLayers() + } + } + + storedValue = UI.projects.superalgos.utilities.nodeConfig.loadPropertyFromNodeConfig(thisObject.payload, 'panelLocation') + + if (storedValue === null || storedValue === undefined) { + // not using this value + saveObjectStatePanelLocation() // this overrides any invalid value at the config. + return + } else { + if (storedValue.upOrDown !== 'up' && storedValue.upOrDown !== 'down') { + saveObjectStatePanelLocation() // this overrides any invalid value at the config. + return + } + if (storedValue.leftOrRight !== 'left' && storedValue.leftOrRight !== 'right') { + saveObjectStatePanelLocation() // this overrides any invalid value at the config. + return + } + thisObject.upDownButton.setStatus(storedValue.upOrDown) + thisObject.leftRightButton.setStatus(storedValue.leftOrRight) + saveObjectStatePanelLocation() + } + } + + function removeLayer(id) { + let layer = layersMap.get(id) + layersMap.delete(id) + layer.turnOff() + layer.container.eventHandler.stopListening(layer.onLayerStatusChangedEventSuscriptionId) + layer.container.eventHandler.stopListening(layer.onMouseWheelEventSuscriptionId) + layer.finalize() + } + + function addLayer(layerNode) { + /* Now we create Product objects */ + let layer = newLayer() + layer.payload = layerNode.payload + layer.nodeId = layerNode.payload.node.id + layer.fitFunction = thisObject.fitFunction + + /* Initialize it */ + layer.initialize(onInitialized) + + function onInitialized(err) { + if (err.result === GLOBAL.DEFAULT_OK_RESPONSE.result) { + layersMap.set(layerNode.id, layer) + + /* Container Stuff */ + layer.container.frame.parentFrame = thisObject.container.frame + layer.container.parentContainer = thisObject.container + layer.container.isWheelable = true + + /* Add to the Product Array */ + thisObject.layers.push(layer) + + /* Listen to Status Changes Events */ + layer.onLayerStatusChangedEventSuscriptionId = layer.container.eventHandler.listenToEvent('Status Changed', onLayerStatusChanged) + layer.onMouseWheelEventSuscriptionId = layer.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + + layer.checkStatusAtShutDown() + + return + } + } + } + + function onMouseWheel(event) { + delta = event.wheelDelta + if (delta > 0) { + delta = -1 + } else { + delta = 1 + } + + if (event.y - thisObject.container.frame.position.y - CURRENT_TOP_MARGIN < headerHeight) { // Mouse wheel over the header, not a layer + desiredVisibleLayers = desiredVisibleLayers + delta + if (desiredVisibleLayers < 0) { desiredVisibleLayers = 0 } + if (desiredVisibleLayers > thisObject.layers.length) { desiredVisibleLayers = thisObject.layers.length } + } else { + firstVisibleLayer = firstVisibleLayer + delta + } + desiredPanelHeight = (layerHeight + LAYER_SEPARATION) * desiredVisibleLayers + headerHeight + footerHeight + calculateVisbleLayers() + saveObjectStateVisibleLayers() + } + + function panelSizePhysics() { + if (isInitialized === false || thisObject.isHidden === true) { return } + + let viewPortHeight = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y + + if (viewPortHeight < headerHeight) { + thisObject.visible = false + } else { + thisObject.visible = true + } + + if (thisObject.visible === false) { return } + + if (desiredPanelHeight > viewPortHeight) { + posibleVisibleLayers = Math.trunc((viewPortHeight - headerHeight - footerHeight) / (layerHeight + LAYER_SEPARATION)) + } else { + posibleVisibleLayers = desiredVisibleLayers + } + if (thisObject.layers.length < posibleVisibleLayers) { posibleVisibleLayers = thisObject.layers.length } + + if (posibleVisibleLayers === 0) { + posiblePanelHeight = (layerHeight + LAYER_SEPARATION) * posibleVisibleLayers + headerHeight + } else { + posiblePanelHeight = (layerHeight + LAYER_SEPARATION) * posibleVisibleLayers + headerHeight + footerHeight + } + + thisObject.container.frame.height = posiblePanelHeight + } + + function calculateVisbleLayers() { + panelSizePhysics() + let availableSlots = posibleVisibleLayers + + if (firstVisibleLayer < 1) { firstVisibleLayer = 1 } + if (firstVisibleLayer > (thisObject.layers.length - availableSlots + 1)) { firstVisibleLayer = thisObject.layers.length - availableSlots + 1 } + + visibleLayers = [] + + for (let i = 0; i < thisObject.layers.length; i++) { + if (i + 1 >= firstVisibleLayer && i + 1 < firstVisibleLayer + availableSlots) { + let layer = thisObject.layers[i] + + layer.container.frame.position.x = 0 + layer.container.frame.position.y = (layerHeight + LAYER_SEPARATION) * visibleLayers.length + headerHeight + + /* Add to Visible Product Array */ + visibleLayers.push(layer) + } + } + } + + function onLayerStatusChanged(layer) { + thisObject.container.eventHandler.raiseEvent('Layer Status Changed', layer) + } + + function getContainer(point, purpose) { + if (isInitialized === false || thisObject.visible === false || thisObject.isHidden === true) { return } + let container + + if (isMouseOver === true && purpose !== GET_CONTAINER_PURPOSE.MOUSE_OVER) { + container = thisObject.upDownButton.getContainer(point) + if (container !== undefined) { return container } + + container = thisObject.leftRightButton.getContainer(point) + if (container !== undefined) { return container } + } + + /* First we check if thisObject point is inside thisObject space. */ + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + /* Now we see which is the inner most container that has it */ + for (let i = 0; i < visibleLayers.length; i++) { + container = visibleLayers[i].getContainer(point) + + if (container !== undefined) { + let checkPoint = { + x: point.x, + y: point.y + } + + checkPoint = thisObject.fitFunction(checkPoint) + + if (point.x === checkPoint.x && point.y === checkPoint.y) { + if (purpose !== GET_CONTAINER_PURPOSE.MOUSE_OVER) { + return container + } + } + } + } + + /* The point does not belong to any inner container, so we return the current container. */ + let checkPoint = { + x: point.x, + y: point.y + } + + checkPoint = thisObject.fitFunction(checkPoint) + + if (point.x === checkPoint.x && point.y === checkPoint.y) { + return thisObject.container + } + } + } + + function childrenPhysics() { + for (let i = 0; i < thisObject.layers.length; i++) { + let layer = thisObject.layers[i] + layer.physics() + } + } + + function syncWithConfigPhysics() { + readObjectState() + } + + function physics() { + if (isInitialized === false) { return } + if (thisObject.payload.node === undefined) { return } + + thisObject.upDownButton.physics() + thisObject.leftRightButton.physics() + UI.projects.superalgos.utilities.loadSaveFrame.saveFrame(thisObject.payload, thisObject.container.frame) + syncWithConfigPhysics() + + /* + The overall idea here is that we need to keep syncronized the panel with the layers that are + defined at the Designer. Users can connect or disconnect any objext resulting in changes in which + are valid layers and which not at any point in time. So what we do here is trying to keep the panel + only with the layers that are connected to the Charting Space hiriarcy. + + To achieve this, first we are going to move all session related cards to a local array. Then we are + going to check for layers at the designer, and will move back the cards which still have layers well + defined to where they were. + + If we find new layers we will add them at that point. Finally, the cards that still remain at the + local array after all the layers at the designer have been processed, are turned off and discarded. + */ + childrenPhysics() + + let localLayers = [] + moveToLocalLayers() + syncWithDesignerLayers() + + /* At this poins all the cards still at the local array need to be removed from the panel. */ + turnOffUnusedLayers() + calculateVisbleLayers() + + function syncWithDesignerLayers() { + if (thisObject.payload.node === undefined) { return } + let layerManager = thisObject.payload.node + let layers = UI.projects.superalgos.utilities.branches.nodeBranchToArray(layerManager, 'Layer') + for (let p = 0; p < layers.length; p++) { + let layerNode = layers[p] + + let found = removeFromLocalLayers(layerNode.id) + if (found !== true) { + let layer = addLayer(layerNode) + if (layer !== undefined) { + onLayerStatusChanged(layer) + } + } + } + } + + function moveToLocalLayers() { + removeNext() + + function removeNext() { + for (let i = 0; i < thisObject.layers.length; i++) { + let layer = thisObject.layers[i] + + thisObject.layers.splice(i, 1) + localLayers.push(layer) + removeNext() + } + } + } + + function removeFromLocalLayers(id) { + for (let i = 0; i < localLayers.length; i++) { + let layer = localLayers[i] + if (layer.payload === undefined) { continue } + if (layer.payload.node === undefined) { continue } + if (layer.payload.node.id === id) { + thisObject.layers.push(layer) + localLayers.splice(i, 1) + return true + } + } + } + + function turnOffUnusedLayers() { + for (let i = 0; i < localLayers.length; i++) { + let layer = localLayers[i] + removeLayer(layer.nodeId) + } + } + } + + function draw() { + if (isInitialized === false || thisObject.visible === false || thisObject.isHidden === true) { return } + + // thisObject.container.frame.draw(false, false, false, thisObject.fitFunction) + + drawHeader() + for (let i = 0; i < visibleLayers.length; i++) { + visibleLayers[i].draw() + } + drawScrollBar() + if (isMouseOver === true) { + thisObject.upDownButton.draw() + thisObject.leftRightButton.draw() + } + } + + function drawHeader() { + if (thisObject.payload === undefined) { return } + if (thisObject.payload.node === undefined) { return } + if (thisObject.payload.node.payload.parentNode === undefined) { return } + if (thisObject.payload.node.payload.parentNode.payload.parentNode === undefined) { return } + + let label1 = thisObject.payload.node.payload.parentNode.payload.parentNode.name.substring(0, 18) + let label2 = thisObject.payload.node.payload.parentNode.name.substring(0, 18) + let label3 = '' + + let icon1 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.payload.parentNode.payload.parentNode.project, thisObject.payload.node.payload.parentNode.payload.parentNode.type) + let icon2 = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(thisObject.payload.node.payload.parentNode.project, thisObject.payload.node.payload.parentNode.type) + + let backgroundColor = UI_COLOR.BLACK + + let params = { + cornerRadius: 5, + lineWidth: 1, + container: thisObject.container, + borderColor: UI_COLOR.RUSTED_RED, + castShadow: false, + backgroundColor: backgroundColor, + opacity: 1 + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + + UI.projects.superalgos.utilities.drawPrint.drawLabel(label1, 1 / 2, 0, 0, 15, 9, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawLabel(label2, 1 / 2, 0, 0, 30, 9, thisObject.container) + + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon1, 1 / 8, 0, 0, 20, 28, thisObject.container) + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon2, 7 / 8, 0, 0, 20, 28, thisObject.container) + } + + function drawScrollBar() { + if (thisObject.layers.length > posibleVisibleLayers && posibleVisibleLayers > 0) { + let xOffset = 4 + let barTopPoint = { + x: thisObject.container.frame.width - xOffset, + y: headerHeight + } + let barBottomPoint = { + x: thisObject.container.frame.width - xOffset, + y: thisObject.container.frame.height - footerHeight + } + let ratio = posibleVisibleLayers / thisObject.layers.length + let handleHeight = (posibleVisibleLayers * (layerHeight + LAYER_SEPARATION)) * ratio + let handleTopPoint = { + x: thisObject.container.frame.width - xOffset, + y: headerHeight + (layerHeight + LAYER_SEPARATION) * ratio * (firstVisibleLayer - 1) + } + let handleBottomPoint = { + x: thisObject.container.frame.width - xOffset, + y: handleTopPoint.y + handleHeight + } + barTopPoint = thisObject.container.frame.frameThisPoint(barTopPoint) + barBottomPoint = thisObject.container.frame.frameThisPoint(barBottomPoint) + handleTopPoint = thisObject.container.frame.frameThisPoint(handleTopPoint) + handleBottomPoint = thisObject.container.frame.frameThisPoint(handleBottomPoint) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(barTopPoint.x, barTopPoint.y) + browserCanvasContext.lineTo(barBottomPoint.x, barBottomPoint.y) + browserCanvasContext.closePath() + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.lineWidth = 1 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + 1 + ')' + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(handleTopPoint.x, handleTopPoint.y) + browserCanvasContext.lineTo(handleBottomPoint.x, handleBottomPoint.y) + browserCanvasContext.closePath() + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.lineWidth = 4 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', ' + 1 + ')' + browserCanvasContext.stroke() + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/LeftRightButton.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/LeftRightButton.js new file mode 100644 index 000000000..f64b7349e --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/LeftRightButton.js @@ -0,0 +1,243 @@ +function newLeftRightButton() { + let thisObject = { + container: undefined, + fitFunction: undefined, + parentContainer: undefined, + status: undefined, + setStatus: setStatus, + physics: physics, + draw: draw, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + let isInitialized = false + /* Cointainer stuff */ + + thisObject.container = newContainer() + thisObject.container.name = 'Left Right Button' + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.frame.containerName = 'Left Right Button' + + thisObject.status = 'left' + let leftPosition = {} + let rightPosition = {} + let transitionPosition = {} + + let onMouseClickEventSuscriptionId + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseClickEventSuscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.parentContainer = undefined + + thisObject.status = undefined + leftPosition = undefined + rightPosition = undefined + transitionPosition = undefined + } + + function initialize() { + thisObject.container.frame.width = 12 + thisObject.container.frame.height = 12 + + let position = { + x: thisObject.container.frame.parentFrame.width * 2.25 / 8 - thisObject.container.frame.width / 2, + y: 40 * 4 / 8 - thisObject.container.frame.height / 2 + } + + thisObject.container.frame.position = position + + leftPosition.x = thisObject.parentContainer.frame.position.x + leftPosition.y = thisObject.parentContainer.frame.position.y + + rightPosition.x = thisObject.parentContainer.frame.position.x + rightPosition.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y + + transitionPosition.x = leftPosition.x + transitionPosition.y = leftPosition.y + + /* Lets listen to our own events to react when we have a Mouse Click */ + onMouseClickEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + + isInitialized = true + } + + function setStatus(value) { + if (value === 'left' && thisObject.status === 'right') { + thisObject.status = 'going left' + } + if (value === 'right' && thisObject.status === 'left') { + thisObject.status = 'going right' + } + } + + function onMouseClick(event) { + if (thisObject.status === 'left') { + transitionPosition.x = leftPosition.x + transitionPosition.y = leftPosition.y + + thisObject.status = 'going right' + } else { + transitionPosition.x = rightPosition.x + transitionPosition.y = rightPosition.y + + thisObject.status = 'going left' + } + } + + function physics() { + if (isInitialized === false) { return } + + leftPosition.x = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.x + leftPosition.y = thisObject.parentContainer.frame.position.y + + rightPosition.x = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.x + rightPosition.y = thisObject.parentContainer.frame.position.y + + if (thisObject.status === 'going right') { + if (transitionPosition.x < rightPosition.x) { + transitionPosition.x = transitionPosition.x + 50 + thisObject.parentContainer.frame.position.x = transitionPosition.x + } else { + thisObject.parentContainer.frame.position.x = rightPosition.x + thisObject.status = 'right' + } + } + if (thisObject.status === 'going left') { + if (transitionPosition.x > leftPosition.x) { + transitionPosition.x = transitionPosition.x - 50 + thisObject.parentContainer.frame.position.x = transitionPosition.x + } else { + thisObject.parentContainer.frame.position.x = leftPosition.x + thisObject.status = 'left' + } + } + } + + function draw() { + if (isInitialized === false) { return } + + drawButton() + } + + function getContainer(point, purpose) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function drawButton() { + let point1 + let point2 + let point3 + + switch (thisObject.status) { + case 'left': { + point1 = { + x: 3, + y: 3 + } + + point2 = { + x: 3, + y: thisObject.container.frame.height - 3 + } + + point3 = { + x: thisObject.container.frame.width - 3, + y: thisObject.container.frame.height / 2 + } + break + } + + case 'going right': { + point1 = { + x: 4, + y: 4 + } + + point2 = { + x: 4, + y: thisObject.container.frame.height - 4 + } + + point3 = { + x: thisObject.container.frame.width - 4, + y: thisObject.container.frame.height / 2 + } + break + } + + case 'going left': { + point1 = { + x: thisObject.container.frame.width - 4, + y: 4 + } + + point2 = { + x: thisObject.container.frame.width - 4, + y: thisObject.container.frame.height - 4 + } + + point3 = { + x: 4, + y: thisObject.container.frame.height / 2 + } + break + } + + case 'right': { + point1 = { + x: thisObject.container.frame.width - 3, + y: 3 + } + + point2 = { + x: thisObject.container.frame.width - 3, + y: thisObject.container.frame.height - 3 + } + + point3 = { + x: 3, + y: thisObject.container.frame.height / 2 + } + break + } + } + + /* Now the transformations. */ + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + + if (thisObject.fitFunction !== undefined) { + point1 = thisObject.fitFunction(point1) + point2 = thisObject.fitFunction(point2) + point3 = thisObject.fitFunction(point3) + } + + /* Lets start the drawing. */ + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(250, 250, 250, 1)' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + + browserCanvasContext.fillStyle = 'rgba(250, 250, 250, 1)' + browserCanvasContext.fill() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/PanelSpace.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/PanelSpace.js new file mode 100644 index 000000000..23d4acd02 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/PanelSpace.js @@ -0,0 +1,379 @@ +/* + +The Panel Space y the place wehre all panels live, no matter who create them. + +*/ +function newSuperalgosPanelSpace() { + let thisObject = { + visible: true, + container: undefined, + unHide: unHide, + hide: hide, + createNewPanel: createNewPanel, + destroyPanel: destroyPanel, + getPanel: getPanel, + physics: physics, + draw: draw, + panels: [], + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize + } + + let container = newContainer() + container.initialize() + thisObject.container = container + thisObject.container.isDraggeable = false + + container.frame.containerName = 'Panels Space' + + return thisObject + + function initialize() { + + /* The space does not create any Panels for itself, it just sits and waits others need panels. */ + + } + + function createNewPanel(pType, pParameters, pOwner, pSession) { + let panel + + switch (pType) { + + case 'Layers Panel': + { + panel = newLayersPanel() + panel.fitFunction = UI.projects.superalgos.spaces.chartingSpace.fitFunction + panel.container.isVisibleFunction = UI.projects.superalgos.spaces.chartingSpace.isThisPointVisible + panel.type = 'Layers Panel' + break + } + case 'Plotter Panel': + { + if (pParameters.panelNode.config.isLegacy !== true) { + panel = newPlotterPanel() + panel.fitFunction = UI.projects.superalgos.spaces.chartingSpace.fitFunction + panel.container.isVisibleFunction = UI.projects.superalgos.spaces.chartingSpace.isThisPointVisible + panel.session = pSession + panel.initialize(pParameters.panelNode) + } else { + panel = getNewPlotterPanel(pParameters.dataMine, pParameters.plotterCodeName, pParameters.moduleCodeName, pParameters.panelNode.config.codeName) + panel.fitFunction = UI.projects.superalgos.spaces.chartingSpace.fitFunction + panel.container.isVisibleFunction = UI.projects.superalgos.spaces.chartingSpace.isThisPointVisible + panel.session = pSession + panel.initialize() + } + panel.type = 'Plotter Panel' + break + } + } + + panel.owner = pOwner + thisObject.panels.push(panel) + + panel.handle = newUniqueId() + + return panel.handle + } + + function unHide(owner, type) { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + if (panel.owner === owner && panel.type === type) { + panel.isHidden = false + } + } + } + + function hide(owner, type) { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + if (panel.owner === owner && panel.type === type) { + panel.isHidden = true + } + } + } + + function destroyPanel(pPanelHandle) { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + + if (panel.handle === pPanelHandle) { + if (panel.finalize !== undefined) { + panel.finalize() + } + + thisObject.panels.splice(i, 1) // Delete item from array. + return + } + } + } + + function getPanel(pPanelHandle) { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + + if (panel.handle === pPanelHandle) { + return panel + } + } + } + + function physics() { + if (thisObject.visible !== true) { return } + + childrenPhysics() + positioningPhysics() + } + + function childrenPhysics() { + if (thisObject.panels !== undefined) { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + let owner = UI.projects.superalgos.spaces.chartingSpace.inViewport.get(panel.owner) + if (owner !== undefined) { + if (panel.physics !== undefined) { + panel.physics() + } + } + } + } + } + + function positioningPhysics() { + if (thisObject.panels !== undefined) { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + if (panel.isVisible === false || panel.isHidden === true) { continue } + /* setting the speed of the panel */ + if (panel.container.speed === undefined) { + panel.container.speed = { + x: 2, + y: 2 + } + } + + /* Trying to move the panel and see if it is possible */ + let centerPoint = { + x: panel.container.frame.position.x, + y: panel.container.frame.position.y + } + centerPoint.x = centerPoint.x + panel.container.frame.width / 2 + centerPoint.y = centerPoint.y + panel.container.frame.height / 2 + + /* Lets see which quadrant the panel is at */ + let verticalLine = (UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.x - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.x) / 2 + UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.x + let horizontalLine = (UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.y) / 2 + UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.y + + if (panel.upDownButton !== undefined) { + if (panel.upDownButton.status === 'up') { + if (panel.leftRightButton !== undefined) { + if (panel.leftRightButton.status === 'left') { + panel.gravitatesTowards = 'topLeft' + } else { + panel.gravitatesTowards = 'topRight' + } + } else { + if (centerPoint.x < verticalLine) { + panel.gravitatesTowards = 'topLeft' + } else { + panel.gravitatesTowards = 'topRight' + } + } + } else { + if (panel.leftRightButton !== undefined) { + if (panel.leftRightButton.status === 'left') { + panel.gravitatesTowards = 'bottomLeft' + } else { + panel.gravitatesTowards = 'bottomRight' + } + } else { + if (centerPoint.x < verticalLine) { + panel.gravitatesTowards = 'bottomLeft' + } else { + panel.gravitatesTowards = 'bottomRight' + } + } + } + } else { + if (centerPoint.x < verticalLine && centerPoint.y < horizontalLine) { + panel.gravitatesTowards = 'topLeft' + } + if (centerPoint.x >= verticalLine && centerPoint.y < horizontalLine) { + panel.gravitatesTowards = 'topRight' + } + if (centerPoint.x < verticalLine && centerPoint.y >= horizontalLine) { + panel.gravitatesTowards = 'bottomLeft' + } + if (centerPoint.x >= verticalLine && centerPoint.y >= horizontalLine) { + panel.gravitatesTowards = 'bottomRight' + } + } + + /* According to the quadrant we push the panels to the sides */ + if (panel.gravitatesTowards === 'topLeft' || panel.gravitatesTowards === 'bottomLeft') { + panel.container.frame.position.x = panel.container.frame.position.x - panel.container.speed.x + if (panel.leftRightButton !== undefined) { + if (panel.leftRightButton.status === 'left' || panel.leftRightButton.status === 'right') { // we avoid pushing other panels while in transition + } + } + isOverlapping(i, panel.container) + if (panel.container.frame.position.x < 0) { + panel.container.frame.position.x = 0 + } + } else { + panel.container.frame.position.x = panel.container.frame.position.x + panel.container.speed.x + isOverlapping(i, panel.container) + if (panel.container.frame.position.x + panel.container.frame.width > browserCanvas.width) { + panel.container.frame.position.x = browserCanvas.width - panel.container.frame.width + } + } + if (panel.container.frame.height <= UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y - UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.y) { + if (panel.gravitatesTowards === 'topLeft' || panel.gravitatesTowards === 'topRight') { + panel.container.frame.position.y = panel.container.frame.position.y - panel.container.speed.y + isOverlapping(i, panel.container) + if (panel.container.frame.position.y < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y) { + panel.container.frame.position.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y + } + } else { + panel.container.frame.position.y = panel.container.frame.position.y + panel.container.speed.y + isOverlapping(i, panel.container) + if (panel.container.frame.position.y + panel.container.frame.height > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y) { + panel.container.frame.position.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y - panel.container.frame.height + } + } + } + } + } + + function isOverlapping(currentIndex, currentContainer) { + let corner1 = { + x: currentContainer.frame.position.x, + y: currentContainer.frame.position.y + } + let corner2 = { + x: currentContainer.frame.position.x + currentContainer.frame.width, + y: currentContainer.frame.position.y + } + let corner3 = { + x: currentContainer.frame.position.x + currentContainer.frame.width, + y: currentContainer.frame.position.y + currentContainer.frame.height + } + let corner4 = { + x: currentContainer.frame.position.x, + y: currentContainer.frame.position.y + currentContainer.frame.height + } + + for (let i = 0; i < currentIndex; i++) { + let panel = thisObject.panels[i] + if (panel.leftRightButton !== undefined) { + if (panel.leftRightButton.status !== 'left' && panel.leftRightButton.status !== 'right') { // we avoid pushing other panels while in transition + continue + } + } + if (panel.isVisible === true && panel.isHidden === false) { + if (isThisPointInsideThisFrame(corner1, panel.container.frame) === true) { + pushOut(currentContainer, panel.container, panel.gravitatesTowards) + } + if (isThisPointInsideThisFrame(corner2, panel.container.frame) === true) { + pushOut(currentContainer, panel.container, panel.gravitatesTowards) + } + if (isThisPointInsideThisFrame(corner3, panel.container.frame) === true) { + pushOut(currentContainer, panel.container, panel.gravitatesTowards) + } + if (isThisPointInsideThisFrame(corner4, panel.container.frame) === true) { + pushOut(currentContainer, panel.container, panel.gravitatesTowards) + } + } + } + + function pushOut(currentContainer, controlContainer, corner) { + switch (corner) { + case 'topLeft': { + currentContainer.frame.position.x = controlContainer.frame.position.x + controlContainer.frame.width + break + } + case 'topRight': { + currentContainer.frame.position.x = controlContainer.frame.position.x - currentContainer.frame.width + break + } + case 'bottomLeft': { + currentContainer.frame.position.x = controlContainer.frame.position.x + controlContainer.frame.width + break + } + case 'bottomRight': { + currentContainer.frame.position.x = controlContainer.frame.position.x - currentContainer.frame.width + break + } + } + } + + function isThisPointInsideThisFrame(point, frame) { + let corner1 = { + x: frame.position.x, + y: frame.position.y + } + let corner2 = { + x: frame.position.x + frame.width, + y: frame.position.y + } + let corner3 = { + x: frame.position.x + frame.width, + y: frame.position.y + frame.height + } + let corner4 = { + x: frame.position.x, + y: frame.position.y + frame.height + } + if (point.x >= corner1.x && point.y >= corner1.y && point.x <= corner3.x && point.y <= corner3.y) { + return true + } else { + return false + } + } + return false + } + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + if (thisObject.visible !== true) { return } + + thisObject.container.frame.draw(false, false) + + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = thisObject.panels[i] + let owner = UI.projects.superalgos.spaces.chartingSpace.inViewport.get(panel.owner) + if (owner !== undefined) { + if (panel.isVisible === true && panel.isHidden === false) { + panel.draw() + } + } + } + } + + function getContainer(point, purpose) { + if (thisObject.visible !== true) { return } + if (purpose === GET_CONTAINER_PURPOSE.DRAGGING && point.shiftKey !== false) {return} + + let container + /* + We search for the container of panels in the oposite direction than we do it for drawing them, + so panels overlapping others are picked firt although they are drawn last. + */ + for (let i = thisObject.panels.length - 1; i >= 0; i--) { + let panel = thisObject.panels[i] + let owner = UI.projects.superalgos.spaces.chartingSpace.inViewport.get(panel.owner) + if (owner !== undefined) { + if (panel.isVisible === true && panel.isHidden === false) { + container = panel.getContainer(point, purpose) + } + } + if (container !== undefined) { + container.space = 'Panels Space' + return container + } + } + // thisObject.container.space = 'Panels Space' + // return thisObject.container + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/PanelsVisibleButton.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/PanelsVisibleButton.js new file mode 100644 index 000000000..2b4b093dc --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/PanelsVisibleButton.js @@ -0,0 +1,84 @@ +function newPanelsVisibleButton() { + let thisObject = { + container: undefined, + fitFunction: undefined, + parentContainer: undefined, + panels: undefined, + showPanels: true, + draw: draw, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + /* Cointainer stuff */ + thisObject.container = newContainer() + thisObject.container.name = 'Panels Visible Button' + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.frame.containerName = 'Panels Visible Button' + + let onMouseClickEventSuscriptionId + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseClickEventSuscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.parentContainer = undefined + thisObject.panels = undefined + } + + function initialize(panels) { + thisObject.panels = panels + setPanelVisibility() + + thisObject.container.frame.width = 25 + thisObject.container.frame.height = 25 + + let position = { + x: thisObject.container.frame.width * 1 / 8, + y: 45 + } + + thisObject.container.frame.position = position + + /* Lets listen to our own events to react when we have a Mouse Click */ + onMouseClickEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + } + + function onMouseClick(event) { + if (thisObject.showPanels === false) { + thisObject.showPanels = true + } else { + thisObject.showPanels = false + } + setPanelVisibility() + } + + function setPanelVisibility() { + for (let i = 0; i < thisObject.panels.length; i++) { + let panel = UI.projects.superalgos.spaces.panelSpace.getPanel(thisObject.panels[i]) + panel.isVisible = thisObject.showPanels + } + } + + function getContainer(point) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function draw() { + let icon + if (thisObject.showPanels === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'plotter-panel') + } else { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'toggle-panel-off') + } + + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon, 1 / 2, 1 / 2, 0, 0, 14, thisObject.container) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/PlotterPanel.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/PlotterPanel.js new file mode 100644 index 000000000..87b5829d6 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/PlotterPanel.js @@ -0,0 +1,183 @@ + +function newPlotterPanel() { + const MODULE_NAME = 'Plotter Panel' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + fitFunction: undefined, + container: undefined, + isVisible: true, + onRecordChange: onRecordChange, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize('Plotter Panel') + + let heightFactor = 1 + let currentRecord + let upDownButton + let panelNode + + return thisObject + + function finalize() { + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.isVisible = undefined + + heightFactor = undefined + currentRecord = undefined + upDownButton.finalize() + upDownButton = undefined + panelNode = undefined + } + + function initialize(pPanelNode) { + panelNode = pPanelNode + thisObject.container.frame.containerName = panelNode.name + + thisObject.container.frame.width = UI_PANEL.WIDTH.NORMAL + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL + + thisObject.container.frame.position.x = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.x - thisObject.container.frame.width - thisObject.container.frame.width * Math.random() * 8 + thisObject.container.frame.position.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y - thisObject.container.frame.height - thisObject.container.frame.height * Math.random() * 1.5 + + upDownButton = newUpDownButton() + upDownButton.parentContainer = thisObject.container + upDownButton.container.frame.parentFrame = thisObject.container.frame + upDownButton.fitFunction = thisObject.fitFunction + upDownButton.initialize() + } + + function getContainer(point) { + if (thisObject.isVisible !== true) { return } + let container + + container = upDownButton.getContainer(point) + if (container !== undefined) { return container } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + let checkPoint = { + x: point.x, + y: point.y + } + + checkPoint = thisObject.fitFunction(checkPoint) + + if (point.x === checkPoint.x && point.y === checkPoint.y) { + return thisObject.container + } + } + } + + function onRecordChange(pCurrentRecord) { + currentRecord = pCurrentRecord + } + + function draw() { + if (thisObject.isVisible !== true) { return } + + thisObject.container.frame.draw(false, false, true, thisObject.fitFunction) + plotCurrentRecordData() + upDownButton.draw() + } + + function plotCurrentRecordData() { + const frameBodyHeight = thisObject.container.frame.getBodyHeight() + const frameTitleHeight = thisObject.container.frame.height - frameBodyHeight + + const X_AXIS = thisObject.container.frame.width / 2 + const Y_AXIS = frameTitleHeight + frameBodyHeight / 2 + + const PANEL_HEIGHT = UI_PANEL.HEIGHT.NORMAL + + if (currentRecord === undefined) { return } + let record = currentRecord + + /* First we execute code if provided. */ + if (panelNode.javascriptCode !== undefined) { + try { + eval(panelNode.javascriptCode.code) + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] plotCurrentRecordData -> err = ' + err.stack) } + if (ERROR_LOG === true) { logger.write('[ERROR] plotCurrentRecordData -> javascriptCode.code = ' + panelNode.javascriptCode.code) } + } + } + + /* Second we go through the panel data. */ + if (panelNode.panelData === undefined) { return } + for (let i = 0; i < panelNode.panelData.length; i++) { + let panelData = panelNode.panelData[i] + + let labelText = panelData.name + let labelPosition = i * 10 + 12 + let valuePosition = i * 10 + 17 + let value = 'No value defined.' + + if (valuePosition > 100) { + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL * 1.25 + } + + if (valuePosition > 125) { + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL * 1.50 + } + + if (valuePosition > 150) { + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL * 1.75 + } + + if (valuePosition > 175) { + thisObject.container.frame.height = UI_PANEL.HEIGHT.NORMAL * 2.00 + } + + if (panelData.dataFormula !== undefined) { + try { + value = eval(panelData.dataFormula.code) + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] plotCurrentRecordData -> err = ' + err.stack) } + if (ERROR_LOG === true) { logger.write('[ERROR] plotCurrentRecordData -> dataFormula.code = ' + panelData.dataFormula.code) } + } + } + + if (panelData.config.valueDecimals !== undefined) { + if (value !== undefined) { + if (isNaN(value) === false) { + value = dynamicDecimals(value, panelData.config.valueDecimals) + } + } + } + + if (panelData.config.labelText !== undefined) { + labelText = panelData.config.labelText + } + + if (panelData.config.labelPosition !== undefined) { + labelPosition = panelData.config.labelPosition + } + + if (panelData.config.valuePosition !== undefined) { + valuePosition = panelData.config.valuePosition + } + + let opacity = '1.00' + let fontSize = 14 + let paletteColor + + if (panelData.textStyle !== undefined) { + if (panelData.textStyle.config.fontSize !== undefined) { fontSize = panelData.textStyle.config.fontSize } + if (panelData.textStyle.config.opacity !== undefined) { opacity = panelData.textStyle.config.opacity } + if (panelData.textStyle.config.paletteColor !== undefined) { paletteColor = eval(panelData.textStyle.config.paletteColor) } + } + + UI.projects.superalgos.utilities.drawPrint.printLabel(labelText, X_AXIS, UI_PANEL.HEIGHT.NORMAL * labelPosition / 100 / heightFactor, '0.60', undefined, undefined, true, thisObject.container, thisObject.fitFunction) + UI.projects.superalgos.utilities.drawPrint.printLabel(value, X_AXIS, UI_PANEL.HEIGHT.NORMAL * valuePosition / 100 / heightFactor, opacity, fontSize, paletteColor, true, thisObject.container, thisObject.fitFunction, false, true) + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Panel-Space/UpDownButton.js b/Integrations/Projects/Superalgos/Spaces/Panel-Space/UpDownButton.js new file mode 100644 index 000000000..d4104e3a5 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Panel-Space/UpDownButton.js @@ -0,0 +1,244 @@ +function newUpDownButton() { + let thisObject = { + container: undefined, + fitFunction: undefined, + parentContainer: undefined, + status: undefined, + setStatus: setStatus, + physics: physics, + draw: draw, + getContainer: getContainer, // returns the inner most container that holds the point received by parameter. + initialize: initialize, + finalize: finalize + } + + let isInitialized = false + /* Cointainer stuff */ + + thisObject.container = newContainer() + thisObject.container.name = 'Up Down Button' + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.frame.containerName = 'Up Down Button' + + thisObject.status = 'up' + let upPosition = {} + let downPosition = {} + let transitionPosition = {} + + let onMouseClickEventSuscriptionId + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseClickEventSuscriptionId) + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + thisObject.parentContainer = undefined + + thisObject.status = undefined + upPosition = undefined + downPosition = undefined + transitionPosition = undefined + } + + function initialize() { + thisObject.container.frame.width = 12 + thisObject.container.frame.height = 12 + + let position = { + x: thisObject.container.frame.parentFrame.width * 5.75 / 8 - thisObject.container.frame.width / 2, + y: 40 * 4 / 8 - thisObject.container.frame.height / 2 + } + + thisObject.container.frame.position = position + + upPosition.x = thisObject.parentContainer.frame.position.x + upPosition.y = thisObject.parentContainer.frame.position.y + + downPosition.x = thisObject.parentContainer.frame.position.x + downPosition.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y + + transitionPosition.x = upPosition.x + transitionPosition.y = upPosition.y + + /* Lets listen to our own events to react when we have a Mouse Click */ + onMouseClickEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + + isInitialized = true + } + + function setStatus(value) { + if (value === 'up' && thisObject.status === 'down') { + thisObject.status = 'going up' + } + if (value === 'down' && thisObject.status === 'up') { + thisObject.status = 'going down' + } + } + + function onMouseClick(event) { + if (thisObject.status === 'up') { + transitionPosition.x = upPosition.x + transitionPosition.y = upPosition.y + + thisObject.status = 'going down' + } else { + transitionPosition.x = downPosition.x + transitionPosition.y = downPosition.y + + thisObject.status = 'going up' + } + } + + function physics() { + if (isInitialized === false) { return } + + upPosition.x = thisObject.parentContainer.frame.position.x + upPosition.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.y + + downPosition.x = thisObject.parentContainer.frame.position.x + downPosition.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y + + if (thisObject.status === 'going down') { + if (transitionPosition.y < downPosition.y) { + transitionPosition.y = transitionPosition.y + 50 + thisObject.parentContainer.frame.position.y = transitionPosition.y + } else { + thisObject.parentContainer.frame.position.y = downPosition.y + thisObject.status = 'down' + } + } + if (thisObject.status === 'going up') { + if (transitionPosition.y > upPosition.y) { + transitionPosition.y = transitionPosition.y - 50 + thisObject.parentContainer.frame.position.y = transitionPosition.y + } else { + thisObject.parentContainer.frame.position.y = upPosition.y + thisObject.status = 'up' + } + } + } + + function draw() { + if (isInitialized === false) { return } + + drawButton() + } + + function getContainer(point, purpose) { + let container + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function drawButton() { + let point1 + let point2 + let point3 + + switch (thisObject.status) { + case 'up': { + point1 = { + x: 3, + y: 3 + } + + point2 = { + x: thisObject.container.frame.width - 3, + y: 3 + } + + point3 = { + x: thisObject.container.frame.width / 2, + y: thisObject.container.frame.height - 3 + } + break + } + + case 'going down': { + point1 = { + x: 4, + y: 4 + } + + point2 = { + x: thisObject.container.frame.width - 4, + y: 4 + } + + point3 = { + x: thisObject.container.frame.width / 2, + y: thisObject.container.frame.height - 4 + } + break + } + + case 'going up': { + point1 = { + x: 4, + y: thisObject.container.frame.height - 4 + } + + point2 = { + x: thisObject.container.frame.width - 4, + y: thisObject.container.frame.height - 4 + } + + point3 = { + x: thisObject.container.frame.width / 2, + y: 4 + } + break + } + + case 'down': { + point1 = { + x: 3, + y: thisObject.container.frame.height - 3 + } + + point2 = { + x: thisObject.container.frame.width - 3, + y: thisObject.container.frame.height - 3 + } + + point3 = { + x: thisObject.container.frame.width / 2, + y: 3 + } + break + } + } + + /* Now the transformations. */ + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + + if (thisObject.fitFunction !== undefined) { + point1 = thisObject.fitFunction(point1) + point2 = thisObject.fitFunction(point2) + point3 = thisObject.fitFunction(point3) + } + + /* Lets start the drawing. */ + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(250, 250, 250, 1)' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + + browserCanvasContext.fillStyle = 'rgba(250, 250, 250, 1)' + browserCanvasContext.fill() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Side-Space/ListItem.js b/Integrations/Projects/Superalgos/Spaces/Side-Space/ListItem.js new file mode 100644 index 000000000..9756fd788 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Side-Space/ListItem.js @@ -0,0 +1,109 @@ +function newListItem() { + const MODULE_NAME = 'List Item' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + container: undefined, + fitFunction: undefined, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize() + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = false + thisObject.container.detectMouseOver = true + thisObject.container.isClickeable = true + thisObject.container.frame.width = SIDE_PANEL_WIDTH * 0.75 + thisObject.container.frame.height = SIDE_PANEL_WIDTH * 0.75 + + let name + let project + let type + + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + let onMouseClickEventSubscriptionId + + let isMouseOver = false + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + thisObject.container.eventHandler.stopListening(onMouseClickEventSubscriptionId) + + thisObject.container.finalize() + thisObject.container = undefined + thisObject.fitFunction = undefined + } + + function initialize(pName, pProject, pType) { + name = pName + project = pProject + type = pType + + onMouseOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseOver', onMouseOver) + onMouseNotOverEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + onMouseClickEventSubscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + } + + function getContainer(point) { + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } + } + + function onMouseOver(event) { + isMouseOver = true + } + + function onMouseNotOver() { + isMouseOver = false + } + + function onMouseClick(event) { + UI.projects.superalgos.spaces.designSpace.workspace.replaceWorkspaceByLoadingOne(name) + } + + function physics() { + + } + + function draw() { + let icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndType(project, type) + let backgroundColor = UI_COLOR.BLACK + let labelColor + + const RED_LINE_HIGHT = 4 + const OPACITY = 1 + + let params = { + cornerRadius: 0, + lineWidth: 1, + container: thisObject.container, + borderColor: UI_COLOR.RUSTED_RED, + castShadow: false, + backgroundColor: backgroundColor, + opacity: OPACITY + } + + if (isMouseOver === false) { + labelColor = UI_COLOR.WHITE + } else { + labelColor = UI_COLOR.TITANIUM_YELLOW + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + + UI.projects.superalgos.utilities.drawPrint.drawLabel(name, 1 / 2, 3.0 / 10, -5, 0, 15, thisObject.container, labelColor) + UI.projects.superalgos.utilities.drawPrint.drawLabel(type, 1 / 2, 7.2 / 10, -5, 0, 15, thisObject.container, labelColor) + UI.projects.superalgos.utilities.drawPrint.drawIcon(icon, 1 / 2, 1 / 2, 0, 0, 80, thisObject.container) + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Side-Space/ListView.js b/Integrations/Projects/Superalgos/Spaces/Side-Space/ListView.js new file mode 100644 index 000000000..c8f9958b0 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Side-Space/ListView.js @@ -0,0 +1,269 @@ +function newListView() { + const MODULE_NAME = 'List View' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + fitFunction: undefined, + container: undefined, + listItems: [], + payload: undefined, + isVisible: false, + resize: resize, + turnOn: turnOn, + turnOff: turnOff, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + thisObject.container = newContainer() + thisObject.container.initialize() + thisObject.container.isDraggeable = false + thisObject.container.isWheelable = true + thisObject.container.detectMouseOver = true + + let isInitialized = false + + let listItemsMap = new Map() + let visibleListItems = [] + let firstVisibleListItem + + let headerHeight = 40 + let footerHeight = 30 + let listItemHeight = SIDE_PANEL_WIDTH * 0.75 + + let posibleVisibleListItems + + let itemSeparation + + let onMouseWheelEventSuscriptionId + + return thisObject + + function finalize() { + thisObject.container.eventHandler.stopListening(onMouseWheelEventSuscriptionId) + + listItemsMap = undefined + visibleListItems = undefined + + thisObject.container.finalize() + thisObject.container = undefined + + thisObject.payload = undefined + thisObject = undefined + } + + function initialize() { + thisObject.container.name = MODULE_NAME + thisObject.container.frame.containerName = thisObject.container.name + thisObject.container.frame.width = SIDE_PANEL_WIDTH * 0.75 + + let position = { // Default position + x: SIDE_PANEL_WIDTH * 0.25 / 2, + y: 20 + } + + thisObject.container.frame.position = position + + onMouseWheelEventSuscriptionId = thisObject.container.eventHandler.listenToEvent('onMouseWheel', onMouseWheel) + + resize() + turnOn() + } + + function resize() { + thisObject.container.frame.height = browserCanvas.height - thisObject.container.frame.position.y * 2 + + let spaceForListItems = (thisObject.container.frame.height - headerHeight - footerHeight) + posibleVisibleListItems = Math.trunc(spaceForListItems / (listItemHeight)) + if ((posibleVisibleListItems - 1) !== 0) { + itemSeparation = (spaceForListItems - listItemHeight * posibleVisibleListItems) / (posibleVisibleListItems - 1) + } else { + itemSeparation = 0 + } + } + + function turnOn() { + firstVisibleListItem = 1 + + callWebServer(undefined, 'ListWorkspaces', onResponse) + + function onResponse(err, text) { + if (err && err.result !== GLOBAL.DEFAULT_OK_RESPONSE.result) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> Could not load the list of your workspaces from the backend. ') } + return + } + + let workspacelist = JSON.parse(text) + thisObject.listItems = [] + for (let i = 0; i < workspacelist.length; i++) { + let workspace = workspacelist[i].replace('.json', '') + let listItem = newListItem() + listItem.initialize(workspace, 'Superalgos', 'Workspace') + listItem.container.connectToParent(thisObject.container, false, false) + + thisObject.listItems.push(listItem) + } + calculateVisbleListItems() + isInitialized = true + thisObject.isVisible = true + } + } + + function turnOff() { + thisObject.isVisible = false + } + + function onMouseWheel(event) { + delta = event.wheelDelta + if (delta > 0) { + delta = -1 + } else { + delta = 1 + } + + firstVisibleListItem = firstVisibleListItem + delta + + calculateVisbleListItems() + } + + function calculateVisbleListItems() { + let availableSlots = Math.trunc(posibleVisibleListItems) + + if (firstVisibleListItem < 1) { firstVisibleListItem = 1 } + if (firstVisibleListItem > (thisObject.listItems.length - availableSlots + 1)) { firstVisibleListItem = thisObject.listItems.length - availableSlots + 1 } + + visibleListItems = [] + + for (let i = 0; i < thisObject.listItems.length; i++) { + if (i + 1 >= firstVisibleListItem && i + 1 < firstVisibleListItem + availableSlots) { + let listItem = thisObject.listItems[i] + + listItem.container.frame.position.x = 0 + listItem.container.frame.position.y = (listItemHeight + itemSeparation) * visibleListItems.length + headerHeight + + /* Add to Visible Product Array */ + visibleListItems.push(listItem) + } + } + } + + function getContainer(point, purpose) { + if (isInitialized === false || thisObject.isVisible === false) { return } + let container + + /* First we check if thisObject point is inside thisObject space. */ + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + /* Now we see which is the inner most container that has it */ + for (let i = 0; i < visibleListItems.length; i++) { + container = visibleListItems[i].getContainer(point) + + if (container !== undefined) { + if (container.isForThisPurpose(purpose) === true) { + return container + } + } + } + + return thisObject.container + } + } + + function childrenPhysics() { + for (let i = 0; i < thisObject.listItems.length; i++) { + let listItem = thisObject.listItems[i] + listItem.physics() + } + } + + function physics() { + if (isInitialized === false || thisObject.isVisible === false) { return } + childrenPhysics() + } + + function draw() { + if (isInitialized === false || thisObject.isVisible === false) { return } + + drawBackground() + drawChildren() + drawScrollBar() + } + + function drawChildren() { + for (let i = 0; i < visibleListItems.length; i++) { + let listItem = visibleListItems[i] + if (listItem !== undefined) { + listItem.draw() + } + } + } + + function drawBackground() { + let label = 'Available Workspaces' + + let backgroundColor = UI_COLOR.WHITE + let params = { + cornerRadius: 5, + lineWidth: 1, + container: thisObject.container, + borderColor: UI_COLOR.RUSTED_RED, + castShadow: false, + backgroundColor: backgroundColor, + opacity: 1 + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + + UI.projects.superalgos.utilities.drawPrint.drawLabel(label, 1 / 2, 0, 0, 25, 15, thisObject.container, UI_COLOR.BLACK) + } + + function drawScrollBar() { + if (thisObject.listItems.length > posibleVisibleListItems && posibleVisibleListItems > 0) { + let xOffset = 4 + let barTopPoint = { + x: thisObject.container.frame.width - xOffset, + y: headerHeight + } + let barBottomPoint = { + x: thisObject.container.frame.width - xOffset, + y: thisObject.container.frame.height - footerHeight + } + let ratio = (posibleVisibleListItems * listItemHeight + (posibleVisibleListItems - 1) * itemSeparation) / (thisObject.listItems.length * listItemHeight + (thisObject.listItems.length - 1) * itemSeparation) + let handleHeight = posibleVisibleListItems * (listItemHeight * ratio) + (posibleVisibleListItems - 1) * itemSeparation * ratio + let handleTopPoint = { + x: thisObject.container.frame.width - xOffset, + y: headerHeight + (listItemHeight * ratio * (firstVisibleListItem - 1) + itemSeparation * ratio * (firstVisibleListItem - 1)) + } + let handleBottomPoint = { + x: thisObject.container.frame.width - xOffset, + y: handleTopPoint.y + handleHeight + } + barTopPoint = thisObject.container.frame.frameThisPoint(barTopPoint) + barBottomPoint = thisObject.container.frame.frameThisPoint(barBottomPoint) + handleTopPoint = thisObject.container.frame.frameThisPoint(handleTopPoint) + handleBottomPoint = thisObject.container.frame.frameThisPoint(handleBottomPoint) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(barTopPoint.x, barTopPoint.y) + browserCanvasContext.lineTo(barBottomPoint.x, barBottomPoint.y) + browserCanvasContext.closePath() + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.lineWidth = 1 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + 1 + ')' + browserCanvasContext.stroke() + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(handleTopPoint.x, handleTopPoint.y) + browserCanvasContext.lineTo(handleBottomPoint.x, handleBottomPoint.y) + browserCanvasContext.closePath() + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.lineWidth = 4 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', ' + 1 + ')' + browserCanvasContext.stroke() + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Side-Space/SidePanelTab.js b/Integrations/Projects/Superalgos/Spaces/Side-Space/SidePanelTab.js new file mode 100644 index 000000000..3b86f7c2d --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Side-Space/SidePanelTab.js @@ -0,0 +1,336 @@ +function newSidePanelTab() { + const MODULE_NAME = 'Side Panel Tab' + let thisObject = { + screenside: undefined, + pointerDirection: undefined, + container: undefined, + index: 0, + open: open, + close: close, + resize: resize, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize + } + + const TAB_WIDTH = 25 + const TAB_HEIGHT = TOP_SPACE_HEIGHT * 2 + const ANIMATION_STEP = 100 + + let isInitialized = false + let animation = 'none' + let ORIGINAL_PARENT_POSITON + let xOffset = 0 + + thisObject.container = newContainer() + thisObject.container.name = MODULE_NAME + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + + return thisObject + + function initialize(screenside, index) { + thisObject.screenside = screenside + if (index !== undefined) { + thisObject.index = index + } + + switch (thisObject.screenside) { + case 'left': { + thisObject.pointerDirection = 'left' + break + } + case 'right': { + thisObject.pointerDirection = 'right' + break + } + } + thisObject.container.frame.width = TAB_WIDTH + thisObject.container.frame.height = TAB_HEIGHT + + thisObject.container.eventHandler.listenToEvent('onMouseClick', onMouseClick) + resize() + isInitialized = true + } + + function resize() { + switch (thisObject.screenside) { + case 'left': { + thisObject.container.frame.position.x = 0 + thisObject.container.parentContainer.frame.width + break + } + case 'right': { + thisObject.container.frame.position.x = 0 - TAB_WIDTH + break + } + } + + ORIGINAL_PARENT_POSITON = thisObject.container.parentContainer.frame.position.x + setClosed() + } + + function open() { + animation = 'opening' + thisObject.container.eventHandler.raiseEvent('opening') + } + + function close() { + animation = 'closing' + thisObject.container.eventHandler.raiseEvent('closing') + } + + function onMouseClick(event) { + switch (thisObject.screenside) { + case 'left': { + if (thisObject.pointerDirection === 'left') { + animation = 'opening' + thisObject.container.eventHandler.raiseEvent('opening') + } else { + animation = 'closing' + thisObject.container.eventHandler.raiseEvent('closing') + } + break + } + case 'right': { + if (thisObject.pointerDirection === 'left') { + animation = 'closing' + thisObject.container.eventHandler.raiseEvent('closing') + } else { + animation = 'opening' + thisObject.container.eventHandler.raiseEvent('opening') + } + break + } + } + } + + function getContainer(point) { + let container + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function physics() { + if (isInitialized === false) { return } + positionPhysics() + animate() + } + + function positionPhysics() { + if (UI.projects.superalgos.spaces.chartingSpace.viewport !== undefined) { + thisObject.container.frame.position.y = browserCanvas.height / 2 - TAB_HEIGHT / 2 // TOP_SPACE_HEIGHT + UI.projects.superalgos.spaces.chartingSpace.viewport.marginAmount + TAB_HEIGHT * thisObject.index + } + } + + function draw() { + if (isInitialized === false) { return } + borders() + arrow() + } + + function animate() { + + if (animation === 'opening') { + xOffset = xOffset + ANIMATION_STEP + switch (thisObject.screenside) { + case 'left': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON + xOffset + break + } + case 'right': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON - xOffset + break + } + } + + if (xOffset >= thisObject.container.parentContainer.frame.width) { + setOpened() + thisObject.container.eventHandler.raiseEvent('opened') + } + } + + if (animation === 'closing') { + xOffset = xOffset - ANIMATION_STEP + switch (thisObject.screenside) { + case 'left': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON + xOffset + break + } + case 'right': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON - xOffset + break + } + } + + if (xOffset <= 0) { + setClosed() + thisObject.container.eventHandler.raiseEvent('closed') + } + } + } + + function setOpened() { + animation = 'none' + xOffset = thisObject.container.parentContainer.frame.width + thisObject.container.parentContainer.status = 'visible' + switch (thisObject.screenside) { + case 'left': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON + xOffset + thisObject.pointerDirection = 'right' + break + } + case 'right': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON - xOffset + thisObject.pointerDirection = 'left' + break + } + } + } + function setClosed() { + animation = 'none' + xOffset = 0 + thisObject.container.parentContainer.status = 'hidden' + switch (thisObject.screenside) { + case 'left': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON + thisObject.pointerDirection = 'left' + break + } + case 'right': { + thisObject.container.parentContainer.frame.position.x = ORIGINAL_PARENT_POSITON + thisObject.pointerDirection = 'right' + break + } + } + } + + function arrow() { + let point1 + let point2 + let point3 + + if (thisObject.pointerDirection === 'left') { + point1 = { + x: TAB_WIDTH * 3.5 / 10, + y: TAB_HEIGHT * 12.5 / 30 + } + + point2 = { + x: TAB_WIDTH * 6.5 / 10, + y: TAB_HEIGHT * 15 / 30 + } + + point3 = { + x: TAB_WIDTH * 3.5 / 10, + y: TAB_HEIGHT * 17.5 / 30 + } + } else { + point1 = { + x: TAB_WIDTH * 6.5 / 10, + y: TAB_HEIGHT * 12.5 / 30 + } + + point2 = { + x: TAB_WIDTH * 3.5 / 10, + y: TAB_HEIGHT * 15 / 30 + } + + point3 = { + x: TAB_WIDTH * 6.5 / 10, + y: TAB_HEIGHT * 17.5 / 30 + } + } + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.closePath() + + let opacity = 1 + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.GREY + ', ' + opacity + '' + browserCanvasContext.fill() + } + + function borders() { + let point1 + let point2 + let point3 + let point4 + + point1 = { + x: 0, + y: 0 + } + + point2 = { + x: TAB_WIDTH, + y: 0 + } + + point3 = { + x: TAB_WIDTH, + y: TAB_HEIGHT + } + + point4 = { + x: 0, + y: TAB_HEIGHT + } + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + point4 = thisObject.container.frame.frameThisPoint(point4) + + let opacity + + /* Shadow */ + + for (let i = 0; i <= 5; i++) { + opacity = 1 - (i / 100) - 0.93 + + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x + i, point1.y + i) + browserCanvasContext.lineTo(point2.x + i, point2.y + i) + browserCanvasContext.lineTo(point3.x + i, point3.y + i) + browserCanvasContext.lineTo(point4.x + i, point4.y + i) + browserCanvasContext.lineTo(point1.x + i, point1.y + i) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + opacity + '' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + } + + /* Background and Border */ + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.lineTo(point4.x, point4.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + opacity = 1 + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', ' + opacity + '' + browserCanvasContext.fill() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + opacity + '' + browserCanvasContext.lineWidth = 0.3 + browserCanvasContext.stroke() + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Side-Space/SideSpace.js b/Integrations/Projects/Superalgos/Spaces/Side-Space/SideSpace.js new file mode 100644 index 000000000..deba8d0f4 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Side-Space/SideSpace.js @@ -0,0 +1,168 @@ +function newSuperalgosSideSpace() { + const MODULE_NAME = 'Side Space' + let thisObject = { + sidePanelTab: undefined, + container: undefined, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + let isInitialized = false + + thisObject.container = newContainer() + thisObject.container.name = MODULE_NAME + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = true + thisObject.container.status = 'hidden' + + let listView + + resize() + + let openingEventSubscriptionId + let closedEventSubscriptionId + let browserResizedEventSubscriptionId + return thisObject + + function initialize() { + thisObject.sidePanelTab = newSidePanelTab() + thisObject.sidePanelTab.container.connectToParent(thisObject.container, false, false) + thisObject.sidePanelTab.initialize('left') + + browserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize) + + initializeListView() + isInitialized = true + } + + function finalize() { + canvas.eventHandler.stopListening(browserResizedEventSubscriptionId) + thisObject.sidePanelTab.container.eventHandler.stopListening(openingEventSubscriptionId) + thisObject.sidePanelTab.container.eventHandler.stopListening(closedEventSubscriptionId) + } + + function initializeListView() { + listView = newListView() + listView.initialize() + listView.container.connectToParent(thisObject.container, false, false) + openingEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('opening', listView.turnOn) + closedEventSubscriptionId = thisObject.sidePanelTab.container.eventHandler.listenToEvent('closed', listView.turnOff) + } + + function resize() { + thisObject.container.frame.width = SIDE_PANEL_WIDTH + thisObject.container.frame.height = browserCanvas.height // - TOP_SPACE_HEIGHT + thisObject.container.frame.position.x = -SIDE_PANEL_WIDTH + thisObject.container.frame.position.y = 0 // TOP_SPACE_HEIGHT + + if (listView !== undefined) { + listView.resize() + } + + if (thisObject.sidePanelTab !== undefined) { + thisObject.sidePanelTab.resize() + } + } + + function getContainer(point, purpose) { + let container + + container = thisObject.sidePanelTab.getContainer(point, purpose) + if (container !== undefined) { return container } + + container = listView.getContainer(point, purpose) + if (container !== undefined) { return container } + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function physics() { + thisObject.sidePanelTab.physics() + listView.physics() + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + if (isInitialized === false) { return } + borders() + thisObject.sidePanelTab.draw() + listView.draw() + } + + function borders() { + let point1 + let point2 + let point3 + let point4 + + point1 = { + x: 0, + y: 0 + } + + point2 = { + x: thisObject.container.frame.width, + y: 0 + } + + point3 = { + x: thisObject.container.frame.width, + y: thisObject.container.frame.height + } + + point4 = { + x: 0, + y: thisObject.container.frame.height + } + + point1 = thisObject.container.frame.frameThisPoint(point1) + point2 = thisObject.container.frame.frameThisPoint(point2) + point3 = thisObject.container.frame.frameThisPoint(point3) + point4 = thisObject.container.frame.frameThisPoint(point4) + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + browserCanvasContext.lineTo(point3.x, point3.y) + browserCanvasContext.lineTo(point4.x, point4.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + let opacity = 1 + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.LIGHT_GREY + ', ' + opacity + '' + browserCanvasContext.fill() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.GREY + ', ' + opacity + '' + browserCanvasContext.lineWidth = 0.3 + browserCanvasContext.stroke() + + /* Shadow */ + + if (thisObject.container.status !== 'hidden') { + for (let i = 0; i <= 30; i++) { + opacity = 1 - (i / 300) - 0.95 + + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.beginPath() + browserCanvasContext.moveTo(point2.x + i, point2.y) + browserCanvasContext.lineTo(point3.x + i, point3.y) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + opacity + '' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.stroke() + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Top-Space/TopSpace.js b/Integrations/Projects/Superalgos/Spaces/Top-Space/TopSpace.js new file mode 100644 index 000000000..aaa676871 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Top-Space/TopSpace.js @@ -0,0 +1,109 @@ + +function newSuperalgosTopSpace() { + let thisObject = { + container: undefined, + draw: draw, + getContainer: getContainer, + initialize: initialize + } + + thisObject.container = newContainer() + thisObject.container.initialize() + + resize() + + thisObject.container.isDraggeable = false + let backgroundImage + let logoImage + return thisObject + + function initialize() { + canvas.eventHandler.listenToEvent('Browser Resized', resize) + + backgroundImage = new Image() + logoImage = new Image() + + loadImage('superalgos-header-background', backgroundImage) + loadImage('superalgos-logo-for-color-background', logoImage) + + function loadImage(name, image) { + const PATH = 'Images/' + + image.onload = onImageLoad + image.fileName = name + + function onImageLoad() { + image.canDraw = true + } + image.src = PATH + name + '.png' + } + } + + function resize() { + thisObject.container.frame.width = browserCanvas.width + thisObject.container.frame.height = TOP_SPACE_HEIGHT + + thisObject.container.frame.position.x = 0 + try { + thisObject.container.frame.position.y = UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.y + } catch (e) { } + } + + function getContainer(point) { + + } + + function draw() { + if (CAN_SPACES_DRAW === false) { return } + thisObject.container.frame.draw(false, false) + + drawBackground() + return + } + + function drawBackground() { + /* Solid Color */ + let opacity = 1 + browserCanvasContext.beginPath() + browserCanvasContext.rect(0, 0, thisObject.container.frame.width, thisObject.container.frame.height) + if (ARE_WE_RECORDING_A_VIDEO === true) { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.PATINATED_TURQUOISE + ', ' + opacity + ')' + } else { + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.RUSTED_RED + ', ' + opacity + ')' + } + browserCanvasContext.closePath() + browserCanvasContext.fill() + + /* Background Image */ + if (backgroundImage !== undefined) { + if (backgroundImage.canDraw === true) { + let imagePosition = { + x: 0, + y: 0 + } + + browserCanvasContext.drawImage( + backgroundImage, imagePosition.x, + imagePosition.y, + browserCanvas.width, + TOP_SPACE_HEIGHT - 2) + } + } + + /* Logo Image */ + if (logoImage !== undefined) { + if (logoImage.canDraw === true) { + let imagePosition = { + x: 10, + y: 0 + } + + browserCanvasContext.drawImage( + logoImage, imagePosition.x, + imagePosition.y, + 215, + TOP_SPACE_HEIGHT - 2) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Spaces/Tutorial-Space/TutorialSpace.js b/Integrations/Projects/Superalgos/Spaces/Tutorial-Space/TutorialSpace.js new file mode 100644 index 000000000..0091438f8 --- /dev/null +++ b/Integrations/Projects/Superalgos/Spaces/Tutorial-Space/TutorialSpace.js @@ -0,0 +1,1229 @@ +function newSuperalgosTutorialSpace() { + const MODULE_NAME = 'Tutorial Space' + let thisObject = { + stop: stop, + skip: skip, + previous: previous, + next: next, + playTutorial: playTutorial, + resumeTutorial: resumeTutorial, + playTutorialTopic: playTutorialTopic, + resumeTutorialTopic: resumeTutorialTopic, + playTutorialStep: playTutorialStep, + resumeTutorialStep: resumeTutorialStep, + resetTutorialProgress: resetTutorialProgress, + container: undefined, + physics: physics, + draw: draw, + getContainer: getContainer, + initialize: initialize, + finalize: finalize + } + + let isInitialized = false + + thisObject.container = newContainer() + thisObject.container.name = MODULE_NAME + thisObject.container.initialize() + thisObject.container.isClickeable = true + thisObject.container.isDraggeable = false + thisObject.container.detectMouseOver = true + thisObject.container.status = 'hidden' + + resize() + + let browserResizedEventSubscriptionId + + let tutorialRootNode + let currentNode + let currentStatus = 'Stopped' + let navigationStack + + let tutorialDiv = document.getElementById('tutorialDiv') + let tutorialFormDiv = document.getElementById('tutorialFormDiv') + let htmlGif = document.createElement("IMG") + let currentGifName = 'Never Set' + let newGifName = 'Never Set' + let htmlImage = document.createElement("IMG") + let currentImageName = 'Never Set' + let newImageName = 'Never Set' + let currentConfig + let newConfig + let currentDocumentationURL + let resumeModeActivated // In this mode, we skip all the node which status is 'Done' + let lastExecutedAction = '' + let actionCounter = 0 + let forcingFocus = false + let currentZoomStep = 0 + let viewportCentered = false + let repositionAtTimeMachineCounter = 0 + let positionAtNodeCounter = 0 + let timeMachineKeyPressedCounter = 0 + let keyPressedCounter = 0 + let sliderCounter = 0 + let changeNodeConfigCounter = 0 + let changeNodeConfigCounterWithEval = 0 + let documentationCounter = 0 + let batchConfigChangesCounter = 0 + let workspacesCounter = 0 + + return thisObject + + function initialize() { + browserResizedEventSubscriptionId = canvas.eventHandler.listenToEvent('Browser Resized', resize) + isInitialized = true + } + + function finalize() { + canvas.eventHandler.stopListening(browserResizedEventSubscriptionId) + + tutorialRootNode = undefined + currentNode = undefined + navigationStack = undefined + + tutorialDiv = undefined + tutorialFormDiv = undefined + image = undefined + gif = undefined + } + + function resize() { + thisObject.container.frame.width = 0 + thisObject.container.frame.height = 0 + } + + function getContainer(point, purpose) { + let container + + if (thisObject.container.frame.isThisPointHere(point, true) === true) { + return thisObject.container + } else { + return undefined + } + } + + function physics() { + if (currentNode === undefined) { return } + + switch (currentStatus) { + case 'Stopped': { + makeInvisible() + currentNode = undefined + break + } + case 'Playing Tutorial': { + makeVsible() + break + } + case 'Playing Topic': { + makeVsible() + break + } + case 'Playing Step': { + makeVsible() + break + } + } + + keyPressed() + checkPressButton() + checkGif() + checkImage() + checkDocumentation() + checkWorkspaces() + checkSlider() + checkReference() + checkViewportZoom() + checkViewportZoomAtCenter() + return + + function keyPressed() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + if ( + config.keyPressed !== undefined + ) { + keyPressedCounter++ + if (keyPressedCounter === 10) { + let event = { + metaKey: config.keyPressed.metaKey, + shiftKey: config.keyPressed.shiftKey, + ctrlKey: config.keyPressed.ctrlKey, + code: config.keyPressed.code, + key: config.keyPressed.key + } + canvas.onKeyDown(event, true) + } + } + } + + function checkViewportZoom() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + if (config.viewportZoom !== undefined) { + let totalZoomSteps = config.viewportZoom + let delta + if (totalZoomSteps > 0) { + delta = 1 + } else { + delta = -1 + } + currentZoomStep = currentZoomStep + delta + let event = { + delta: delta + } + if (totalZoomSteps > 0) { + if (currentZoomStep < totalZoomSteps) { + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + } + } else { + if (currentZoomStep > totalZoomSteps) { + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + } + } + } + } + + function checkViewportZoomAtCenter() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + if (config.viewportZoomAtCenter !== undefined) { + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomAtCenter(config.viewportZoomAtCenter) + } + } + + function checkPressButton() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + if (config.pressButton === undefined || config.pressButton === "") { + return + } + /* Remove this item from the navigation stack. */ + navigationStack.splice(navigationStack.length - 1) + switch (config.pressButton) { + case "Stop": { + stop() + return + } + case "Skip": { + skip() + return + } + case "Previous": { + previous() + return + } + case "Next": { + next() + return + } + } + } + + function checkGif() { + let tutorialGifDiv = document.getElementById('tutorialGifDiv') + if (tutorialGifDiv !== null && tutorialGifDiv !== undefined) { + tutorialGifDiv.appendChild(htmlGif) + } + + if (currentGifName === newGifName) { return } + currentGifName = newGifName + htmlGif.src = 'Images/Tutorial/Gifs/' + currentGifName + '.gif' + htmlGif.width = "400" + htmlGif.height = "580" + } + + function checkImage() { + let tutorialImageDiv = document.getElementById('tutorialImageDiv') + if (tutorialImageDiv !== null && tutorialImageDiv !== undefined) { + tutorialImageDiv.appendChild(htmlImage) + } + + if (currentImageName === newImageName) { return } + currentImageName = newImageName + let webParam + if (currentImageName.indexOf('/') >= 0) { + webParam = 'Icons/' + currentImageName + '.png' + } else { + webParam = 'Icons/' + 'Superalgos' + '/' + currentImageName + '.png' + } + htmlImage.src = webParam + htmlImage.width = "100" + htmlImage.height = "100" + } + + function checkDocumentation() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + let newDocumentationURL = config.documentationURL + documentationCounter++ + if (documentationCounter === 10) { + if (newDocumentationURL === undefined) { + /* + The doc panel will remain as it is, and the user will be free to open or close it at will. + */ + return + } + if (newDocumentationURL === "") { + /* + This forces the tutorial to close the documentation panel and to keep it closed. + */ + UI.projects.superalgos.spaces.docSpace.sidePanelTab.close() + return + } + UI.projects.superalgos.spaces.docSpace.sidePanelTab.open() + if (newDocumentationURL === currentDocumentationURL) { return } + + currentDocumentationURL = newDocumentationURL + UI.projects.superalgos.spaces.docSpace.navigateTo(currentDocumentationURL) + } + } + + function checkWorkspaces() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + workspacesCounter++ + if (config.workspaces === undefined) { + /* + The worspaces panel will remain as it is, and the user will be free to open or close it at will. + */ + return + } + if (config.workspaces === "" || config.workspaces === "Close") { + /* + This forces the tutorial to close the workspaces panel and to keep it closed. + */ + if (workspacesCounter === 5) { + UI.projects.superalgos.spaces.sideSpace.sidePanelTab.close() + } + return + } + if (config.workspaces === "Open") { + /* + This forces the tutorial to open the workspaces panel and to keep it closed. + */ + if (workspacesCounter === 5) { + UI.projects.superalgos.spaces.sideSpace.sidePanelTab.open() + } + return + } + } + + function checkSlider() { + if (currentNode === undefined) { return } + let config = JSON.parse(currentNode.config) + if (config.slider === undefined) { + /* + The cockpit space will remain where it is. The user is free to move it. + */ + return + } + sliderCounter++ + if (sliderCounter === 10 || sliderCounter === 100) { + + switch (config.slider) { + case "toTop": { + /* + This forces the tutorial to close the charting space and to keep it closed. + */ + UI.projects.superalgos.spaces.cockpitSpace.toTop() + return + } + case "toMiddle": { + /* + This forces the tutorial to share the screen half with the designer and half + with the charting space and force it in that way. + */ + UI.projects.superalgos.spaces.cockpitSpace.toMiddle() + return + } + case "toBottom": { + /* + This forces the tutorial to fully open the charting space and to keep it open. + */ + UI.projects.superalgos.spaces.cockpitSpace.toBottom() + return + } + } + } + } + + function checkReference() { + /* + If there is a reference parent defined, we will highlight it + and move the designer so that that node be at the center of the screen. + */ + if (currentNode !== undefined) { + if (currentNode.payload !== undefined) { + if (currentNode.payload.referenceParent !== undefined) { + if (currentNode.payload.referenceParent.payload !== undefined) { + if (currentNode.payload.referenceParent.payload.uiObject !== undefined) { + let config = JSON.parse(currentNode.config) + if ( + config.positionAtReferenceParent === true + ) { + /* + This moves the Designs Space so that the referenced node is at the center of the screen. + */ + positionAtNodeCounter++ + if (positionAtNodeCounter === 1) { + currentNode.payload.referenceParent.payload.floatingObject.unCollapseParent() + } + if (positionAtNodeCounter < 100) { + UI.projects.superalgos.spaces.floatingSpace.positionAtNode(currentNode.payload.referenceParent) + } + } + if ( + config.highlightReferenceParent === true + ) { + currentNode.payload.referenceParent.payload.uiObject.highlight() + } + if ( + config.highlightReferenceParent === true + ) { + currentNode.payload.referenceParent.payload.uiObject.highlight() + } + if ( + config.setErrorMessageReferenceParent !== undefined + ) { + currentNode.payload.referenceParent.payload.uiObject.setErrorMessage(config.setErrorMessageReferenceParent) + } + if ( + config.setWarningMessageReferenceParent !== undefined + ) { + currentNode.payload.referenceParent.payload.uiObject.setWarningMessage(config.setWarningMessageReferenceParent) + } + if ( + config.setInfoMessageReferenceParent !== undefined + ) { + currentNode.payload.referenceParent.payload.uiObject.setInfoMessage(config.setInfoMessageReferenceParent) + } + if ( + config.setValueReferenceParent !== undefined + ) { + currentNode.payload.referenceParent.payload.uiObject.setValue(config.setValueReferenceParent) + } + if ( + config.setPercentageReferenceParent !== undefined + ) { + currentNode.payload.referenceParent.payload.uiObject.setPercentage(config.setPercentageReferenceParent) + } + if ( + config.setStatusReferenceParent !== undefined + ) { + currentNode.payload.referenceParent.payload.uiObject.setStatus(config.setStatusReferenceParent) + } + if ( + config.setFocusReferenceParent === true + ) { + currentNode.payload.referenceParent.payload.floatingObject.forceFocus() + forcingFocus = true + } + if ( + config.batchConfigChangesReferenceParent !== undefined + ) { + batchConfigChangesCounter++ + if (batchConfigChangesCounter === 5 || batchConfigChangesCounter === 10) { + /* + Here we are going to process an array of different changes to descendents + of the reference parent. The end user needs to specify a nodePath to the + descendent and an object with the properties and new values that want to + change at each descendent. The nodePath route starts at the object + referenceParent. + */ + let referenceParent = currentNode.payload.referenceParent + for (let i = 0; i < config.batchConfigChangesReferenceParent.length; i++) { + let arrayItem = config.batchConfigChangesReferenceParent[i] + let nodePath = arrayItem.nodePath + let properties = arrayItem.properties + + let childNode + try { + childNode = eval(nodePath) + } catch (err) { + console.log(MODULE_NAME + ' -> The nodePath provided is not good. -> nodePath = ' + nodePath) + let splittedPath = nodePath.split('.') + let partialPath = '' + let separator = '' + for (let j = 0; j < splittedPath.length; j++) { + let part = splittedPath[j] + let partialNode + let splittedParts = part.split('[') + if (splittedParts.length > 1) { + partialNode = eval(partialPath + separator + splittedParts[0]) + if (partialNode === undefined) { + console.log(MODULE_NAME + ' -> This part can not be undefined -> part = ' + splittedParts[0]) + currentNode.payload.uiObject.setErrorMessage('Node Path is incorrect. See console log for details.') + break + } + } + partialPath = partialPath + separator + part + separator = '.' + + partialNode = eval(partialPath) + if (partialNode === undefined) { + console.log(MODULE_NAME + ' -> This part can not be undefined -> part = ' + part) + currentNode.payload.uiObject.setErrorMessage('Node Path is incorrect. See console log for details.') + break + } + } + continue + } + let nodeConfig = JSON.parse(childNode.config) + for (const property in properties) { + nodeConfig[property] = properties[property] + } + childNode.config = JSON.stringify(nodeConfig, undefined, 5) + } + } + } + if ( + config.changeNodeConfigReferenceParent !== undefined + ) { + changeNodeConfigCounter++ + if (changeNodeConfigCounter === 5) { + let nodeConfig = JSON.parse(currentNode.payload.referenceParent.config) + for (const property in config.changeNodeConfigReferenceParent) { + nodeConfig[property] = config.changeNodeConfigReferenceParent[property] + } + currentNode.payload.referenceParent.config = JSON.stringify(nodeConfig) + } + } + if ( + config.changeNodeConfigWithEvalReferenceParent !== undefined + ) { + changeNodeConfigCounterWithEval++ + if (changeNodeConfigCounterWithEval === 5 || changeNodeConfigCounterWithEval === 10) { + let nodeConfig = JSON.parse(currentNode.payload.referenceParent.config) + for (const property in config.changeNodeConfigWithEvalReferenceParent) { + nodeConfig[property] = eval(config.changeNodeConfigWithEvalReferenceParent[property]) + } + currentNode.payload.referenceParent.config = JSON.stringify(nodeConfig) + } + } + if ( + config.menuActionReferenceParent !== undefined + ) { + /* + Some actions needs a little delay because they depend on animations to happen. + For that reason, we will count until 10 before executing actions from tutorial. + */ + actionCounter++ + if (actionCounter > 10) { + if (config.menuActionReferenceParent + currentNode.id !== lastExecutedAction) { + currentNode.payload.referenceParent.payload.uiObject.menu.internalClick(config.menuActionReferenceParent) + lastExecutedAction = config.menuActionReferenceParent + currentNode.id + } + } + } else { + lastExecutedAction = "" + } + if ( + config.keyPressedTimeMachineReferenceParent !== undefined + ) { + if (currentNode.payload.referenceParent.type === 'Time Machine') { + let timeMachine = UI.projects.superalgos.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent) + if (timeMachine !== undefined) { + timeMachineKeyPressedCounter++ + if (timeMachineKeyPressedCounter === 10) { + let event = { + metaKey: config.keyPressedTimeMachineReferenceParent.metaKey, + shiftKey: config.keyPressedTimeMachineReferenceParent.shiftKey, + ctrlKey: config.keyPressedTimeMachineReferenceParent.ctrlKey, + code: config.keyPressedTimeMachineReferenceParent.code, + key: config.keyPressedTimeMachineReferenceParent.key + } + timeMachine.edgeEditor.onKeyPressed(event, true) + } + } + } + } + if ( + config.viewportCenterReferenceParent === true + ) { + if (currentNode.payload.referenceParent.type === 'Time Machine') { + let timeMachine = UI.projects.superalgos.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent) + if (timeMachine !== undefined && viewportCentered === false) { + UI.projects.superalgos.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container) + viewportCentered = true + } + } + } + if ( + config.repositionAtTimeMachineReferenceParent === true + ) { + if (currentNode.payload.referenceParent.type === 'Time Machine') { + let timeMachine = UI.projects.superalgos.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent) + if (timeMachine !== undefined) { + repositionAtTimeMachineCounter++ + switch (true) { + case (repositionAtTimeMachineCounter > 0 && repositionAtTimeMachineCounter < 20): { + let event = { + delta: -1 + } + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + break + } + case (repositionAtTimeMachineCounter === 30): { + UI.projects.superalgos.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container) + break + } + case (repositionAtTimeMachineCounter === 60): { + let event = { + metaKey: true, + shiftKey: true, + ctrlKey: true, + code: undefined, + key: 'A' + } + timeMachine.edgeEditor.onKeyPressed(event, true) + break + } + case (repositionAtTimeMachineCounter === 90): { + UI.projects.superalgos.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container) + break + } + case (repositionAtTimeMachineCounter === 120): { + UI.projects.superalgos.spaces.chartingSpace.viewport.zoomAtCenter(8) + break + } + } + } + } + } + if ( + config.repositionAtTimeMachineNoZoomReferenceParent === true + ) { + if (currentNode.payload.referenceParent.type === 'Time Machine') { + let timeMachine = UI.projects.superalgos.spaces.chartingSpace.findTimeMachine(currentNode.payload.referenceParent) + if (timeMachine !== undefined) { + repositionAtTimeMachineCounter++ + switch (true) { + case (repositionAtTimeMachineCounter > 10 && repositionAtTimeMachineCounter < 20): { + let event = { + delta: -1 + } + UI.projects.superalgos.spaces.chartingSpace.viewport.onMouseWheel(event) + break + } + case (repositionAtTimeMachineCounter === 30): { + UI.projects.superalgos.spaces.chartingSpace.viewport.displaceToContainer(timeMachine.container) + break + } + } + } + } + } + } + } + } + } + } + } + + function makeInvisible() { + const HEIGHT = 0 + const WIDTH = 0 + const FORM_HEIGHT = 0 + + tutorialPosition = { + x: 100000, + y: 100000 + } + + tutorialDiv.style = ' ' + + 'position:fixed; top:' + tutorialPosition.y + 'px; ' + + 'left:' + tutorialPosition.x + 'px; ' + + 'width: ' + WIDTH + 'px;' + + 'height: ' + HEIGHT + 'px;' + + 'z-index:1;' + + tutorialFormDiv.style = ' ' + + 'position:fixed; top:' + (tutorialPosition.y) + 'px; ' + + 'left:' + tutorialPosition.x + 'px; ' + + 'width: ' + WIDTH + 'px;' + + 'height: ' + FORM_HEIGHT + 'px;' + + 'z-index:1;' + } + + function makeVsible() { + const HEIGHT = 650 + const WIDTH = 400 + const MARGIN = 100 + const FORM_HEIGHT = 40 + + let nodeConfig + try { + nodeConfig = JSON.parse(currentNode.config) + } catch (err) { + return + } + + switch (nodeConfig.position) { + case 'Left': { + tutorialPosition = { + x: MARGIN, + y: (browserCanvas.height - HEIGHT) / 2 + } + break + } + case 'Center': { + tutorialPosition = { + x: browserCanvas.width / 2 - WIDTH / 2, + y: (browserCanvas.height - HEIGHT) / 2 + } + break + } + case 'Right': { + tutorialPosition = { + x: browserCanvas.width - WIDTH - MARGIN, + y: (browserCanvas.height - HEIGHT) / 2 + } + break + } + default: { + tutorialPosition = { + x: browserCanvas.width / 2 - WIDTH / 2, + y: (browserCanvas.height - HEIGHT) / 2 + } + break + } + } + tutorialDiv.style = ' ' + + 'position:fixed; top:' + tutorialPosition.y + 'px; ' + + 'left:' + tutorialPosition.x + 'px; ' + + 'width: ' + WIDTH + 'px;' + + 'height: ' + HEIGHT + 'px;' + + 'z-index:1;' + + buildHTML() + + tutorialFormDiv.style = ' ' + + 'position:fixed; top:' + (tutorialPosition.y + HEIGHT - 30) + 'px; ' + + 'left:' + tutorialPosition.x + 'px; ' + + 'width: ' + (WIDTH + 20) + 'px;' + + 'height: ' + FORM_HEIGHT + 'px;' + + 'z-index:1;' + } + } + + function stop() { + resetAfterButtonPressed() + checkForceFocus() + if (tutorialRootNode !== undefined) { + tutorialRootNode.payload.uiObject.isPlaying = false + tutorialRootNode = undefined + } + currentStatus = 'Stopped' + } + + function skip() { + resetAfterButtonPressed() + checkForceFocus() + let tutorial = { + status: 'Skipped' + } + UI.projects.superalgos.utilities.tutorial.saveTutorial(currentNode.payload, tutorial) + advance(true) + } + + function previous() { + resetAfterButtonPressed() + checkForceFocus() + if (navigationStack.length > 1) { + let previousNode = navigationStack[navigationStack.length - 2] + switch (previousNode.type) { + case 'Tutorial': { + currentNode = previousNode + findTutorialNode(currentNode) + currentStatus = 'Playing Tutorial' + navigationStack.splice(navigationStack.length - 1) + break + } + case 'Tutorial Topic': { + currentNode = previousNode + findTutorialNode(currentNode) + currentStatus = 'Playing Topic' + navigationStack.splice(navigationStack.length - 1) + break + } + case 'Tutorial Step': { + currentNode = previousNode + findTutorialNode(currentNode) + currentStatus = 'Playing Step' + navigationStack.splice(navigationStack.length - 1) + break + } + } + } + } + + function next() { + resetAfterButtonPressed() + checkForceFocus() + if (ckeckGoingToAnotherTutorial() === true) { return } + let tutorial = { + status: 'Done' + } + UI.projects.superalgos.utilities.tutorial.saveTutorial(currentNode.payload, tutorial) + advance() + } + + function resetAfterButtonPressed() { + resetViewport() + resetActions() + resetWorkspaces() + resetDocumentation() + resetRepositionAtTimeMachine() + resetPositionAtNode() + resetTimeMachineKeyPressed() + resetKeyPressed() + resetSlider() + changeNodeConfig() + batchConfigChanges() + + function resetActions() { + lastExecutedAction = "" + actionCounter = 0 + } + + function resetViewport() { + currentZoomStep = 0 + viewportCentered = false + } + + function resetWorkspaces() { + workspacesCounter = 0 + } + + function resetDocumentation() { + currentDocumentationURL = '' + documentationCounter = 0 + } + + function resetRepositionAtTimeMachine() { + repositionAtTimeMachineCounter = 0 + } + + function resetPositionAtNode() { + positionAtNodeCounter = 0 + } + + function resetTimeMachineKeyPressed() { + timeMachineKeyPressedCounter = 0 + } + + function resetKeyPressed() { + keyPressedCounter = 0 + } + + function resetSlider() { + sliderCounter = 0 + } + + function changeNodeConfig() { + changeNodeConfigCounter = 0 + changeNodeConfigCounterWithEval = 0 + } + + function batchConfigChanges() { + batchConfigChangesCounter = 0 + } + } + + function checkForceFocus() { + if (forcingFocus === true) { + currentNode.payload.referenceParent.payload.floatingObject.removeForceFocus() + forcingFocus = false + } + } + + function ckeckGoingToAnotherTutorial() { + if (currentNode.payload !== undefined) { + if (currentNode.payload.referenceParent !== undefined) { + let config = JSON.parse(currentNode.config) + if (config.startTutorialReferenceParent === true) { + if (currentNode.payload.referenceParent.type === 'Tutorial') { + tutorialRootNode = currentNode.payload.referenceParent + currentNode = currentNode.payload.referenceParent + currentStatus = 'Playing Tutorial' + resumeModeActivated = false + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return true + } + } + } + } + } + + function playTutorial(node) { + navigationStack = [] + node.payload.uiObject.isPlaying = true + tutorialRootNode = node + currentNode = node + currentStatus = 'Playing Tutorial' + resumeModeActivated = false + navigationStack.push(currentNode) + } + + function resumeTutorial(node) { + navigationStack = [] + node.payload.uiObject.isPlaying = true + tutorialRootNode = node + currentNode = node + currentStatus = 'Playing Tutorial' + resumeModeActivated = true + navigationStack.push(currentNode) + advance() + } + + function playTutorialTopic(node) { + navigationStack = [] + currentTopicNode = node + currentNode = node + currentStatus = 'Playing Topic' + resumeModeActivated = false + navigationStack.push(currentNode) + findTutorialNode(node) + } + + function resumeTutorialTopic(node) { + navigationStack = [] + currentTopicNode = node + currentNode = node + currentStatus = 'Playing Topic' + resumeModeActivated = true + navigationStack.push(currentNode) + findTutorialNode(node) + } + + function playTutorialStep(node) { + navigationStack = [] + currentStepNode = node + currentNode = node + currentStatus = 'Playing Step' + resumeModeActivated = false + navigationStack.push(currentNode) + findTutorialNode(node) + } + + function resumeTutorialStep(node) { + navigationStack = [] + currentStepNode = node + currentNode = node + currentStatus = 'Playing Step' + resumeModeActivated = true + navigationStack.push(currentNode) + findTutorialNode(node) + } + + function resetTutorialProgress(node) { + resetNextNode(node) + + function resetNextNode(node) { + + for (let i = 0; i < node.tutorialSteps.length; i++) { + let tutorialStep = node.tutorialSteps[i] + let tutorial = { + status: 'Reset' + } + UI.projects.superalgos.utilities.tutorial.saveTutorial(tutorialStep.payload, tutorial) + } + + for (let i = 0; i < node.tutorialTopics.length; i++) { + let tutorialTopic = node.tutorialTopics[i] + let tutorial = { + status: 'Reset' + } + UI.projects.superalgos.utilities.tutorial.saveTutorial(tutorialTopic.payload, tutorial) + resetNextNode(tutorialTopic) + } + } + } + + function findTutorialNode(node) { + /* + We will consider the tutorialRootNode the head of the hirierchy + */ + tutorialRootNode = node + + if (node.payload !== undefined) { + if (node.payload.parentNode !== undefined) { + findTutorialNode(node.payload.parentNode) + } + } + } + + function advance(isSkipping) { + let found + let advanced + + switch (currentStatus) { + case 'Playing Tutorial': { + found = true + findNextNode(tutorialRootNode) + break + } + case 'Playing Topic': { + found = false + if (isSkipping === true) { + findNextTopic(tutorialRootNode) + } else { + findNextNode(tutorialRootNode) + } + break + } + case 'Playing Step': { + found = false + findNextNode(tutorialRootNode) + break + } + } + + /* + If we can not find a next node, then we stop the tutorial. + */ + if (advanced === undefined) { + stop() + return + } + + + function findNextNode(node) { + if (node.tutorialSteps !== undefined) { + for (let i = 0; i < node.tutorialSteps.length; i++) { + + let tutorialStep = node.tutorialSteps[i] + if (found === true) { + if (resumeModeActivated !== true) { + currentNode = tutorialStep + currentStatus = 'Playing Step' + advanced = true + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return + } else { + let tutorial = { + status: undefined + } + UI.projects.superalgos.utilities.tutorial.loadTutorial(tutorialStep.payload, tutorial) + if (tutorial.status !== 'Done') { + currentNode = tutorialStep + currentStatus = 'Playing Step' + advanced = true + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return + } + } + } + if (tutorialStep.id === currentNode.id) { + found = true + } + } + } + + for (let i = 0; i < node.tutorialTopics.length; i++) { + + if (advanced === true) { + return + } + + let tutorialTopic = node.tutorialTopics[i] + if (found === true) { + if (resumeModeActivated !== true) { + currentNode = tutorialTopic + currentStatus = 'Playing Topic' + advanced = true + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return + } else { + let tutorial = { + status: undefined + } + UI.projects.superalgos.utilities.tutorial.loadTutorial(tutorialTopic.payload, tutorial) + if (tutorial.status !== 'Done') { + currentNode = tutorialTopic + currentStatus = 'Playing Topic' + advanced = true + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return + } + } + } + if (tutorialTopic.id === currentNode.id) { + found = true + } + + findNextNode(tutorialTopic, found) + } + } + + function findNextTopic(node) { + for (let i = 0; i < node.tutorialTopics.length; i++) { + + if (advanced === true) { + return + } + + let tutorialTopic = node.tutorialTopics[i] + if (found === true) { + if (resumeModeActivated !== true) { + currentNode = tutorialTopic + currentStatus = 'Playing Topic' + advanced = true + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return + } else { + let tutorial = { + status: undefined + } + UI.projects.superalgos.utilities.tutorial.loadTutorial(tutorialTopic.payload, tutorial) + if (tutorial.status !== 'Done') { + currentNode = tutorialTopic + currentStatus = 'Playing Topic' + advanced = true + navigationStack.push(currentNode) + findTutorialNode(currentNode) + return + } + } + } + if (tutorialTopic.id === currentNode.id) { + found = true + } else { + findNextTopic(tutorialTopic, found) + } + } + } + } + + function buildHTML() { + + newConfig = currentNode.config + if (newConfig === currentConfig) { return } + currentConfig = newConfig + + let nodeConfig = JSON.parse(currentNode.config) + let html = '' + if (nodeConfig.title !== undefined && nodeConfig.title !== '') { + html = html + '

' + nodeConfig.title + '

' + } + html = html + '
' + if (nodeConfig.summary !== undefined && nodeConfig.summary !== '') { + html = html + '
' + addToolTips(nodeConfig.summary) + '
' + } + if (nodeConfig.subTitle !== undefined && nodeConfig.subTitle !== '') { + html = html + '

' + nodeConfig.subTitle + '

' + } + if (nodeConfig.gif !== undefined && nodeConfig.gif !== '') { + html = html + '
' + newGifName = nodeConfig.gif + } + + if (nodeConfig.definition !== undefined && nodeConfig.definition !== '') { + html = html + '' + html = html + '' + html = html + '' + html = html + '' + html = html + '' + html = html + '
' + if (nodeConfig.image !== undefined && nodeConfig.image !== '') { + html = html + '
' + newImageName = nodeConfig.image + } + html = html + '
' + html = html + '' + addToolTips(nodeConfig.definition) + '' + html = html + '
' + } + if (nodeConfig.bulletListIntro !== undefined && nodeConfig.bulletListIntro !== '') { + html = html + '
' + addToolTips(nodeConfig.bulletListIntro) + '
' + } + if (nodeConfig.bulletArray !== undefined) { + html = html + '' + } + if (nodeConfig.paragraph1 !== undefined && nodeConfig.paragraph1 !== '') { + html = html + '
' + addToolTips(nodeConfig.paragraph1) + '
' + } + if (nodeConfig.callOut !== undefined && nodeConfig.callOut !== '') { + html = html + '
' + addToolTips(nodeConfig.callOut) + '' + if (nodeConfig.externalLink !== undefined) { + html = html + '' + nodeConfig.externalLink[0] + '' + } + html = html + '
' + } + if (nodeConfig.paragraph2 !== undefined && nodeConfig.paragraph2 !== '') { + html = html + '
' + addToolTips(nodeConfig.paragraph2) + '
' + } + if (nodeConfig.note !== undefined && nodeConfig.note !== '') { + html = html + '' + } + if (nodeConfig.tip !== undefined && nodeConfig.tip !== '') { + html = html + '' + } + if (nodeConfig.important !== undefined && nodeConfig.important !== '') { + html = html + '' + } + if (nodeConfig.warning !== undefined && nodeConfig.warning !== '') { + html = html + '' + } + html = html + '
' + + tutorialDiv.innerHTML = html + + function addToolTips(text) { + const TOOL_TIP_HTML = '
NODE_TYPENODE_DEFINITION
' + let resultingText = '' + let splittedText = text.split('->') + + for (let i = 0; i < splittedText.length; i = i + 2) { + let firstPart = splittedText[i] + let nodeType = splittedText[i + 1] + if (nodeType === undefined) { + return resultingText + firstPart + } + let splittedNodeType = nodeType.split('/') + let project + let type + if (splittedNodeType.length > 1) { + project = splittedNodeType[0] + type = splittedNodeType[1] + } else { + project = 'Superalgos' + type = splittedNodeType[0] + } + let definitionNode = SCHEMAS_BY_PROJECT.get(project).map.docSchema.get(type) + if (definitionNode === undefined) { + definitionNode = SCHEMAS_BY_PROJECT.get(project).map.conceptSchema.get(type) + if (definitionNode === undefined) { + currentNode.payload.uiObject.setErrorMessage(type + ' not found at Doc Schema or Concept Schema.') + return + } + } + let definition = definitionNode.definition + if (definition === undefined || definition === "") { + resultingText = resultingText + firstPart + type + } else { + let tooltip = TOOL_TIP_HTML.replace('NODE_TYPE', type).replace('NODE_DEFINITION', definition) + resultingText = resultingText + firstPart + tooltip + } + } + return resultingText + } + } + + function draw() { + if (isInitialized === false) { return } + } +} diff --git a/Integrations/Projects/Superalgos/Utilities/Branches.js b/Integrations/Projects/Superalgos/Utilities/Branches.js new file mode 100644 index 000000000..1ea5e4251 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Branches.js @@ -0,0 +1,66 @@ +function newSuperalgosUtilitiesBranches() { + thisObject = { + nodeBranchToArray: nodeBranchToArray, + findInBranch: findInBranch + } + + return thisObject + + function nodeBranchToArray(node, nodeType) { + /* + This function scans a node branch for a certain node type and + return an array of with all the nodes of that type found. + */ + if (node === undefined) { return } + if (nodeType === undefined) { return } + let resultArray = [] + scanNodeBranch(node, nodeType) + return resultArray + + function scanNodeBranch(startingNode) { + if (startingNode === undefined) { return } + + let nodeDefinition = getNodeDefinition(startingNode) + if (nodeDefinition === undefined) { return } + + if (startingNode.type === nodeType) { + resultArray.push(startingNode) + return + } + + if (nodeDefinition.properties === undefined) { return } + let lastNodePropertyName + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + if (lastNodePropertyName === property.name) { continue } // Some nodes have a single property por multiple child node types. We need to check repeated properties only once so as no to duplicate results. + switch (property.type) { + case 'node': { + scanNodeBranch(startingNode[property.name]) + lastNodePropertyName = property.name + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + for (let m = 0; m < startingNodePropertyArray.length; m++) { + scanNodeBranch(startingNodePropertyArray[m]) + } + break + } + } + } + } + } + + function findInBranch(startingNode, nodeType, errorNode, connectedViaPayload) { + /* + This function finds a node in a brach and reports an error if it could not be found. + */ + let nodeFound = UI.projects.superalgos.utilities.meshes.findNodeInNodeMesh(startingNode, nodeType, undefined, connectedViaPayload, true, false, false) + if (nodeFound === undefined) { + errorNode.payload.uiObject.setErrorMessage('Could not find ' + nodeType + ' at ' + startingNode.type + ' ' + startingNode.name) + return + } else { + return nodeFound + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/Clipboard.js b/Integrations/Projects/Superalgos/Utilities/Clipboard.js new file mode 100644 index 000000000..877358cf3 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Clipboard.js @@ -0,0 +1,44 @@ +function newSuperalgosUtilitiesClipboard() { + thisObject = { + fallbackCopyTextToClipboard: fallbackCopyTextToClipboard, + copyTextToClipboard: copyTextToClipboard + } + + return thisObject + + function fallbackCopyTextToClipboard(text) { + var textArea = document.createElement("textarea"); + textArea.value = text; + + // Avoid scrolling to bottom + textArea.style.top = "0"; + textArea.style.left = "0"; + textArea.style.position = "fixed"; + + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + var successful = document.execCommand('copy'); + var msg = successful ? 'successful' : 'unsuccessful'; + //console.log('Fallback: Copying text command was ' + msg); + } catch (err) { + console.error('Fallback: Oops, unable to copy', err); + } + + document.body.removeChild(textArea); + } + + function copyTextToClipboard(text) { + if (!navigator.clipboard) { + fallbackCopyTextToClipboard(text); + return; + } + navigator.clipboard.writeText(text).then(function () { + //console.log('Async: Copying to clipboard was successful!'); + }, function (err) { + console.error('Async: Could not copy text: ', err); + }); + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/CoordinateTransformations.js b/Integrations/Projects/Superalgos/Utilities/CoordinateTransformations.js new file mode 100644 index 000000000..00922e6fc --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/CoordinateTransformations.js @@ -0,0 +1,26 @@ +function newSuperalgosUtilitiesCoordinateTransformations() { + thisObject = { + transformThisPoint: transformThisPoint, + unTransformThisPoint: unTransformThisPoint + } + + return thisObject + + function transformThisPoint(point, container) { + /* We make the point relative to the current frame */ + + point = container.frame.frameThisPoint(point) + + /* We pass this point through the UI.projects.superalgos.spaces.chartingSpace.viewport lends, meaning we apply the UI.projects.superalgos.spaces.chartingSpace.viewport zoom and displacement. */ + + point = UI.projects.superalgos.spaces.chartingSpace.viewport.transformThisPoint(point) + return point + } + + function unTransformThisPoint(point, container) { + point = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(point) + + point = container.frame.unframeThisPoint(point) + return point + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/DateRateTransformations.js b/Integrations/Projects/Superalgos/Utilities/DateRateTransformations.js new file mode 100644 index 000000000..1f17fef42 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/DateRateTransformations.js @@ -0,0 +1,66 @@ +function newSuperalgosUtilitiesDateRateTransformations() { + thisObject = { + getDateFromPointAtBrowserCanvas: getDateFromPointAtBrowserCanvas, + getDateFromPointAtChartsSpace: getDateFromPointAtChartsSpace, + getDateFromPointAtContainer: getDateFromPointAtContainer, + getRateFromPointAtBrowserCanvas: getRateFromPointAtBrowserCanvas, + getRateFromPointAtChartingSpace: getRateFromPointAtChartingSpace, + getRateFromPointAtContainer: getRateFromPointAtContainer, + getMilisecondsFromPoint: getMilisecondsFromPoint + } + + return thisObject + + function getDateFromPointAtBrowserCanvas(point, container, coordinateSystem) { + point = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(point, container) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + let date = new Date(point.x) + + return date + } + + function getDateFromPointAtChartsSpace(point, container, coordinateSystem) { + point = container.frame.unframeThisPoint(point) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + let date = new Date(point.x) + + return date + } + + function getDateFromPointAtContainer(point, container, coordinateSystem) { + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + let date = new Date(point.x) + + return date + } + + function getRateFromPointAtBrowserCanvas(point, container, coordinateSystem) { + point = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(point, container) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.y + } + + function getRateFromPointAtChartingSpace(point, container, coordinateSystem) { + point = container.frame.unframeThisPoint(point) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.y + } + + function getRateFromPointAtContainer(point, container, coordinateSystem) { + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.y + } + + function getMilisecondsFromPoint(point, container, coordinateSystem) { + point = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(point, container) + point = coordinateSystem.unInverseTransform(point, container.frame.height) + + return point.x + } +} diff --git a/Integrations/Projects/Superalgos/Utilities/Dates.js b/Integrations/Projects/Superalgos/Utilities/Dates.js new file mode 100644 index 000000000..2a406c2cb --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Dates.js @@ -0,0 +1,21 @@ +function newSuperalgosUtilitiesDates() { + thisObject = { + formatDate: formatDate + } + + return thisObject + + function formatDate(date) { + var d = new Date(date), + month = '' + (d.getMonth() + 1), + day = '' + d.getDate(), + year = d.getFullYear(); + + if (month.length < 2) + month = '0' + month; + if (day.length < 2) + day = '0' + day; + + return [year, month, day].join('-'); + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/Download.js b/Integrations/Projects/Superalgos/Utilities/Download.js new file mode 100644 index 000000000..85be8aa43 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Download.js @@ -0,0 +1,69 @@ +function newSuperalgosUtilitiesDownload() { + thisObject = { + downloadText: downloadText, + downloadCanvas: downloadCanvas, + downloadPanorama: downloadPanorama, + addToMarketPanorama: addToMarketPanorama + } + + return thisObject + + function downloadText(filename, text) { + let element = document.createElement('a') + element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text)) + element.setAttribute('download', filename) + element.style.display = 'none' + document.body.appendChild(element) + element.click() + document.body.removeChild(element) + } + + function downloadCanvas(filename, canvasToUse) { + let data = canvasToUse.toDataURL('image/png', 1) + /* Change MIME type to trick the browser to downlaod the file instead of displaying it */ + data = data.replace(/^data:image\/[^;]*/, 'data:application/octet-stream') + + /* In addition to 's "download" attribute, you can define HTTP-style headers */ + data = data.replace(/^data:application\/octet-stream/, 'data:application/octet-stream;headers=Content-Disposition%3A%20attachment%3B%20filename=' + filename + '.png') + + let element = document.createElement('a') + element.setAttribute('href', data) + element.setAttribute('download', filename + '.PNG') + element.style.display = 'none' + document.body.appendChild(element) + element.click() + document.body.removeChild(element) + } + + function downloadPanorama(filename) { + let INITIAL_WIDTH = 800 + let FINAL_WIDTH = browserCanvas.width - INITIAL_WIDTH + + if (ARE_WE_RECORDING_A_MARKET_PANORAMA === false) { + /* We start recording the panorame. */ + ARE_WE_RECORDING_A_MARKET_PANORAMA = true + marketPanoramaCanvas = document.createElement('canvas') + marketPanoramaCanvas.width = INITIAL_WIDTH * 50 + marketPanoramaCanvas.height = browserCanvas.height + marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, 0, 0, INITIAL_WIDTH, browserCanvas.height, 0, 0, INITIAL_WIDTH, browserCanvas.height) + CURRENT_PANORAMA_POSITION = INITIAL_WIDTH + } else { + /* We stop recording and download the resulting image. */ + ARE_WE_RECORDING_A_MARKET_PANORAMA = false + marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, browserCanvas.width - FINAL_WIDTH, 0, FINAL_WIDTH, browserCanvas.height, CURRENT_PANORAMA_POSITION, 0, FINAL_WIDTH, browserCanvas.height) + let finalResultCanvas = document.createElement('canvas') + finalResultCanvas.width = CURRENT_PANORAMA_POSITION + FINAL_WIDTH + finalResultCanvas.height = browserCanvas.height + finalResultCanvas.getContext('2d').drawImage(marketPanoramaCanvas, 0, 0, finalResultCanvas.width, browserCanvas.height, 0, 0, finalResultCanvas.width, browserCanvas.height) + downloadCanvas(filename, finalResultCanvas) + marketPanoramaCanvas = undefined + } + } + + function addToMarketPanorama() { + let SECTION_WIDTH = 80 * 2 + let INITIAL_POSITION = 800 - SECTION_WIDTH + marketPanoramaCanvas.getContext('2d').drawImage(browserCanvas, INITIAL_POSITION, 0, SECTION_WIDTH, browserCanvas.height, CURRENT_PANORAMA_POSITION, 0, SECTION_WIDTH, browserCanvas.height) + CURRENT_PANORAMA_POSITION = CURRENT_PANORAMA_POSITION + SECTION_WIDTH + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/DrawPrint.js b/Integrations/Projects/Superalgos/Utilities/DrawPrint.js new file mode 100644 index 000000000..b0fffa308 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/DrawPrint.js @@ -0,0 +1,232 @@ +function newSuperalgosUtilitiesDrawPrint() { + thisObject = { + roundedCornersBackground: roundedCornersBackground, + drawLabel: drawLabel, + drawIcon: drawIcon, + drawContainerBackground: drawContainerBackground, + printLabel: printLabel + } + + return thisObject + + function roundedCornersBackground(params) { + if (params.xOffset === undefined) { params.xOffset = 0 } + borderPoint1 = { + x: params.cornerRadius + params.xOffset, + y: params.cornerRadius + } + + borderPoint2 = { + x: params.container.frame.width - params.cornerRadius, + y: params.cornerRadius + } + + borderPoint3 = { + x: params.container.frame.width - params.cornerRadius, + y: params.container.frame.height - params.cornerRadius + } + + borderPoint4 = { + x: params.cornerRadius + params.xOffset, + y: params.container.frame.height - params.cornerRadius + } + + /* Now the transformations. */ + + if (params.coordinateSystem !== undefined) { // For instance Candles would use this + borderPoint1 = params.coordinateSystem.transformThisPoint(borderPoint1) + borderPoint2 = params.coordinateSystem.transformThisPoint(borderPoint2) + borderPoint3 = params.coordinateSystem.transformThisPoint(borderPoint3) + borderPoint4 = params.coordinateSystem.transformThisPoint(borderPoint4) + } else { + if (params.outsideViewPort === true) { // If the element is not affected by zoom + borderPoint1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(borderPoint1, params.container) + borderPoint2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(borderPoint2, params.container) + borderPoint3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(borderPoint3, params.container) + borderPoint4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(borderPoint4, params.container) + } else { // For instance Panels use this + borderPoint1 = params.container.frame.frameThisPoint(borderPoint1) + borderPoint2 = params.container.frame.frameThisPoint(borderPoint2) + borderPoint3 = params.container.frame.frameThisPoint(borderPoint3) + borderPoint4 = params.container.frame.frameThisPoint(borderPoint4) + } + } + + if (params.fitFunction !== undefined) { + borderPoint1 = params.fitFunction(borderPoint1) + borderPoint2 = params.fitFunction(borderPoint2) + borderPoint3 = params.fitFunction(borderPoint3) + borderPoint4 = params.fitFunction(borderPoint4) + } + + browserCanvasContext.setLineDash([0, 0]) + + /* Shadow */ + + if (params.castShadow === true) { + let SHADOW_OFFSET = 2 + + browserCanvasContext.beginPath() + + browserCanvasContext.arc(borderPoint1.x + SHADOW_OFFSET, borderPoint1.y + SHADOW_OFFSET, params.cornerRadius, 1.0 * Math.PI, 1.5 * Math.PI) + browserCanvasContext.lineTo(borderPoint2.x + SHADOW_OFFSET, borderPoint2.y - params.cornerRadius + SHADOW_OFFSET) + browserCanvasContext.arc(borderPoint2.x + SHADOW_OFFSET, borderPoint2.y + SHADOW_OFFSET, params.cornerRadius, 1.5 * Math.PI, 2.0 * Math.PI) + browserCanvasContext.lineTo(borderPoint3.x + params.cornerRadius + SHADOW_OFFSET, borderPoint3.y + SHADOW_OFFSET) + browserCanvasContext.arc(borderPoint3.x + SHADOW_OFFSET, borderPoint3.y + SHADOW_OFFSET, params.cornerRadius, 0 * Math.PI, 0.5 * Math.PI) + browserCanvasContext.lineTo(borderPoint4.x + SHADOW_OFFSET, borderPoint4.y + params.cornerRadius + SHADOW_OFFSET) + browserCanvasContext.arc(borderPoint4.x + SHADOW_OFFSET, borderPoint4.y + SHADOW_OFFSET, params.cornerRadius, 0.5 * Math.PI, 1.0 * Math.PI) + browserCanvasContext.lineTo(borderPoint1.x - params.cornerRadius + SHADOW_OFFSET, borderPoint1.y + SHADOW_OFFSET) + + browserCanvasContext.closePath() + + browserCanvasContext.lineWidth = 10 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + 0.01 + ')' + browserCanvasContext.stroke() + + browserCanvasContext.lineWidth = 5 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + 0.01 + ')' + browserCanvasContext.stroke() + + browserCanvasContext.lineWidth = 1 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.BLACK + ', ' + 0.15 + ')' + browserCanvasContext.stroke() + } + + /* Background */ + + browserCanvasContext.beginPath() + + browserCanvasContext.arc(borderPoint1.x, borderPoint1.y, params.cornerRadius, 1.0 * Math.PI, 1.5 * Math.PI) + browserCanvasContext.lineTo(borderPoint2.x, borderPoint2.y - params.cornerRadius) + browserCanvasContext.arc(borderPoint2.x, borderPoint2.y, params.cornerRadius, 1.5 * Math.PI, 2.0 * Math.PI) + browserCanvasContext.lineTo(borderPoint3.x + params.cornerRadius, borderPoint3.y) + browserCanvasContext.arc(borderPoint3.x, borderPoint3.y, params.cornerRadius, 0 * Math.PI, 0.5 * Math.PI) + browserCanvasContext.lineTo(borderPoint4.x, borderPoint4.y + params.cornerRadius) + browserCanvasContext.arc(borderPoint4.x, borderPoint4.y, params.cornerRadius, 0.5 * Math.PI, 1.0 * Math.PI) + browserCanvasContext.lineTo(borderPoint1.x - params.cornerRadius, borderPoint1.y) + + browserCanvasContext.closePath() + + browserCanvasContext.fillStyle = 'rgba(' + params.backgroundColor + ', ' + params.opacity + ')' + + browserCanvasContext.fill() + + browserCanvasContext.lineWidth = params.lineWidth + browserCanvasContext.strokeStyle = 'rgba(' + params.borderColor + ', ' + params.opacity + ')' + browserCanvasContext.stroke() + } + + function drawLabel(label, xFactor, yFactor, xOffset, yOffset, fontSize, container, fontColor, noFrameX, noFrameY, opacity) { + if (label === undefined) { return } + if (fontColor === undefined) { fontColor = UI_COLOR.WHITE } + let fontOffset = label.length * fontSize * FONT_ASPECT_RATIO + 10 + if (opacity === undefined) { opacity = 1 } + + let labelPoint = { + x: container.frame.width * xFactor + xOffset - fontOffset / 2, + y: container.frame.height * yFactor + yOffset + } + + labelPoint = container.frame.frameThisPoint(labelPoint) + + if (noFrameX !== undefined) { + labelPoint.x = noFrameX + xOffset + } + + if (noFrameY !== undefined) { + labelPoint.y = noFrameY + yOffset + } + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + browserCanvasContext.fillStyle = 'rgba(' + fontColor + ', ' + opacity + ')' + + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + + function drawIcon(icon, xFactor, yFactor, xOffset, yOffset, imageSize, container) { + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + let imagePosition = { + x: container.frame.width * xFactor + xOffset - imageSize / 2, + y: container.frame.height * yFactor + yOffset - imageSize / 2 + } + + imagePosition = container.frame.frameThisPoint(imagePosition) + + browserCanvasContext.drawImage( + icon, imagePosition.x, + imagePosition.y, + imageSize, + imageSize) + } + } + } + + function drawContainerBackground(container, color, opacity, fitFunction) { + let fromPoint = { + x: 0, + y: 0 + } + + let toPoint = { + x: container.frame.width, + y: container.frame.height + } + + fromPoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(fromPoint, container) + toPoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(toPoint, container) + + fromPoint = fitFunction(fromPoint) + toPoint = fitFunction(toPoint) + + browserCanvasContext.beginPath() + browserCanvasContext.rect(fromPoint.x, fromPoint.y, toPoint.x - fromPoint.x, toPoint.y - fromPoint.y) + browserCanvasContext.fillStyle = 'rgba(' + color + ', ' + opacity + ')' + browserCanvasContext.closePath() + browserCanvasContext.fill() + } + + function printLabel(labelToPrint, x, y, opacity, fontSize, color, center, container, fitFunction, noDecimals, fixedDecimals) { + if (labelToPrint === undefined) { return } + let labelPoint + if (color === undefined) { color = UI_COLOR.DARK } + if (fontSize === undefined) { fontSize = 10 }; + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + let label = labelToPrint + if (isNaN(label) === false && label !== '') { + if (fixedDecimals !== true) { + label = dynamicDecimals(labelToPrint) + } + label = label.toLocaleString() + if (noDecimals === true) { + label = Math.trunc(label) + } + } + + let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + + if (center === true) { + labelPoint = { + x: x - xOffset, + y: y + } + } else { + labelPoint = { + x: x, + y: y + } + } + + if (container !== undefined) { + labelPoint = container.frame.frameThisPoint(labelPoint) + } + + if (fitFunction !== undefined) { + labelPoint = fitFunction(labelPoint) + } + + browserCanvasContext.fillStyle = 'rgba(' + color + ', ' + opacity + ')' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } +} diff --git a/Integrations/Projects/Superalgos/Utilities/Folders.js b/Integrations/Projects/Superalgos/Utilities/Folders.js new file mode 100644 index 000000000..4f50fc0ce --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Folders.js @@ -0,0 +1,99 @@ +function newSuperalgosUtilitiesFolders() { + thisObject = { + asymetricalFolderStructureCloning: asymetricalFolderStructureCloning + } + + return thisObject + + function asymetricalFolderStructureCloning( + originNode, + destinationNode, + originObjectsArrayPropertyName, + originFoldersArrayPropertyName, + destinationFoldersArrayPropertyName, + clonedObjectType, + destinationFolderType, + originObjectArrayPropertyName, + functionLibraryUiObjectsFromNodes + ) { + /* + This function will scan one branch of the workspace, and recreate + a similar structure in some other place, based on the parameters + sent to the function is what types of nodes are going to be mapped + into which other types. + */ + if (originNode === undefined) { return } + if (destinationNode === undefined) { return } + + /* Now we go down through all the children of the origin node, recreating at the destination node the same children structure*/ + let nodeDefinition = getNodeDefinition(originNode) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.name) { + case originObjectsArrayPropertyName: { + let originNodePropertyArray = originNode[property.name] + for (let m = 0; m < originNodePropertyArray.length; m++) { + let originObject = originNodePropertyArray[m] + if (originObjectArrayPropertyName === undefined) { + /* + In this first Use Case, we are going to connect each orgin object clone + to its origin object counter part. + */ + let clonedObject = functionLibraryUiObjectsFromNodes.addUIObject(destinationNode, clonedObjectType) + clonedObject.payload.referenceParent = originObject + } else { + /* + In this second Use Case, we are going to connect an inner object that is + inside the origin object at an array. We recognize we have to do this + because we did receive this array proeprty name. + */ + let originObjectArray = originObject[originObjectArrayPropertyName] + for (let n = 0; n < originObjectArray.length; n++) { + let originObjectArrayItem = originObjectArray[n] + let clonedObject = functionLibraryUiObjectsFromNodes.addUIObject(destinationNode, clonedObjectType) + clonedObject.payload.referenceParent = originObjectArrayItem + } + } + } + break + } + case originFoldersArrayPropertyName: { + let originNodePropertyArray = originNode[property.name] + let destinationNodePropertyArray = destinationNode[destinationFoldersArrayPropertyName] + for (let m = 0; m < originNodePropertyArray.length; m++) { + let originFolder = originNodePropertyArray[m] + let found = false + let destinationFolder + for (let n = 0; n < destinationNodePropertyArray.length; n++) { + destinationFolder = destinationNodePropertyArray[n] + if (originFolder.name === destinationFolder.name) { + found = true + } + } + if (found === false) { + destinationFolder = functionLibraryUiObjectsFromNodes.addUIObject(destinationNode, destinationFolderType) + destinationFolder.name = originFolder.name + } + asymetricalFolderStructureCloning( + originFolder, + destinationFolder, + originObjectsArrayPropertyName, + originFoldersArrayPropertyName, + destinationFoldersArrayPropertyName, + clonedObjectType, + destinationFolderType, + originObjectArrayPropertyName, + functionLibraryUiObjectsFromNodes + ) + } + break + } + } + } + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/Hierarchy.js b/Integrations/Projects/Superalgos/Utilities/Hierarchy.js new file mode 100644 index 000000000..d5a2de5a3 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Hierarchy.js @@ -0,0 +1,71 @@ +function newSuperalgosUtilitiesHierarchy() { + thisObject = { + getHiriarchyMap: getHiriarchyMap, + getHiriarchyHead: getHiriarchyHead + } + + return thisObject + + function getHiriarchyMap(rootNode) { + /* + This function scans a node hiriatchy recursively and creates a Map by Id of all the nodes of it. + */ + let hiriatchyMap = new Map() + evalNode(rootNode) + return hiriatchyMap + + function evalNode(node) { + if (node === undefined) { return } + + hiriatchyMap.set(node.id, node) + + /* Now we go down through all this node children */ + let nodeDefinition = getNodeDefinition(node) + if (nodeDefinition === undefined) { return } + + if (nodeDefinition.properties !== undefined) { + let previousPropertyName // Since there are cases where there are many properties with the same name,because they can hold nodes of different types but only one at the time, we have to avoind counting each property of those as individual children. + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + if (property.name !== previousPropertyName) { + if (node[property.name] !== undefined) { + evalNode(node[property.name]) + } + previousPropertyName = property.name + } + break + } + case 'array': { + if (node[property.name] !== undefined) { + let nodePropertyArray = node[property.name] + for (let m = 0; m < nodePropertyArray.length; m++) { + evalNode(nodePropertyArray[m]) + } + } + break + } + } + } + } + } + } + + function getHiriarchyHead(initialNode) { + let headNode + stepBackwards(initialNode) + return headNode + + function stepBackwards(node) { + if (node === undefined) { return } + if (node.payload === undefined) { return } + if (node.payload.parentNode === undefined) { + headNode = node + } else { + stepBackwards(node.payload.parentNode) + } + } + } +} diff --git a/Integrations/Projects/Superalgos/Utilities/LoadSaveFrame.js b/Integrations/Projects/Superalgos/Utilities/LoadSaveFrame.js new file mode 100644 index 000000000..6ee69da82 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/LoadSaveFrame.js @@ -0,0 +1,33 @@ +function newSuperalgosUtilitiesLoadSaveFrame() { + thisObject = { + saveFrame: saveFrame, + loadFrame: loadFrame + } + + return thisObject + + function saveFrame(payload, frame) { + payload.frame = { + position: { + x: frame.position.x, + y: frame.position.y + }, + width: frame.width, + height: frame.height, + radius: frame.radius + } + } + + function loadFrame(payload, frame) { + if (payload.node.savedPayload !== undefined) { + if (payload.node.savedPayload.frame !== undefined) { + frame.position.x = payload.node.savedPayload.frame.position.x + frame.position.y = payload.node.savedPayload.frame.position.y + frame.width = payload.node.savedPayload.frame.width + frame.height = payload.node.savedPayload.frame.height + frame.radius = payload.node.savedPayload.frame.radius + payload.node.savedPayload.frame = undefined + } + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/Menu.js b/Integrations/Projects/Superalgos/Utilities/Menu.js new file mode 100644 index 000000000..74297e468 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Menu.js @@ -0,0 +1,23 @@ +function newSuperalgosUtilitiesMenu() { + thisObject = { + menuClickOfNodeArray: menuClickOfNodeArray, + menuClick: menuClick + } + + return thisObject + + function menuClickOfNodeArray(nodeArray, action, confirm) { + for (let j = 0; j < nodeArray.length; j++) { + let node = nodeArray[j] + menuClick(node, action, confirm) + } + } + + function menuClick(node, action, confirm) { + let menu = node.payload.uiObject.menu + menu.internalClick(action) + if (confirm === true) { + menu.internalClick(action) + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/Meshes.js b/Integrations/Projects/Superalgos/Utilities/Meshes.js new file mode 100644 index 000000000..c466e892c --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Meshes.js @@ -0,0 +1,96 @@ +function newSuperalgosUtilitiesMeshes() { + thisObject = { + findNodeInNodeMesh: findNodeInNodeMesh + } + + return thisObject + + function findNodeInNodeMesh(node, nodeType, nodeName, connectedViaPayload, findInChildren, findInParents, findInReferenceParents) { + /* + This function scans a node mesh for a certain node type and + returns the first instance found. Please specify either nodeType + or nodeName. + */ + const MAX_DEPTH = 100 + let nodeFound + scanNodeMesh(node, 0) + return nodeFound + + function scanNodeMesh(startingNode, depth) { + if (startingNode === undefined) { return } + if (connectedViaPayload === true) { + if (startingNode.payload === undefined) { return } + } + if (nodeFound !== undefined) { return } + if (depth >= MAX_DEPTH) { return } + + let nodeDefinition = getNodeDefinition(startingNode) + if (nodeDefinition === undefined) { return } + + if (nodeType !== undefined) { + if (startingNode.type === nodeType) { + nodeFound = startingNode + return + } + } + + if (nodeName !== undefined) { + if (startingNode.name === nodeName) { + nodeFound = startingNode + return + } + } + + /* We scan through this node children */ + if (findInChildren === true) { + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + scanNodeMesh(startingNode[property.name], depth + 1) + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + scanNodeMesh(startingNodePropertyArray[m], depth + 1) + } + } + break + } + } + } + } + } + + /* We scan parents nodes. */ + if (findInParents === true) { + if (connectedViaPayload === true) { + if (startingNode.payload.parentNode !== undefined) { + scanNodeMesh(startingNode.payload.parentNode, depth + 1) + } + } else { + if (startingNode.parentNode !== undefined) { + scanNodeMesh(startingNode.parentNode, depth + 1) + } + } + } + + /* We scan reference parents too. */ + if (findInReferenceParents === true) { + if (connectedViaPayload === true) { + if (startingNode.payload.referenceParent !== undefined) { + scanNodeMesh(startingNode.payload.referenceParent, depth + 1) + } + } else { + if (startingNode.referenceParent !== undefined) { + scanNodeMesh(startingNode.referenceParent, depth + 1) + } + } + } + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/NodeChildren.js b/Integrations/Projects/Superalgos/Utilities/NodeChildren.js new file mode 100644 index 000000000..2aec22a55 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/NodeChildren.js @@ -0,0 +1,244 @@ +function newSuperalgosUtilitiesNodeChildren() { + thisObject = { + isMissingChildren: isMissingChildren, + findChildReferencingThisNode: findChildReferencingThisNode, + findOrCreateChildWithReference: findOrCreateChildWithReference, + findAndRecreateChildWithReference: findAndRecreateChildWithReference, + findChildIndexAtParentNode: findChildIndexAtParentNode, + isMissingChildrenByName: isMissingChildrenByName + } + + return thisObject + + function isMissingChildren(startingNode, checkNode, checkReferenceParent) { + /* + This functioin scan all the children of a node and returns true or false + depending if the checkNode is a missing children. + */ + if (startingNode === undefined) { return } + if (checkNode === undefined) { return } + + let nodeDefinition = getNodeDefinition(startingNode) + if (nodeDefinition === undefined) { return } + + /* We scan through this node children */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + let child = startingNode[property.name] + if (child === undefined) { continue } + if (checkReferenceParent !== true) { + if (child.id === checkNode.id) { + return false + } + } else { + if (child.payload !== undefined) { + if (child.payload.referenceParent !== undefined) { + if (child.payload.referenceParent.id === checkNode.id) { + return false + } + } + } + } + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + let arrayItem = startingNodePropertyArray[m] + if (checkReferenceParent !== true) { + if (arrayItem.id === checkNode.id) { + return false + } + } else { + if (arrayItem.payload !== undefined) { + if (arrayItem.payload.referenceParent !== undefined) { + if (arrayItem.payload.referenceParent.id === checkNode.id) { + return false + } + } + } + } + } + } + break + } + } + } + return true + } + } + + function findChildReferencingThisNode(startingNode, checkNode) { + /* + This functioin scan all the children of a node and returns the child + that is referencing the checkNode. + */ + if (startingNode === undefined) { return } + if (checkNode === undefined) { return } + + let nodeDefinition = getNodeDefinition(startingNode) + if (nodeDefinition === undefined) { return } + + /* We scan through this node children */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + let child = startingNode[property.name] + + if (child.payload !== undefined) { + if (child.payload.referenceParent !== undefined) { + if (child.payload.referenceParent.id === checkNode.id) { + return child + } + } + } + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + let arrayItem = startingNodePropertyArray[m] + + if (arrayItem.payload !== undefined) { + if (arrayItem.payload.referenceParent !== undefined) { + if (arrayItem.payload.referenceParent.id === checkNode.id) { + return arrayItem + } + } + } + } + } + break + } + } + } + return true + } + } + + function findOrCreateChildWithReference(startingNode, childType, referencedNode, functionLibraryUiObjectsFromNodes) { + /* + This function find the child node of starting node that references + refereced node. If there is none, then it creates a child and stablish + the referecen. + */ + let child + if (isMissingChildren(startingNode, referencedNode, true) === true) { + child = functionLibraryUiObjectsFromNodes.addUIObject(startingNode, childType) + child.payload.referenceParent = referencedNode + } else { + child = findChildReferencingThisNode(startingNode, referencedNode) + } + return child + } + + function findAndRecreateChildWithReference(startingNode, childType, referencedNode, rootNodes, functionLibraryUiObjectsFromNodes, functionLibraryNodeDeleter) { + /* + This function finds the child of the starting node that references + the reference node and if found it deletes it. Existing or not + it creates it again with the child type specified. + */ + let child + child = findChildReferencingThisNode(startingNode, referencedNode) + if (child !== undefined) { + functionLibraryNodeDeleter.deleteUIObject(child, rootNodes) + } + child = functionLibraryUiObjectsFromNodes.addUIObject(startingNode, childType) + child.payload.referenceParent = referencedNode + return child + } + + function findChildIndexAtParentNode(startingNode) { + /* + This functioin scan all the children of a node's parent and returns the child + index at the node property array that contains it. + */ + if (startingNode === undefined) { return } + if (startingNode.payload === undefined) { return } + if (startingNode.payload.parentNode === undefined) { return } + + let parentNode = startingNode.payload.parentNode + + let nodeDefinition = getNodeDefinition(parentNode) + if (nodeDefinition === undefined) { return } + + /* We scan through this parent node children */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + /* We ignore these types of children. */ + } + break + case 'array': { + let startingNodePropertyArray = parentNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + let arrayItem = startingNodePropertyArray[m] + + if (arrayItem.id === startingNode.id) { + return m + } + } + } + break + } + } + } + } + } + + function isMissingChildrenByName(startingNode, nodeName) { + /* + This functioin scan all the children of a node and returns true or false + depending if the node has a children with a certain name. + */ + if (startingNode === undefined) { return } + + let nodeDefinition = getNodeDefinition(startingNode) + if (nodeDefinition === undefined) { return } + + /* We scan through this node children */ + if (nodeDefinition.properties !== undefined) { + for (let i = 0; i < nodeDefinition.properties.length; i++) { + let property = nodeDefinition.properties[i] + + switch (property.type) { + case 'node': { + let child = startingNode[property.name] + if (child !== undefined) { + if (child.name === nodeName) { + return false + } + } + } + break + case 'array': { + let startingNodePropertyArray = startingNode[property.name] + if (startingNodePropertyArray !== undefined) { + for (let m = 0; m < startingNodePropertyArray.length; m++) { + let arrayItem = startingNodePropertyArray[m] + if (arrayItem.name === nodeName) { + return false + } + } + } + break + } + } + } + return true + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/NodeConfig.js b/Integrations/Projects/Superalgos/Utilities/NodeConfig.js new file mode 100644 index 000000000..866a99a8e --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/NodeConfig.js @@ -0,0 +1,27 @@ +function newSuperalgosUtilitiesNodeConfig() { + thisObject = { + savePropertyAtNodeConfig: savePropertyAtNodeConfig, + loadPropertyFromNodeConfig: loadPropertyFromNodeConfig + } + + return thisObject + + function savePropertyAtNodeConfig(payload, propertyName, value) { + try { + let config = JSON.parse(payload.node.config) + config[propertyName] = value + payload.node.config = JSON.stringify(config, null, 4) + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } + + function loadPropertyFromNodeConfig(payload, propertyName) { + try { + let config = JSON.parse(payload.node.config) + return config[propertyName] + } catch (err) { + // we ignore errors here since most likely they will be parsing errors. + } + } +} \ No newline at end of file diff --git a/Integrations/Projects/Superalgos/Utilities/Tutorial.js b/Integrations/Projects/Superalgos/Utilities/Tutorial.js new file mode 100644 index 000000000..a9571eeb4 --- /dev/null +++ b/Integrations/Projects/Superalgos/Utilities/Tutorial.js @@ -0,0 +1,27 @@ +function newSuperalgosUtilitiesTutorial() { + thisObject = { + saveTutorial: saveTutorial, + loadTutorial: loadTutorial + } + + return thisObject + + function saveTutorial(payload, tutorial) { + payload.tutorial = { + status: tutorial.status + } + } + + function loadTutorial(payload, tutorial) { + if (payload.tutorial !== undefined) { + tutorial.status = payload.tutorial.status + return + } + if (payload.node.savedPayload !== undefined) { + if (payload.node.savedPayload.tutorial !== undefined) { + tutorial.status = payload.node.savedPayload.tutorial.status + payload.node.savedPayload.tutorial = undefined + } + } + } +} \ No newline at end of file