diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..eba1110b5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..f1d32b8c8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ + +.vs/* +obj/* +bin/* + +node_modules/* +this.server.config.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..1bcba0f85 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.detectIndentation": false +} \ No newline at end of file diff --git a/Animation.js b/Animation.js new file mode 100644 index 000000000..6278b3f69 --- /dev/null +++ b/Animation.js @@ -0,0 +1,174 @@ + +function newAnimation() { + const MODULE_NAME = 'Animation' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + /* + + Essentially, what the animation object does, is to call several functions in order to draw the content of each frame. + To do that, it has an array of functions that need to be called, and it just goes one by one through them. His parent object + is responsible for providing more functions to call, or removing functions from its list when they are not longer needed. + + */ + + let animationLoopHandle // This is the handle to the animation loop. With this handle we can cancel the loop, for instance. + let callBackFunctions = new Map() + + let thisObject = { + start: start, + stop: stop, + addCallBackFunction: addCallBackFunction, + removeCallBackFunction: removeCallBackFunction, + initialize: initialize, + finalize: finalize + } + + let totalConsumption = 0 + let totalCounter = 0 + let pointerIcon + let videoRecorder + return thisObject + + function finalize() { + thisObject.stop() + pointerIcon = undefined + videoRecorder = undefined + } + + function initialize() { + videoRecorder = newVideoRecorder() + } + + function start() { + animationLoop() // Inside this function the animation process is started, and at the same time it creates a loop. + } + + function stop() { + window.cancelAnimationFrame(animationLoopHandle) + } + + function addCallBackFunction(key, callBack) { + callBackFunctions.set(key, callBack) + } + + function removeCallBackFunction(key) { + callBackFunctions.delete(key) + } + + function animationLoop() { + try { + /* First thing is to clear the actual canvas */ + clearBrowserCanvas() + + /* We loop through the callback functions collections and execute them all. */ + let performanceMap = new Map() + let totalTimeConsumed = 0 + let totalElements = 0 + let chanceToExecute + let randomNumber + let mustExecute + + // console.clear() + let row = 0 + for (const [key, callBackFunction] of callBackFunctions.entries()) { + row++ + switch (key) { + case 'Floating Space Physics': + chanceToExecute = 50 + randomNumber = Math.random() * 100 * chanceToExecute + if (randomNumber > 100 - chanceToExecute) { mustExecute = true } else { mustExecute = false } + break + default: { + mustExecute = true + } + + } + let timeConsumed = 0 + if (mustExecute === true) { + let t0 = performance.now() + callBackFunction() + let t1 = performance.now() + timeConsumed = t1 - t0 + if (key === 'Charting Space Draw') { + if (Math.random() * 100 > 99) { + totalConsumption = 0 + totalCounter = 0 + } + + totalConsumption = totalConsumption + timeConsumed + totalCounter = totalCounter + 1 + } + performanceMap.set(key, timeConsumed) + totalTimeConsumed = totalTimeConsumed + timeConsumed + totalElements++ + } + } + + /* Frame per Seconds */ + + ANIMATION_FRAME_PER_SECONDS = Math.trunc(1000 / totalTimeConsumed) + + /* Performance Check */ + if (SHOW_ANIMATION_PERFORMACE === true) { + row = 0 + for (const [key, timeConsumed] of performanceMap.entries()) { + row++ + let labelToPrint = key + ' ' + timeConsumed.toFixed(4) + UI.projects.superalgos.utilities.drawPrint.printLabel(labelToPrint, 10, 100 + row * 30, 1, 20, UI_COLOR.RED) + let percentage = timeConsumed * 100 / totalTimeConsumed + labelToPrint = key + ' ' + percentage.toFixed(1) + '%' + UI.projects.superalgos.utilities.drawPrint.printLabel(labelToPrint, 300, 100 + row * 30, 1, 20, UI_COLOR.RED) + } + + /* Other Variables */ + row++ + UI.projects.superalgos.utilities.drawPrint.printLabel(DEBUG.variable1, 300, 100 + row * 30, 1, 20, UI_COLOR.RED) + row++ + UI.projects.superalgos.utilities.drawPrint.printLabel(DEBUG.variable2, 300, 100 + row * 30, 1, 20, UI_COLOR.RED) + row++ + UI.projects.superalgos.utilities.drawPrint.printLabel(DEBUG.variable3, 300, 100 + row * 30, 1, 20, UI_COLOR.RED) + row++ + UI.projects.superalgos.utilities.drawPrint.printLabel(DEBUG.variable4, 300, 100 + row * 30, 1, 20, UI_COLOR.RED) + row++ + UI.projects.superalgos.utilities.drawPrint.printLabel('Animation Frame Per Seconds: ' + ANIMATION_FRAME_PER_SECONDS, 300, 100 + row * 30, 1, 20, UI_COLOR.RED) + } + + /* Video Recording */ + videoRecorder.physics() + if (ARE_WE_RECORDING_A_VIDEO === true) { + videoRecorder.recordCanvas() + } + + /* Panorama Recording */ + if (ARE_WE_RECORDING_A_MARKET_PANORAMA === true) { + if (PANORAMA_WAS_PANNED === true) { + UI.projects.superalgos.utilities.download.addToMarketPanorama() + PANORAMA_WAS_PANNED = false + } + } + + /* We request the next frame to be drawn, and stablishing a loop */ + + /* Use this code for full animation speed. */ + animationLoopHandle = window.requestAnimationFrame(animationLoop) + + /* Use this code for max 10 frames per second animation speed, if the app is consumming too much of your CPU. */ + /* + setTimeout(nextLoop, 100) + function nextLoop () { + animationLoopHandle = window.requestAnimationFrame(animationLoop) + } + */ + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] animationLoop -> err = ' + err.stack) } + } + } + + function clearBrowserCanvas() { + if (CAN_SPACES_DRAW === false) { return } + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.clearRect(0, 0, browserCanvas.width, browserCanvas.height) + } +} diff --git a/AppLoader.js b/AppLoader.js new file mode 100644 index 000000000..904f7aab1 --- /dev/null +++ b/AppLoader.js @@ -0,0 +1,190 @@ +let postLoader + +function newAppLoader() { + const MODULE_NAME = 'App Loader' + const INFO_LOG = false + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + loadModules: loadModules + } + + return thisObject + + async function loadModules() { + try { + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> Entering function.') } + + let plotters + let modulesArray = [] + + plotters = defaultPlotters() + + modulesArray = modulesArray.concat([ + + 'Globals.js', + 'Workspace.js', + + 'Files/SingleFile.js', + 'Files/FileCloud.js', + 'Files/MarketFiles.js', + 'Files/DailyFiles.js', + 'Files/FileCursor.js', + 'Files/FileSequence.js', + 'Files/FileStorage.js', + + 'EventsServerClient.js', + + 'LegacyPlotter.js', + 'PlotterPanel.js', + + 'VideoRecorder.js', + 'ProductStorage.js', + 'Canvas.js', + 'EventHandler.js', + 'Frame.js', + 'Animation.js', + 'Container.js', + 'Utilities.js', + 'AppPostLoader.js' + ]) + + modulesArray = modulesArray.concat(plotters) + + functionLibraries() + + function functionLibraries() { + let url = 'ListFunctionLibraries' + callWebServer(undefined, url, onResponse) + + function onResponse(err, fileList) { + let urlArray = [] + let fileArray = JSON.parse(fileList) + for (let i = 0; i < fileArray.length; i++) { + let item = fileArray[i] + + project = item[0] + fileName = item[1] + urlArray.push('Projects' + '/' + project + '/' + 'Function-Libraries' + '/' + fileName) + } + + modulesArray = modulesArray.concat(urlArray) + utilities() + } + } + + function utilities() { + let url = 'ListUtilitiesFiles' + callWebServer(undefined, url, onResponse) + + function onResponse(err, fileList) { + let urlArray = [] + let fileArray = JSON.parse(fileList) + for (let i = 0; i < fileArray.length; i++) { + let item = fileArray[i] + + project = item[0] + fileName = item[1] + urlArray.push('Projects' + '/' + project + '/' + 'Utilities' + '/' + fileName) + } + + modulesArray = modulesArray.concat(urlArray) + globals() + } + } + + function globals() { + let url = 'ListGlobalFiles' + callWebServer(undefined, url, onResponse) + + function onResponse(err, fileList) { + let urlArray = [] + let fileArray = JSON.parse(fileList) + for (let i = 0; i < fileArray.length; i++) { + let item = fileArray[i] + + project = item[0] + fileName = item[1] + urlArray.push('Projects' + '/' + project + '/' + 'Globals' + '/' + fileName) + } + + modulesArray = modulesArray.concat(urlArray) + spaces() + } + } + + function spaces() { + let url = 'ListSpaceFiles' + callWebServer(undefined, url, onResponse) + + function onResponse(err, fileList) { + let urlArray = [] + let fileArray = JSON.parse(fileList) + for (let i = 0; i < fileArray.length; i++) { + let path = fileArray[i] + urlArray.push('Projects' + '/' + path) + } + + modulesArray = modulesArray.concat(urlArray) + downloadIncludedFiles() + } + } + + function downloadIncludedFiles() { + + let downloadedCounter = 0 + + for (let i = 0; i < modulesArray.length; i++) { + let path = modulesArray[i] + + REQUIREJS([path], onRequired) + + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> Module Requested.') } + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> path = ' + path) } + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> total requested = ' + (i + 1)) } + + function onRequired(pModule) { + try { + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> onRequired -> Entering function.') } + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> onRequired -> Module Downloaded.') } + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> onRequired -> path = ' + path) } + + downloadedCounter++ + + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> onRequired -> downloadedCounter = ' + downloadedCounter) } + + if (downloadedCounter === modulesArray.length) { + if (INFO_LOG === true) { logger.write('[INFO] loadModules -> onRequired -> Starting Advanced Algos Platform.') } + setTimeout(() => { + postLoader = newAppPostLoader() + postLoader.start() + }, 500) + + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] loadModules -> onRequired -> err = ' + err.stack) } + } + } + } + } + + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] loadModules -> err = ' + err.stack) } + } + } + + function defaultPlotters() { + return [ + 'Plotters/Masters/Plotters-Candles-Volumes/Candles.js', + 'Plotters/Masters/Plotters-Candles-Volumes/CandlePanel.js', + 'Plotters/Masters/Plotters-Candles-Volumes/Volumes.js', + 'Plotters/Masters/Plotters-Candles-Volumes/VolumePanel.js', + 'Plotters/Masters/Plotters-Bollinger-Bands/BollingerBands.js', + 'Plotters/Masters/Plotters-Bollinger-Bands/BollingerBandsPanel.js', + 'Plotters/Masters/Plotters-Bollinger-Bands/PercentageBandwidth.js' + ] + } +} + diff --git a/AppPostLoader.js b/AppPostLoader.js new file mode 100644 index 000000000..cfb5488db --- /dev/null +++ b/AppPostLoader.js @@ -0,0 +1,147 @@ + + +function newAppPostLoader() { + const MODULE_NAME = 'Post Loader' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + start: start + } + + return thisObject + + function start() { + try { + /* Moving PROJECTS_ARRAY to the global variable */ + PROJECTS_ARRAY = window.PROJECTS + window.PROJECTS = undefined + + setBrowserEvents() + setupProjectsSchema() + + function setupProjectsSchema() { + callWebServer(undefined, 'ProjectsSchema', onResponse) + + function onResponse(err, file) { + PROJECTS_SCHEMA = JSON.parse(file) + setupSchemas() + } + } + + function setupSchemas() { + + let totalWebServerCalls = 0 + let webServerResponses = 0 + + for (let i = 0; i < PROJECTS_ARRAY.length; i++) { + let project = PROJECTS_ARRAY[i] + let schemas = { + array: { + appSchema: [], + docSchema: [], + conceptSchema: [] + }, + map: { + appSchema: new Map(), + docSchema: new Map(), + conceptSchema: new Map() + } + } + SCHEMAS_BY_PROJECT.set(project, schemas) + + totalWebServerCalls++ + callWebServer(undefined, 'Schema/' + project + '/AppSchema', onResponseAppSchema) + + function onResponseAppSchema(err, schema) { + try { + schemas.array.appSchema = JSON.parse(schema) + + for (let j = 0; j < schemas.array.appSchema.length; j++) { + let nodeDefinition = schemas.array.appSchema[j] + let key = nodeDefinition.type + schemas.map.appSchema.set(key, nodeDefinition) + } + } catch (err) { + console.log(err.stack) + } + + webServerResponses++ + if (webServerResponses === totalWebServerCalls) { startCanvas() } + } + + totalWebServerCalls++ + callWebServer(undefined, 'Schema/' + project + '/DocSchema', onResponseDocSchema) + + function onResponseDocSchema(err, schema) { + try { + schemas.array.docSchema = JSON.parse(schema) + + for (let j = 0; j < schemas.array.docSchema.length; j++) { + let nodeDefinition = schemas.array.docSchema[j] + let key = nodeDefinition.type + schemas.map.docSchema.set(key, nodeDefinition) + } + } catch (err) { + console.log(err.stack) + } + + webServerResponses++ + if (webServerResponses === totalWebServerCalls) { startCanvas() } + } + + totalWebServerCalls++ + callWebServer(undefined, 'Schema/' + project + '/ConceptSchema', onResponseConceptSchema) + + function onResponseConceptSchema(err, schema) { + try { + schemas.array.conceptSchema = JSON.parse(schema) + + for (let j = 0; j < schemas.array.conceptSchema.length; j++) { + let nodeDefinition = schemas.array.conceptSchema[j] + let key = nodeDefinition.type + schemas.map.conceptSchema.set(key, nodeDefinition) + } + } catch (err) { + console.log(err.stack) + } + + webServerResponses++ + if (webServerResponses === totalWebServerCalls) { startCanvas() } + } + } + } + + function startCanvas() { + /* If this method is executed for a second time, it should finalize the current execution structure */ + if (canvas !== undefined) { canvas.finalize() } + + canvas = newCanvas() + canvas.initialize() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] start -> err = ' + err.stack) } + } + } + + function setBrowserEvents() { + window.onbeforeunload = saveWorkspace + + /* handles backspace and refresh(F5) from keyboard */ + window.manageBackRefresh = function (event) { + var tag = event.target.tagName.toLowerCase() + if (event.keyCode === 8 && tag !== 'input' && tag !== 'textarea') { // Backbutton pressed + saveWorkspace() + } else if (event.keyCode === 116) { // F5 pressed + saveWorkspace() + } + } + + window.addEventListener('keydown', window.manageBackRefresh) + + function saveWorkspace() { + UI.projects.superalgos.spaces.designSpace.workspace.save() + } + } +} diff --git a/AppPreLoader.js b/AppPreLoader.js new file mode 100644 index 000000000..2e9df25c5 --- /dev/null +++ b/AppPreLoader.js @@ -0,0 +1,158 @@ + +/* Callbacks default responses. */ + +let GLOBAL = {}; + +GLOBAL.DEFAULT_OK_RESPONSE = { + result: "Ok", + message: "Operation Succeeded" +}; + +GLOBAL.DEFAULT_FAIL_RESPONSE = { + result: "Fail", + message: "Operation Failed" +}; + +GLOBAL.DEFAULT_RETRY_RESPONSE = { + result: "Retry", + message: "Retry Later" +}; + +GLOBAL.CUSTOM_OK_RESPONSE = { + result: "Ok, but check Message", + message: "Custom Message" +}; + +GLOBAL.CUSTOM_FAIL_RESPONSE = { + result: "Fail Because", + message: "Custom Message" +}; + +let browserCanvas; // This is the canvas object of the browser. + +function spacePad(str, max) { + str = str.toString(); + return str.length < max ? spacePad(" " + str, max) : str; +} + +function loadSuperalgos() { + + const MODULE_NAME = "App Pre-Loader"; + const INFO_LOG = false; + + setupHTMLTextArea() + setupHTMLInput() + setupHTMLCanvas() + loadDebugModule() + + function setupHTMLTextArea() { + let textArea = document.createElement('textarea'); + textArea.id = "textArea"; + textArea.spellcheck = false; + textArea.style = 'resize: none;' + + ' border: none;' + + ' outline: none;' + + 'box-shadow: none;' + + 'overflow:hidden;' + + 'font-family: ' + 'Saira' + ';' + + 'font-size: 12px;' + + 'background-color: rgb(255, 255, 255);' + + 'color:rgb(255, 255, 255);' + + 'width: ' + 600 + 'px;' + + 'height: ' + 400 + 'px' + + let textAreaDiv = document.getElementById('textAreaDiv'); + textAreaDiv.appendChild(textArea); + textAreaDiv.style = 'position:fixed; top:' + -1500 + 'px; left:' + 500 + 'px; z-index:10; ' + } + + function setupHTMLInput() { + let input = document.createElement('input'); + input.id = "input"; + input.spellcheck = false; + input.style = "border: none; outline: none; box-shadow: none; overflow:hidden; width: 0px; height: 0px;"; + + let inputDiv = document.getElementById('inputDiv'); + inputDiv.appendChild(input); + } + + function setupHTMLCanvas() { + let canvas = document.createElement('canvas'); + + canvas.id = "canvas"; + canvas.width = 1400; + canvas.height = 600; + canvas.style.border = "0"; + canvas.style = "position:absolute; top:0px; left:0px; z-index:1"; + + let canvasApp = document.getElementById('canvasApp'); + canvasApp.appendChild(canvas); + + browserCanvas = document.getElementById('canvas'); + + browserCanvas.width = window.innerWidth; + browserCanvas.height = window.innerHeight; + browserCanvas.style.border = "none"; + + browserCanvas.style.top = 0 + 'px'; + } + + function loadDebugModule() { + let path = "WebDebugLog.js"; + REQUIREJS([path], onRequired); + + function onRequired(pModule) { + if (INFO_LOG === true) { console.log(spacePad(MODULE_NAME, 50) + " : " + "[INFO] " + path + " downloaded."); } + setUpProjects(); + } + } + + function setUpProjects() { + let url = 'ProjectNames' + callWebServer(undefined, url, onResponse) + + function onResponse(err, projects) { + window.PROJECTS = JSON.parse(projects) + loadModules() + } + } + + /* And Finally, we start loading all the scripts we will inmediatelly need. */ + function loadModules() { + let path = "AppLoader.js"; + REQUIREJS([path], onRequired); + + function onRequired(pModule) { + if (INFO_LOG === true) { console.log(spacePad(MODULE_NAME, 50) + " : " + "[INFO] " + path + " downloaded."); } + let APP_LOADER_MODULE = newAppLoader(); + APP_LOADER_MODULE.loadModules(); + } + } +} + +function callWebServer(pContentToSend, pPath, callBackFunction) { + let xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function () { + if (this.readyState === 4 && this.status === 200) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE, xhttp.responseText); + return; + } else if (this.readyState === 4 && this.status === 404) { + callBackFunction({ result: "Fail", message: xhttp.responseText.trim(), code: xhttp.responseText.trim() }); + return; + } + }; + + if (pContentToSend === undefined) { + xhttp.open("GET", pPath, true); + xhttp.send(); + } else { + try { + let blob = new Blob([pContentToSend], { type: 'text/plain' }); + xhttp.open("POST", pPath, true); + xhttp.send(blob); + } catch (err) { + if (ERROR_LOG === true) { console.log(spacePad(MODULE_NAME, 50) + " : " + "[ERROR] callServer -> err.message = " & err.message); } + callBackFunction({ result: "Fail", message: err.message }) + } + } +} diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..4b37b0e16 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team using the online form in the contacts page of the superalgos.org website. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..1d1736260 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,9 @@ +# Contributing + +We are thrilled you are interested in contributing to this repository. Your help may be essential to the project's success and we want you in! + +Superalgos is a collective project, currently at a very early stage. We not only welcome contributions but may even choose to incentivize relevant contributions via the project's native crypto token. + +Please read the [Join the Dev Team]( https://superalgos.org/developers-join-dev-team.shtml) page in our website to learn more about how you may become a relevant member of the Community and even make a living out of contributing code to the project. + +Contributions are deemed _voluntary work_, irrespective of the incentives that may be in place, and are subject to the conditions described in the LICENSE \ No newline at end of file diff --git a/Canvas.js b/Canvas.js new file mode 100644 index 000000000..d39333ada --- /dev/null +++ b/Canvas.js @@ -0,0 +1,1082 @@ + +let browserCanvasContext // The context of the canvas object. + +function newCanvas() { + const MODULE_NAME = 'Canvas' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + /* Mouse event related variables. */ + + let containerDragStarted = false + let floatingObjectDragStarted = false + let containerBeingDragged + let viewPortBeingDragged = false + let ignoreNextClick = false + + let dragVector = { + downX: 0, + downY: 0, + upX: 0, + upY: 0 + } + + /* canvas object */ + + let thisObject = { + eventHandler: undefined, + animation: undefined, + mouse: undefined, + shorcutNumbers: new Map(), + onKeyDown: onKeyDown, + initialize: initialize, + finalize: finalize + } + + thisObject.eventHandler = newEventHandler() + + let lastContainerMouseOver + + thisObject.mouse = { + position: { + x: 0, + y: 0 + }, + action: '' + } + + return thisObject + + function finalize() { + try { + UI.projects.superalgos.spaces.tutorialSpace.finalize() + UI.projects.superalgos.spaces.docSpace.finalize() + //thisObject.chatSpace.finalize() + UI.projects.superalgos.spaces.sideSpace.finalize() + UI.projects.superalgos.spaces.chartingSpace.finalize() + UI.projects.superalgos.spaces.floatingSpace.finalize() + thisObject.shorcutNumbers = undefined + + if (browserCanvas.removeEventListener) { + browserCanvas.removeEventListener('mousewheel', onMouseWheel, false)// IE9, Chrome, Safari, Opera + browserCanvas.removeEventListener('DOMMouseScroll', onMouseWheel, false) // Firefox + browserCanvas.removeEventListener('mousedown', onMouseDown, false) + browserCanvas.removeEventListener('mouseup', onMouseUp, false) + browserCanvas.removeEventListener('mousemove', onMouseMove, false) + browserCanvas.removeEventListener('click', onMouseClick, false) + browserCanvas.removeEventListener('mouseout', onMouseOut, false) + + browserCanvas.removeEventListener('dragenter', onDragEnter, false) + browserCanvas.removeEventListener('dragleave', onDragLeave, false) + browserCanvas.removeEventListener('dragover', onDragOver, false) + browserCanvas.removeEventListener('drop', onDragDrop, false) + + browserCanvas.removeEventListener('keydown', onKeyDown, false) + browserCanvas.removeEventListener('keyup', onKeyUp, false) + } else { + browserCanvas.detachEvent('onmousewheel', onMouseWheel) // IE 6/7/8 + } + + lastContainerMouseOver = undefined + + thisObject.mouse = undefined + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] finalize -> err = ' + err.stack) } + } + } + + function initialize() { + try { + + browserResized() + initializeBrowserCanvas() + addCanvasEvents() + + thisObject.animation = newAnimation() + thisObject.animation.initialize() + + /* + Here we will setup the UI object, with all the + projects and spaces. + */ + for (let i = 0; i < PROJECTS_SCHEMA.length; i++) { + let projectDefinition = PROJECTS_SCHEMA[i] + UI.projects[projectDefinition.propertyName] = {} + let projectInstance = UI.projects[projectDefinition.propertyName] + projectInstance.spaces = {} + projectInstance.events = {} + projectInstance.utilities = {} + projectInstance.globals = {} + + projectInstance.events.onMouseWheelMap = new Map() + projectInstance.events.onMouseOverMap = new Map() + projectInstance.events.onMouseClickMap = new Map() + projectInstance.events.onMouseDownMap = new Map() + + let spaceInitializationMap = new Map() + let spaceAnimationPhysicsMap = new Map() + let spaceDefinitionPhysicsMap = new Map() + let spaceAnimationDrawMap = new Map() + let spaceDefinitionDrawMap = new Map() + + /* Space Instantiation */ + if (projectDefinition.spaces === undefined) { continue } + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceDefinition = projectDefinition.spaces[j] + projectInstance.spaces[spaceDefinition.propertyName] = eval(spaceDefinition.functionName + '()') + let spaceInstance = projectInstance.spaces[spaceDefinition.propertyName] + spaceInstance.definition = spaceDefinition + + if (spaceDefinition.initializationIndex !== undefined) { + spaceInitializationMap.set(spaceDefinition.initializationIndex, spaceInstance) + } + + if (spaceDefinition.animationPhysicsIndex !== undefined) { + spaceAnimationPhysicsMap.set(spaceDefinition.animationPhysicsIndex, spaceInstance) + spaceDefinitionPhysicsMap.set(spaceDefinition.animationPhysicsIndex, spaceDefinition) + } + + if (spaceDefinition.animationDrawIndex !== undefined) { + spaceAnimationDrawMap.set(spaceDefinition.animationDrawIndex, spaceInstance) + spaceDefinitionDrawMap.set(spaceDefinition.animationDrawIndex, spaceDefinition) + } + + /* Build the maps that defines the secuence of execution of different events. */ + if (spaceDefinition.onMouseWheelIndex !== undefined) { + projectInstance.events.onMouseWheelMap.set(spaceDefinition.onMouseWheelIndex, spaceInstance) + } + if (spaceDefinition.onMouseOverIndex !== undefined) { + projectInstance.events.onMouseOverMap.set(spaceDefinition.onMouseOverIndex, spaceInstance) + } + if (spaceDefinition.onMouseOverIndex !== undefined) { + projectInstance.events.onMouseClickMap.set(spaceDefinition.onMouseClickIndex, spaceInstance) + } + if (spaceDefinition.onMouseDownIndex !== undefined) { + projectInstance.events.onMouseDownMap.set(spaceDefinition.onMouseDownIndex, spaceInstance) + } + } + + /* Space Initialization */ + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = spaceInitializationMap.get(j) + if (spaceInstance !== undefined) { + spaceInstance.initialize() + } + } + + /* Space Animation Physics */ + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = spaceAnimationPhysicsMap.get(j) + let spaceDefinition = spaceDefinitionPhysicsMap.get(j) + if (spaceInstance === undefined || spaceDefinition === undefined) { continue } + if (spaceInstance.physics !== undefined) { + thisObject.animation.addCallBackFunction(spaceDefinition.name + ' ' + 'Physics', spaceInstance.physics) + } + } + + /* Space Animation Drawing*/ + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = spaceAnimationDrawMap.get(j) + let spaceDefinition = spaceDefinitionDrawMap.get(j) + if (spaceInstance === undefined || spaceDefinition === undefined) { continue } + if (spaceInstance.draw !== undefined) { + thisObject.animation.addCallBackFunction(spaceDefinition.name + ' ' + 'Draw', spaceInstance.draw) + } + } + + /* Set up Utilities of this Project */ + if (projectDefinition.utilities === undefined) { continue } + for (let j = 0; j < projectDefinition.utilities.length; j++) { + let utilityDefinition = projectDefinition.utilities[j] + + projectInstance.utilities[utilityDefinition.propertyName] = eval(utilityDefinition.functionName + '()') + } + + /* Set up Globals of this Project */ + if (projectDefinition.globals === undefined) { continue } + for (let j = 0; j < projectDefinition.globals.length; j++) { + let globalDefinition = projectDefinition.globals[j] + + projectInstance.globals[globalDefinition.propertyName] = eval(globalDefinition.functionName + '()') + } + } + thisObject.animation.start() + + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = ' + err.stack) } + } + } + + function initializeBrowserCanvas() { + try { + browserCanvasContext = browserCanvas.getContext('2d') + browserCanvasContext.font = 'Saira Condensed' + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initializeBrowserCanvas -> err = ' + err.stack) } + } + } + + function addCanvasEvents() { + try { + /* Keyboard events */ + window.addEventListener('keydown', onKeyDown, true) + window.addEventListener('keyup', onKeyUp, true) + + if (browserCanvas.addEventListener) { + canvas.eventHandler.listenToEvent('Browser Resized', browserResized) + + /* Mouse Events */ + browserCanvas.addEventListener('mousedown', onMouseDown, false) + browserCanvas.addEventListener('mouseup', onMouseUp, false) + browserCanvas.addEventListener('mousemove', onMouseMove, false) + browserCanvas.addEventListener('click', onMouseClick, false) + browserCanvas.addEventListener('mouseout', onMouseOut, false) + + browserCanvas.addEventListener('mousewheel', onMouseWheel, false) // IE9, Chrome, Safari, Opera + browserCanvas.addEventListener('DOMMouseScroll', onMouseWheel, false) // Firefox + + /* Dragging Files Over the Canvas */ + browserCanvas.addEventListener('dragenter', onDragEnter, false) + browserCanvas.addEventListener('dragleave', onDragLeave, false) + browserCanvas.addEventListener('dragover', onDragOver, false) + browserCanvas.addEventListener('drop', onDragDrop, false) + + } else browserCanvas.attachEvent('onmousewheel', onMouseWheel)// IE 6/7/8 + + // Disables the context menu when you right mouse click the canvas. + browserCanvas.oncontextmenu = function (e) { + e.preventDefault() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] addCanvasEvents -> err = ' + err.stack) } + } + } + + function browserResized() { + browserCanvas = document.getElementById('canvas') + + browserCanvas.width = window.innerWidth + browserCanvas.height = window.innerHeight - CURRENT_TOP_MARGIN + browserCanvas.style = "position:absolute; top:0px; left:0px; z-index:1" + } + + function checkMediaRecording(event) { + if ((event.ctrlKey === true || event.metaKey === true)) { + let constructorParams + switch (event.keyCode) { + case 113: { // F2 + browserCanvas.width = 400 * 3 + browserCanvas.height = 580 * 3 + centerCanvas() + DISABLE_BROWSER_RESIZE_EVENT = true + return + break + } + case 114: { // F2 + browserCanvas.width = 400 * 2 + browserCanvas.height = 580 * 2 + centerCanvas() + DISABLE_BROWSER_RESIZE_EVENT = true + return + break + } + case 115: { // F3 + browserCanvas.width = 400 * 1.5 + browserCanvas.height = 580 * 1.5 + centerCanvas() + DISABLE_BROWSER_RESIZE_EVENT = true + return + break + } + case 116: { // F5 + browserCanvas.width = 400 * 1 + browserCanvas.height = 580 * 1 + centerCanvas() + DISABLE_BROWSER_RESIZE_EVENT = true + return + break + } + case 117: { // F6 + browserCanvas.width = window.innerWidth + browserCanvas.height = window.innerHeight - CURRENT_TOP_MARGIN + centerCanvas() + DISABLE_BROWSER_RESIZE_EVENT = false + return + break + } + case 118: { // F7 + UI.projects.superalgos.utilities.download.downloadPanorama('Superalgos.Market.Panorama') + return + break + } + case 119: { // F8 + UI.projects.superalgos.utilities.download.downloadCanvas('Superalgos.Image.Capture', browserCanvas) + return + break + } + case 120: { // F9 + console.log('Recording gid with framerate = ' + 24) + constructorParams = { format: 'gif', workersPath: 'externalScripts/', framerate: 24, name: 'Superalgos.Video.Capture' } + break + } + case 121: { // F10 + console.log('Recording gid with framerate = ' + 24) + constructorParams = { format: 'webm', framerate: 24, name: 'Superalgos.Video.Capture' } + break + } + default: return + } + + if (ARE_WE_RECORDING_A_VIDEO === false) { + console.log('RECORDING', constructorParams.format) + mediaRecorder = new CCapture(constructorParams) + mediaRecorder.start() + ARE_WE_RECORDING_A_VIDEO = true + } else { + console.log('SAVING', constructorParams.format) + ARE_WE_RECORDING_A_VIDEO = false + mediaRecorder.stop() + mediaRecorder.save() + } + } + + function centerCanvas() { + return + let top = (window.innerHeight - browserCanvas.height) / 2 + let left = (window.innerWidth - browserCanvas.width) / 2 + browserCanvas.style = "position:absolute; top:" + top + "px; left:" + left + "px; z-index:1" + } + } + + function onKeyUp(event) { + thisObject.mouse.event = event + thisObject.mouse.action = 'key up' + } + + async function onKeyDown(event) { + if (EDITOR_ON_FOCUS === true) { return } + thisObject.mouse.event = event + thisObject.mouse.action = 'key down' + + checkMediaRecording(event) + UI.projects.superalgos.spaces.chartingSpace.onKeyPressed(event) + + /* Shourcuts to Menu Items */ + if ((event.keyCode >= 48 && event.keyCode <= 57)) { + let number = event.key + if (MENU_ITEM_ON_FOCUS !== undefined) { + let menuItem = thisObject.shorcutNumbers.get(number) + if (menuItem !== undefined) { + menuItem.shorcutNumber = undefined + } + thisObject.shorcutNumbers.set(number, MENU_ITEM_ON_FOCUS) + MENU_ITEM_ON_FOCUS.shorcutNumber = number + } else { + let menuItem = thisObject.shorcutNumbers.get(number) + if (menuItem !== undefined) { + menuItem.internalClick() + } + } + } + + if (event.key === 'Escape' && UI.projects.superalgos.spaces.floatingSpace.inMapMode === true) { + UI.projects.superalgos.spaces.floatingSpace.exitMapMode() + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && (event.key === 'M' || event.key === 'm')) { + UI.projects.superalgos.spaces.floatingSpace.toggleMapMode() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && (event.key === 'R' || event.key === 'r')) { + UI.projects.superalgos.spaces.floatingSpace.toggleDrawReferenceLines() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && (event.key === 'C' || event.key === 'c')) { + UI.projects.superalgos.spaces.floatingSpace.toggleDrawChainLines() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && (event.key === 'S' || event.key === 's')) { + UI.projects.superalgos.spaces.designSpace.workspace.save() + if (event.preventDefault !== undefined) { + event.preventDefault() + } + return + } + + let nodeOnFocus = await UI.projects.superalgos.spaces.designSpace.workspace.getNodeThatIsOnFocus() + if (nodeOnFocus !== undefined) { + if (nodeOnFocus.payload.uiObject.codeEditor !== undefined) { + if (nodeOnFocus.payload.uiObject.codeEditor.visible === true) { + return + } + } + if (nodeOnFocus.payload.uiObject.configEditor !== undefined) { + if (nodeOnFocus.payload.uiObject.configEditor.visible === true) { + return + } + } + if (nodeOnFocus.payload.uiObject.formulaEditor !== undefined) { + if (nodeOnFocus.payload.uiObject.formulaEditor.visible === true) { + return + } + } + if (nodeOnFocus.payload.uiObject.conditionEditor !== undefined) { + if (nodeOnFocus.payload.uiObject.conditionEditor.visible === true) { + return + } + } + if (nodeOnFocus.payload.uiObject.uiObjectTitle !== undefined) { + if (nodeOnFocus.payload.uiObject.uiObjectTitle.editMode === true) { + return + } + } + } + + if ((event.ctrlKey === true || event.metaKey === true) && event.altKey === true && event.shiftKey === true && event.keyCode === 123) { // Dev Tool when used with F12 + if (nodeOnFocus !== undefined) { + console.log(nodeOnFocus) + return + } + } + + if ((event.ctrlKey === true || event.metaKey === true) && event.altKey === true && event.shiftKey === true && event.keyCode === 122) { // Dev Tool when used with F11 + if (SHOW_ANIMATION_PERFORMACE === false) { + SHOW_ANIMATION_PERFORMACE = true + } else { + SHOW_ANIMATION_PERFORMACE = false + } + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && event.code === 'ArrowUp') { + UI.projects.superalgos.spaces.cockpitSpace.toTop() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && event.code === 'ArrowDown') { + UI.projects.superalgos.spaces.cockpitSpace.toBottom() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && event.code === 'ArrowLeft') { + UI.projects.superalgos.spaces.cockpitSpace.moveUp() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === true || event.metaKey === true) && event.code === 'ArrowRight') { + UI.projects.superalgos.spaces.cockpitSpace.moveDown() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === false && event.metaKey === false) && event.code === 'ArrowLeft') { + UI.projects.superalgos.spaces.chartingSpace.oneScreenLeft() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === false && event.metaKey === false) && event.code === 'ArrowRight') { + UI.projects.superalgos.spaces.chartingSpace.oneScreenRight() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === false && event.metaKey === false) && event.code === 'ArrowUp') { + UI.projects.superalgos.spaces.chartingSpace.oneScreenUp() + event.preventDefault() + return + } + + if (event.shiftKey === true && (event.ctrlKey === false && event.metaKey === false) && event.code === 'ArrowDown') { + UI.projects.superalgos.spaces.chartingSpace.oneScreenDown() + event.preventDefault() + return + } + + if ((event.ctrlKey === true || event.metaKey === true) && event.shiftKey === false && event.code === 'ArrowLeft') { + let displaceVector = UI.projects.superalgos.spaces.floatingSpace.oneScreenLeft() + if (displaceVector !== undefined) { + dragVector.downX = dragVector.downX + displaceVector.x + dragVector.downY = dragVector.downY + displaceVector.y + checkDrag() + } + event.preventDefault() + return + } + + if ((event.ctrlKey === true || event.metaKey === true) && event.shiftKey === false && event.code === 'ArrowRight') { + let displaceVector = UI.projects.superalgos.spaces.floatingSpace.oneScreenRight() + if (displaceVector !== undefined) { + dragVector.downX = dragVector.downX + displaceVector.x + dragVector.downY = dragVector.downY + displaceVector.y + checkDrag() + } + event.preventDefault() + return + } + + if ((event.ctrlKey === true || event.metaKey === true) && event.shiftKey === false && event.code === 'ArrowUp') { + let displaceVector = UI.projects.superalgos.spaces.floatingSpace.oneScreenUp() + if (displaceVector !== undefined) { + dragVector.downX = dragVector.downX + displaceVector.x + dragVector.downY = dragVector.downY + displaceVector.y + checkDrag() + } + event.preventDefault() + return + } + + if ((event.ctrlKey === true || event.metaKey === true) && event.shiftKey === false && event.code === 'ArrowDown') { + let displaceVector = UI.projects.superalgos.spaces.floatingSpace.oneScreenDown() + if (displaceVector !== undefined) { + dragVector.downX = dragVector.downX + displaceVector.x + dragVector.downY = dragVector.downY + displaceVector.y + checkDrag() + } + event.preventDefault() + return + } + + if (event.code === 'Period') { + if (nodeOnFocus !== undefined) { + nodeOnFocus.payload.uiObject.valueAtAngle = false + nodeOnFocus.payload.uiObject.setValue('Id: ' + nodeOnFocus.id) + return + } + } + if ((event.ctrlKey === true || event.metaKey === true) && event.altKey === true) { + /* Shortcuts to nodes */ + if ((event.keyCode >= 48 && event.keyCode <= 57) || (event.keyCode >= 65 && event.keyCode <= 90)) { + /* From here we prevent the default behaviour. Putting it earlier prevents imput box and text area to receive keystrokes */ + event.preventDefault() + let nodeUsingThisKey = await UI.projects.superalgos.spaces.designSpace.workspace.getNodeByShortcutKey(event.key) + + if (nodeUsingThisKey !== undefined) { + if (nodeOnFocus !== undefined) { + if (nodeUsingThisKey.id === nodeOnFocus.id) { + nodeOnFocus.payload.uiObject.shortcutKey = '' + nodeOnFocus.payload.uiObject.valueAtAngle = false + nodeOnFocus.payload.uiObject.setValue('Shortcut Key Removed ') + } else { + nodeUsingThisKey.payload.uiObject.shortcutKey = '' + nodeUsingThisKey = undefined + nodeOnFocus.payload.uiObject.shortcutKey = event.key + nodeOnFocus.payload.uiObject.valueAtAngle = false + nodeOnFocus.payload.uiObject.setValue('Shortcut Key: Ctrl + Alt + ' + event.key) + } + } else { + UI.projects.superalgos.spaces.floatingSpace.positionAtNode(nodeUsingThisKey) + } + return + } else { + if (nodeOnFocus !== undefined) { + nodeOnFocus.payload.uiObject.shortcutKey = event.key + nodeOnFocus.payload.uiObject.valueAtAngle = false + nodeOnFocus.payload.uiObject.setValue('Shortcut Key: Ctrl + Alt + ' + event.key) + } + } + } + } + if ((event.ctrlKey === true || event.metaKey === true) && event.altKey === true && nodeOnFocus !== undefined) { + if (nodeOnFocus.payload.uiObject.shortcutKey !== undefined && nodeOnFocus.payload.uiObject.shortcutKey !== '') { + nodeOnFocus.payload.uiObject.valueAtAngle = false + nodeOnFocus.payload.uiObject.setValue('Shortcut Key: Ctrl + Alt + ' + nodeOnFocus.payload.uiObject.shortcutKey) + } + } + } + + function onDragEnter(event) { + try { + event.preventDefault() + event.stopPropagation() + UI.projects.superalgos.spaces.cockpitSpace.toTop() + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onDragEnter -> err = ' + err.stack) } + } + } + + function onDragLeave(event) { + try { + event.preventDefault() + event.stopPropagation() + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onDragLeave -> err = ' + err.stack) } + } + } + + function onDragOver(event) { + try { + event.preventDefault() + event.stopPropagation() + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onDragOver -> err = ' + err.stack) } + } + } + + function onDragDrop(event) { + try { + event.preventDefault() + event.stopPropagation() + + let files = event.dataTransfer.files + + handleFiles(files) + function handleFiles(files) { + ([...files]).forEach(loadFileData) + } + + function loadFileData(file) { + let reader = new FileReader() + reader.readAsText(file) + reader.onloadend = function () { + let mousePosition = { + x: event.x, + y: event.y + } + UI.projects.superalgos.spaces.designSpace.workspace.spawn(reader.result, mousePosition) + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onDragDrop -> err = ' + err.stack) } + } + } + + function onMouseDown(event) { + try { + thisObject.mouse.event = event + thisObject.mouse.position.x = event.pageX + thisObject.mouse.position.y = event.pageY - CURRENT_TOP_MARGIN + switch (event.buttons) { + case 1: { + thisObject.mouse.action = 'clicking with left button' + break + } + case 2: { + thisObject.mouse.action = 'clicking with right button' + break + } + } + + let point = event + point.x = event.pageX + point.y = event.pageY - CURRENT_TOP_MARGIN + + dragVector.downX = point.x + dragVector.downY = point.y + + let container + /* + We will go through all the spaces defined at the project schema for each project + and we are going to query each space to see if they have the container that is at + the mouse position. We will order firt the spaces by the sequence order defined + at the project schema specifically for this mouse event. + */ + for (let i = 0; i < PROJECTS_SCHEMA.length; i++) { + let projectDefinition = PROJECTS_SCHEMA[i] + let projectInstance = UI.projects[projectDefinition.propertyName] + + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = projectInstance.events.onMouseDownMap.get(j) + if (spaceInstance === undefined) { continue } + + /* + Here we manage a few exceptional behaviours. Specifically with the + charting space and the floating space, since they have a non standard + way of handling this event. + */ + if (spaceInstance.definition.name === 'Charting Space') { + /* we check if it is over any of the existing containers at the Charting Space. */ + container = spaceInstance.getContainer(point, GET_CONTAINER_PURPOSE.DRAGGING) + + if (container !== undefined) { + if (container.isDraggeable === true) { + containerBeingDragged = container + containerDragStarted = true + containerBeingDragged.eventHandler.raiseEvent('onDragStarted', point) + return + } else { + if (container.isClickeable === false) { + viewPortBeingDragged = true + } + return + } + } + continue + } + + if (spaceInstance.definition.name === 'Floating Space') { + /* we check if it is over any of the existing containers at the Floating Space. */ + container = spaceInstance.getContainer(point) + + if (container !== undefined && container.isDraggeable === true) { + containerBeingDragged = container + containerDragStarted = true + floatingObjectDragStarted = true + containerBeingDragged.eventHandler.raiseEvent('onDragStarted', point) + + if (event.candelDragging === true) { + containerBeingDragged = undefined + containerDragStarted = false + floatingObjectDragStarted = false + } + return + } + + if (container !== undefined && container.isClickeable === true) { + /* We dont want to mess up with the click */ + return + } + continue + } + + /* Next is what happens to all non exceptional behaviour */ + if (spaceInstance.getContainer === undefined) { continue } + container = spaceInstance.getContainer(point) + + if (container !== undefined && container.isDraggeable === true) { + containerBeingDragged = container + containerDragStarted = true + containerBeingDragged.eventHandler.raiseEvent('onDragStarted', point) + return + } + + if (container !== undefined && container.isDraggeable === false) { + return + } + + if (container !== undefined && container.isClickeable === true) { + /* We dont want to mess up with the click */ + return + } + } + } + + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onMouseDown -> err = ' + err.stack) } + } + } + + function onMouseClick(event) { + try { + if (ignoreNextClick === true) { + ignoreNextClick = false + return + } + + let point = event + point.x = event.pageX + point.y = event.pageY - CURRENT_TOP_MARGIN + + let container + /* + We will go through all the spaces defined at the project schema for each project + and we are going to query each space to see if they have the container that is at + the mouse position. We will order firt the spaces by the sequence order defined + at the project schema specifically for this mouse event. + */ + for (let i = 0; i < PROJECTS_SCHEMA.length; i++) { + let projectDefinition = PROJECTS_SCHEMA[i] + let projectInstance = UI.projects[projectDefinition.propertyName] + + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = projectInstance.events.onMouseClickMap.get(j) + if (spaceInstance === undefined) { continue } + + if (spaceInstance.getContainer === undefined) { continue } + container = spaceInstance.getContainer(point, GET_CONTAINER_PURPOSE.MOUSE_CLICK) + + if (container !== undefined && container.isClickeable === true) { + container.eventHandler.raiseEvent('onMouseClick', point) + return + } + } + } + + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onMouseClick -> err = ' + err.stack) } + } + } + + function onMouseOut(event) { + deactivateDragging(event) + + /* When the mouse leaves the canvas, our elements needs to react to the fact that the mouse is over a far away place */ + let thisEvent = { + pageX: VERY_LARGE_NUMBER, + pageY: VERY_LARGE_NUMBER + } + onMouseOver(thisEvent) + } + + function onMouseUp(event) { + deactivateDragging(event) + } + + function onMouseMove(event) { + try { + thisObject.mouse.event = event + thisObject.mouse.position.x = event.pageX + thisObject.mouse.position.y = event.pageY - CURRENT_TOP_MARGIN + thisObject.mouse.action = 'moving' + + /* Processing the event */ + let point = event + point.x = event.pageX + point.y = event.pageY - CURRENT_TOP_MARGIN + + if (UI.projects.superalgos.spaces.chartingSpace.viewport !== undefined) { + UI.projects.superalgos.spaces.chartingSpace.viewport.mousePosition.x = point.x + UI.projects.superalgos.spaces.chartingSpace.viewport.mousePosition.y = point.y + } + + if (containerDragStarted === true || floatingObjectDragStarted === true || viewPortBeingDragged === true) { + if (floatingObjectDragStarted === true) { + let targetContainer = UI.projects.superalgos.spaces.floatingSpace.getContainer(point) + if (targetContainer !== undefined) { + if (targetContainer.id !== containerBeingDragged.id) { + containerBeingDragged.eventHandler.raiseEvent('onDragFinished', point) + containerBeingDragged = undefined + containerDragStarted = false + floatingObjectDragStarted = false + browserCanvas.style.cursor = 'auto' + ignoreNextClick = true + return + } + } else { + containerBeingDragged.eventHandler.raiseEvent('onDragFinished', point) + containerDragStarted = false + containerBeingDragged = undefined + floatingObjectDragStarted = false + browserCanvas.style.cursor = 'auto' + ignoreNextClick = true + return + } + } + + dragVector.upX = point.x + dragVector.upY = point.y + + checkDrag(event) + } + onMouseOver(event) + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onMouseMove -> err = ' + err.stack) } + } + } + + function onMouseOver(event) { + try { + let point = event + point.x = event.pageX + point.y = event.pageY - CURRENT_TOP_MARGIN + + if (containerDragStarted === true) { + containerBeingDragged.eventHandler.raiseEvent('onMouseOver', point) + return + } + + /* Then we check who is the current object underneeth the mounse. */ + + let container + + /* + We will go through all the spaces defined at the project schema for each project + and we are going to query each space to see if they have the container that is at + the mouse position. We will order firt the spaces by the sequence order defined + at the project schema specifically for this mouse event. + */ + for (let i = 0; i < PROJECTS_SCHEMA.length; i++) { + let projectDefinition = PROJECTS_SCHEMA[i] + let projectInstance = UI.projects[projectDefinition.propertyName] + + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = projectInstance.events.onMouseOverMap.get(j) + if (spaceInstance === undefined) { continue } + + if (spaceInstance.getContainer === undefined) { continue } + container = spaceInstance.getContainer(point, GET_CONTAINER_PURPOSE.MOUSE_OVER) + + if (container !== undefined && container.detectMouseOver === true) { + containerFound() + return + } + } + } + + function containerFound() { + if (lastContainerMouseOver !== undefined) { + if (container.id !== lastContainerMouseOver.id) { + lastContainerMouseOver.eventHandler.raiseEvent('onMouseNotOver', point) + } + } + container.eventHandler.raiseEvent('onMouseOver', point) + lastContainerMouseOver = container + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onMouseOver -> err = ' + err.stack) } + } + } + + function onMouseWheel(event) { + try { + thisObject.mouse.event = event + thisObject.mouse.position.x = event.pageX + thisObject.mouse.position.y = event.pageY - CURRENT_TOP_MARGIN + thisObject.mouse.action = 'wheel' + + // cross-browser wheel delta + var event = window.event || event // old IE support + event.delta = Math.max(-1, Math.min(1, event.wheelDelta || -event.detail)) + if (IS_MAC) { + event.delta = event.delta / MAC_AMOUNT_FACTOR + } + + let point = { + x: event.pageX, + y: event.pageY - CURRENT_TOP_MARGIN + } + + event.mousePosition = point + let container + + /* + We will go through all the spaces defined at the project schema for each project + and we are going to query each space to see if they have the container that is at + the mouse position. We will order firt the spaces by the sequence order defined + at the project schema specifically for this mouse event. + */ + for (let i = 0; i < PROJECTS_SCHEMA.length; i++) { + let projectDefinition = PROJECTS_SCHEMA[i] + let projectInstance = UI.projects[projectDefinition.propertyName] + + for (let j = 0; j < projectDefinition.spaces.length; j++) { + let spaceInstance = projectInstance.events.onMouseWheelMap.get(j) + if (spaceInstance === undefined) { continue } + + if (spaceInstance.getContainer === undefined) { continue } + container = spaceInstance.getContainer({ x: point.x, y: point.y }, GET_CONTAINER_PURPOSE.MOUSE_WHEEL) + + if (container !== undefined && container.isWheelable === true) { + container.eventHandler.raiseEvent('onMouseWheel', event) + return false // This instructs the browser not to take the event and scroll the page. + } + } + } + + return false + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] onMouseWheel -> err = ' + err.stack) } + } + } + + function deactivateDragging(event) { + try { + if (containerDragStarted || viewPortBeingDragged || floatingObjectDragStarted) { + thisObject.eventHandler.raiseEvent('Drag Finished', undefined) + } + + if ( + containerDragStarted || + floatingObjectDragStarted || + viewPortBeingDragged + ) { + ignoreNextClick = false + } + /* Turn off all the possible things that can be dragged. */ + + containerDragStarted = false + floatingObjectDragStarted = false + viewPortBeingDragged = false + + if (containerBeingDragged !== undefined) { + containerBeingDragged.eventHandler.raiseEvent('onDragFinished', event) + containerBeingDragged = undefined + } + + browserCanvas.style.cursor = 'auto' + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] deactivateDragging -> err = ' + err.stack) } + } + } + + function checkDrag(event) { + try { + if (containerDragStarted === true || floatingObjectDragStarted === true || viewPortBeingDragged === true) { + if (event !== undefined) { + thisObject.mouse.event = event + thisObject.mouse.position.x = event.pageX + thisObject.mouse.position.y = event.pageY - CURRENT_TOP_MARGIN + thisObject.mouse.action = 'dragging' + } + + browserCanvas.style.cursor = 'grabbing' + thisObject.eventHandler.raiseEvent('Dragging', undefined) + + if (viewPortBeingDragged) { + let displaceVector = { + x: dragVector.upX - dragVector.downX, + y: dragVector.upY - dragVector.downY + } + + UI.projects.superalgos.spaces.chartingSpace.viewport.displace(displaceVector) + } + if (containerDragStarted) { + if (containerBeingDragged !== undefined) { + if (containerBeingDragged.space === 'Charting Space') { + let downCopy = { + x: dragVector.downX, + y: dragVector.downY + } + + let downNoZoom + downNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(downCopy) + + let upCopy = { + x: dragVector.upX, + y: dragVector.upY + } + + let upNoZoom + upNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(upCopy) + + displaceVector = { + x: upNoZoom.x - downNoZoom.x, + y: upNoZoom.y - downNoZoom.y + } + + let moveSucceed = containerBeingDragged.displace(displaceVector) + if (moveSucceed === false) { + deactivateDragging(event) + } + } else { + let displaceVector = { + x: dragVector.upX - dragVector.downX, + y: dragVector.upY - dragVector.downY + } + let moveSucceed = containerBeingDragged.displace(displaceVector) + if (moveSucceed === false) { + deactivateDragging(event) + } + } + } + } + + /* Finally we set the starting point of the new dragVector at this current point. */ + dragVector.downX = dragVector.upX + dragVector.downY = dragVector.upY + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] checkDrag -> err = ' + err.stack) } + } + } +} diff --git a/Container.js b/Container.js new file mode 100644 index 000000000..f3afb66e5 --- /dev/null +++ b/Container.js @@ -0,0 +1,262 @@ + +function newContainer() { + let thisObject = { + id: newUniqueId(), + type: 'Rectangle', + frame: undefined, + eventHandler: undefined, + parentContainer: undefined, + isDraggeable: true, + notDraggingOnX: false, + notDraggingOnY: false, + isClickeable: false, + isWheelable: false, + detectMouseOver: false, + name: undefined, + fitFunction: undefined, + isVisibleFunction: undefined, + displace: displace, + initialize: initialize, + finalize: finalize, + connectToParent: connectToParent, + isForThisPurpose: isForThisPurpose + } + + let connectedToParentWidth = false + let connectedToParentHeight = false + let connectedToParentRadius = false + + let onDimmensionsChangedParentEvent = false + let onMouseOverParentEvent = false + let onMouseNotOverParentEvent = false + let onFocusParentEvent = false + let onNotFocusParentEvent = false + let onDisplaceParentEvent = false + let onDragStartedParentEvent = false + let onDragFinishedParentEvent = false + + let isConnectedToParent = false + + let dimensionsChangedEventSubscriptionId + let onMouseOverEventSubscriptionId + let onMouseNotOverEventSubscriptionId + let onFocusEventSubscriptionId + let onNotFocusEventSubscriptionId + let onDisplaceEventSubscriptionId + let onDragStartedEventSubscriptionId + let onDragFinishedEventSubscriptionId + + return thisObject + + function finalize() { + if (isConnectedToParent === true) { + thisObject.parentContainer.eventHandler.stopListening(dimensionsChangedEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onMouseOverEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onMouseNotOverEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onFocusEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onNotFocusEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onDisplaceEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onDragStartedEventSubscriptionId) + thisObject.parentContainer.eventHandler.stopListening(onDragFinishedEventSubscriptionId) + + thisObject.parentContainer = undefined + thisObject.eventHandler.finalize() + + thisObject.frame.finalize() + } + } + + function initialize(pName, pType) { + thisObject.name = pName + thisObject.type = pType + + thisObject.frame = newFrame() + thisObject.frame.initialize(pType) + if (pName !== undefined) { + thisObject.frame.containerName = pName + } + thisObject.frame.container = thisObject + + thisObject.eventHandler = newEventHandler() + thisObject.eventHandler.name = pName + } + + function connectToParent( + parentContainer, + onWidth, + onHeight, + onRadius, + onDimmensionsChangedEvent, + onMouseOverEvent, + onMouseNotOverEvent, + onFocusEvent, + onNotFocusEvent, + onDisplaceEvent, + onDragStartedEvent, + onDragFinishedEvent + ) { + connectedToParentWidth = onWidth + connectedToParentHeight = onHeight + connectedToParentRadius = onRadius + + onDimmensionsChangedParentEvent = onDimmensionsChangedEvent + onMouseOverParentEvent = onMouseOverEvent + onMouseNotOverParentEvent = onMouseNotOverEvent + onFocusParentEvent = onFocusEvent + onNotFocusParentEvent = onNotFocusEvent + onDisplaceParentEvent = onDisplaceEvent + onDragStartedParentEvent = onDragStartedEvent + onDragFinishedParentEvent = onDragFinishedEvent + + isConnectedToParent = true + + thisObject.frame.parentFrame = parentContainer.frame + thisObject.parentContainer = parentContainer + + if (onDimmensionsChangedParentEvent === true) { + dimensionsChangedEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('Dimmensions Changed', onParentDimmensionsChanged) + } + if (onMouseOverParentEvent === true) { + onMouseOverEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onMouseOver', onMouseOver) + } + if (onMouseNotOverParentEvent === true) { + onMouseNotOverEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onMouseNotOver', onMouseNotOver) + } + if (onFocusParentEvent === true) { + onFocusEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onFocus', onFocus) + } + if (onNotFocusParentEvent === true) { + onNotFocusEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onNotFocus', onNotFocus) + } + if (onDisplaceParentEvent === true) { + onDisplaceEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onDisplace', onDisplace) + } + if (onDragStartedParentEvent === true) { + onDragStartedEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onDragStarted', onDragStarted) + } + if (onDragFinishedParentEvent === true) { + onDragFinishedEventSubscriptionId = thisObject.parentContainer.eventHandler.listenToEvent('onDragFinished', onDragFinished) + } + + if (connectedToParentWidth) { + thisObject.frame.width = thisObject.parentContainer.frame.width + } + if (connectedToParentHeight) { + thisObject.frame.height = thisObject.parentContainer.frame.height + } + if (connectedToParentRadius) { + thisObject.frame.radius = thisObject.parentContainer.frame.radius + } + } + + function onParentDimmensionsChanged(event) { + let dimmensionsChanged = false + if (connectedToParentWidth) { + thisObject.frame.width = thisObject.parentContainer.frame.width + dimmensionsChanged = true + } + if (connectedToParentHeight) { + thisObject.frame.height = thisObject.parentContainer.frame.height + dimmensionsChanged = true + } + if (connectedToParentRadius) { + thisObject.frame.radius = thisObject.parentContainer.frame.radius + dimmensionsChanged = true + } + + if (dimmensionsChanged) { + thisObject.eventHandler.raiseEvent('Dimmensions Changed', event) + } + } + + function onMouseOver(event) { + thisObject.eventHandler.raiseEvent('onMouseOver', event) + } + + function onMouseNotOver(event) { + thisObject.eventHandler.raiseEvent('onMouseNotOver', event) + } + + function onFocus(event) { + thisObject.eventHandler.raiseEvent('onFocus', event) + } + + function onNotFocus(event) { + thisObject.eventHandler.raiseEvent('onNotFocus', event) + } + + function onDisplace(event) { + thisObject.eventHandler.raiseEvent('onDisplace', event) + } + + function onDragStarted(event) { + thisObject.eventHandler.raiseEvent('onDragStarted', event) + } + + function onDragFinished(event) { + thisObject.eventHandler.raiseEvent('onDragFinished', event) + } + + function isForThisPurpose(purpose) { + if (purpose !== undefined) { + switch (purpose) { + case GET_CONTAINER_PURPOSE.MOUSE_OVER: + if (thisObject.detectMouseOver === true) { + return true + } + break + case GET_CONTAINER_PURPOSE.MOUSE_WHEEL: + if (thisObject.isWheelable === true) { + return true + } + break + case GET_CONTAINER_PURPOSE.MOUSE_CLICK: + if (thisObject.isClickeable === true) { + return true + } + break + case GET_CONTAINER_PURPOSE.DRAGGING: + if (thisObject.isDraggeable === true) { + return true + } + break + default: { return false } + + } + } + return false + } + + function displace(point) { + /* This function will move the container according to the vector received. If it has a visible function will use it + to see if the container can be moved there or not. It wont if the center will not be visible. + + Returns true if move was possible, false if not. */ + + let checkPoint = {} + + if (thisObject.isVisibleFunction === undefined) { + drag() + return true + } else { + checkPoint.x = thisObject.frame.position.x + point.x + thisObject.frame.width / 2 + checkPoint.y = thisObject.frame.position.y + point.y + thisObject.frame.height / 2 + let isVisible = thisObject.isVisibleFunction(checkPoint) + if (isVisible === true) { + drag() + return true + } else { + return false + } + } + function drag() { + if (thisObject.notDraggingOnX === false) { + thisObject.frame.position.x = thisObject.frame.position.x + point.x + } + if (thisObject.notDraggingOnY === false) { + thisObject.frame.position.y = thisObject.frame.position.y + point.y + } + thisObject.eventHandler.raiseEvent('onDisplace', event) + } + } +} diff --git a/EventHandler.js b/EventHandler.js new file mode 100644 index 000000000..3820a8242 --- /dev/null +++ b/EventHandler.js @@ -0,0 +1,46 @@ + +function newEventHandler() { + let thisObject = { + name: undefined, // This is for debugging purposes only. + listenToEvent: listenToEvent, + stopListening: stopListening, + raiseEvent: raiseEvent, + finalize: finalize + } + + let eventHandlers = [] // Here we store all the functions we will call when an event is raiseed. + + return thisObject + + function finalize() { + eventHandlers = [] + } + + function listenToEvent(eventType, handler, extraData) { + let eventSubscriptionId = newUniqueId() + eventHandlers.push([eventType, handler, extraData, eventSubscriptionId]) + return eventSubscriptionId + } + + function stopListening(pEventSubscriptionId) { + for (let i = 0; i < eventHandlers.length; i++) { + if (pEventSubscriptionId === eventHandlers[i][3]) { + eventHandlers.splice(i, 1) + return + } + } + } + + function raiseEvent(eventType, event) { + for (var i = 0; i < eventHandlers.length; i++) { + /* We will execute all the functions listening to this event type. */ + + if (eventHandlers[i][0] === eventType) { + let handler = eventHandlers[i][1] + let extraData = eventHandlers[i][2] + + handler(event, extraData) + } + } + } +} diff --git a/EventsServerClient.js b/EventsServerClient.js new file mode 100644 index 000000000..71002ce23 --- /dev/null +++ b/EventsServerClient.js @@ -0,0 +1,292 @@ + +function newEventsServerClient(networkNode) { + /* Web Sockets Connection */ + + const MODULE_NAME = 'System Event Handler' + const ERROR_LOG = true + const INFO_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let WEB_SOCKETS_CONNECTION + + let thisObject = { + isConnected: isConnected, + initialize: initialize, + finalize: finalize, + physics: physics, + createEventHandler: createEventHandler, + deleteEventHandler: deleteEventHandler, + listenToEvent: listenToEvent, + stopListening: stopListening, + raiseEvent: raiseEvent + } + + let eventListeners = new Map() + let responseWaiters = new Map() + let commandsWaitingConfirmation = new Map() + let commandsSentByTimestamp = new Map() + let nonce = 0 + let lastTryToReconnectDatetime + + return thisObject + + function initialize(callBackFunction) { + setuptWebSockets(callBackFunction) + } + + function finalize() { + WEB_SOCKETS_CONNECTION.close() + WEB_SOCKETS_CONNECTION.onerror = undefined + WEB_SOCKETS_CONNECTION.onmessage = undefined + WEB_SOCKETS_CONNECTION.onopen = undefined + WEB_SOCKETS_CONNECTION = undefined + + eventListeners = undefined + responseWaiters = undefined + commandsWaitingConfirmation = undefined + commandsSentByTimestamp = undefined + nonce = undefined + thisObject = undefined + } + + function isConnected() { + if (WEB_SOCKETS_CONNECTION !== undefined) { + if (WEB_SOCKETS_CONNECTION.readyState === 1) { + return true + } + } + } + + function sendCommand(command, responseCallBack, eventsCallBack) { + if (command.action === 'listenToEvent') { + let key + if (command.callerId !== undefined) { + key = command.eventHandlerName + '-' + command.eventType + '-' + command.callerId + } else { + key = command.eventHandlerName + '-' + command.eventType + } + eventListeners.set(key, eventsCallBack) + } + if (command.callerId && responseCallBack) { + responseWaiters.set(command.callerId, responseCallBack) + } + + sendToWebSocketServer(command) + } + + function sendToWebSocketServer(command) { + if (commandsWaitingConfirmation === undefined) {return} // object already finalized + + nonce++ + let stringNonce = nonce.toString() + let timestamp = (new Date()).valueOf() + commandsWaitingConfirmation.set(stringNonce, command) + commandsSentByTimestamp.set(stringNonce, timestamp) + + let messageToWebSocketServer = 'Web Browser' + '|*|' + stringNonce + '|*|' + JSON.stringify(command) + + if (WEB_SOCKETS_CONNECTION.readyState === 1) { + WEB_SOCKETS_CONNECTION.send(messageToWebSocketServer) + } else { + //console.log('WebSocket message could not be sent because the connection was not ready. Will retry soon. Message = ' + JSON.stringify(command)) + } + } + + function createEventHandler(eventHandlerName, callerId, responseCallBack) { + let eventCommand = { + action: 'createEventHandler', + eventHandlerName: eventHandlerName, + callerId: callerId + } + sendCommand(eventCommand, responseCallBack) + } + + function deleteEventHandler(eventHandlerName, callerId, responseCallBack) { + let eventCommand = { + action: 'deleteEventHandler', + eventHandlerName: eventHandlerName, + callerId: callerId + } + sendCommand(eventCommand, responseCallBack) + } + + function listenToEvent(eventHandlerName, eventType, extraData, callerId, responseCallBack, eventsCallBack) { + let eventCommand = { + action: 'listenToEvent', + eventHandlerName: eventHandlerName, + eventType: eventType, + extraData: extraData, + callerId: callerId + } + sendCommand(eventCommand, responseCallBack, eventsCallBack) + } + + function stopListening(eventHandlerName, eventSubscriptionId, callerId, responseCallBack) { + let eventCommand = { + action: 'stopListening', + eventHandlerName: eventHandlerName, + eventSubscriptionId: eventSubscriptionId, + callerId: callerId + } + sendCommand(eventCommand, responseCallBack) + } + + function raiseEvent(eventHandlerName, eventType, event, callerId, responseCallBack) { + let eventCommand = { + action: 'raiseEvent', + eventHandlerName: eventHandlerName, + eventType: eventType, + event: event, + callerId: callerId + } + sendCommand(eventCommand, responseCallBack) + } + + function retryCommandsPhysics() { + commandsSentByTimestamp.forEach(checkTimestamp) + + function checkTimestamp(timestamp, nonce) { + const MILISECONDS_TO_WAIT_FOR_RETRYING_A_COMMAND = 10000 + let now = (new Date()).valueOf() + + if ((now - timestamp) > MILISECONDS_TO_WAIT_FOR_RETRYING_A_COMMAND) { + let command = commandsWaitingConfirmation.get(nonce) + commandsWaitingConfirmation.delete(nonce) + commandsSentByTimestamp.delete(nonce) + + sendToWebSocketServer(command) + } + } + } + + function physics() { + if (WEB_SOCKETS_CONNECTION !== undefined) { + if (WEB_SOCKETS_CONNECTION.readyState === 3) { + /* + Connection closed. May happen after computer goes to sleep. + We will try to reconnect, but we will do it no more that once + a minute. + */ + if (lastTryToReconnectDatetime === undefined) { + setuptWebSockets(onOpen) + lastTryToReconnectDatetime = (new Date()).valueOf() + } else { + let now = (new Date()).valueOf() + if (now - lastTryToReconnectDatetime > 60000) { + setuptWebSockets(onOpen) + } + } + + function onOpen() { + if (INFO_LOG === true) { logger.write('[INFO] setuptWebSockets -> Found Web Sockets Connection Closed. Reconnected to WebSockets Server.') } + } + } + } else { + /* + If there is a problem with the configuration then we go through + this path, retrying this until the configuration is fixed. + */ + setuptWebSockets(onOpen) + } + + if (thisObject.isConnected() !== true ) { + if ( lastTryToReconnectDatetime !== undefined) { + if (networkNode !== undefined) { + if (networkNode.payload !== undefined) { + if (networkNode.payload.uiObject !== undefined) { + networkNode.payload.uiObject.setErrorMessage('Failed to Connect to Superalgos Backend via WebSockets. Retrying in 1 minute.') + } + } + } + } + } else { + if (networkNode !== undefined) { + if (networkNode.payload !== undefined) { + if (networkNode.payload.uiObject !== undefined) { + networkNode.payload.uiObject.setStatus('Connected to Superalgos Backend via WebSockets.') + retryCommandsPhysics() + } + } + } + } + + DEBUG.variable4 = 'Commands Waiting For Confirmation from WS Server: ' + commandsWaitingConfirmation.size + } + + function setuptWebSockets(callBackFunction) { + try { + let host + let port + /* At this point the node does not have the payload property yet, that is why we have to do this manually */ + try { + let config = JSON.parse(networkNode.config) + host = config.host + port = config.webSocketsPort + + /* Check if we really have to stablish the connection. */ + if (config.autoConnect === false) {return} + } catch (err) { + networkNode.payload.uiObject.setErrorMessage('Bad configuration. Cannot know the host name or the webSocketsPort.') + return + } + + if (host === undefined) { + networkNode.payload.uiObject.setErrorMessage('Bad configuration. You need to have a host property.') + return + } + + if (port === undefined) { + networkNode.payload.uiObject.setErrorMessage('Bad configuration. You need to have a webSocketsPort property.') + return + } + + const WEB_SOCKETS_URL = 'ws://' + host + ':' + port + '' + + WEB_SOCKETS_CONNECTION = new WebSocket(WEB_SOCKETS_URL) + WEB_SOCKETS_CONNECTION.onerror = error => { + console.log('WebSocket error:' + JSON.stringify(error)) + } + WEB_SOCKETS_CONNECTION.onopen = () => { + if (callBackFunction !== undefined) { + callBackFunction() + } + } + WEB_SOCKETS_CONNECTION.onmessage = e => { + // console.log('Websoked Message Received: ' + e.data) + + let message = JSON.parse(e.data) + + if (message.action === 'Event Raised') { + let key + if (message.callerId !== undefined) { + key = message.eventHandlerName + '-' + message.eventType + '-' + message.callerId + } else { + key = message.eventHandlerName + '-' + message.eventType + } + let handler = eventListeners.get(key) + if (handler) { + handler(message) + } + return + } + + if (message.action === 'Event Server Response') { + let handler = responseWaiters.get(message.callerId) + if (handler) { + handler(message) + } + return + } + + if (message.action === 'Acknowledge') { + commandsWaitingConfirmation.delete(message.nonce) + commandsSentByTimestamp.delete(message.nonce) + return + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] setuptWebSockets -> err = ' + err.stack) } + } + } +} diff --git a/Frame.js b/Frame.js new file mode 100644 index 000000000..5176f9927 --- /dev/null +++ b/Frame.js @@ -0,0 +1,537 @@ + +function newFrame() { + const PANEL_CORNERS_RADIUS = 5 + const TITLE_BAR_HEIGHT = 15 // this must be grater than radius + + let thisObject = { + type: 'Rectangle', + containerName: '', // This is for debugging purposes only. + parentFrame: undefined, // Here we store the parent cointainer zoom object. + radius: 0, + offset: undefined, + position: undefined, + container: undefined, + width: browserCanvas.width, + height: browserCanvas.height, + getBodyHeight: getBodyHeight, + draw: draw, + fitIntoFrame: fitIntoFrame, + frameThisPoint: frameThisPoint, // This function changes the actual frame coordinate system to the screen coordinate system. + unframeThisPoint: unframeThisPoint, + isThisPointHere: isThisPointHere, // This function return true is the point received as parameter lives within this frame. + isThisScreenPointHere: isThisScreenPointHere, + isInViewPort: isInViewPort, + isCenterInViewPort: isCenterInViewPort, + initialize: initialize, + finalize: finalize + } + + return thisObject + + function finalize() { + thisObject.parentFrame = undefined + thisObject.position = undefined + thisObject.offset = undefined + } + + function initialize(pType) { + if (pType !== undefined) { thisObject.type = pType } + + thisObject.position = { + x: 0, + y: 0 + } + + thisObject.offset = { + x: 0, + y: 0 + } + } + + function getBodyHeight() { + return thisObject.height - TITLE_BAR_HEIGHT + } + + function isInViewPort() { + /* This function is usefull to know if the object who has this frame is currently appearing at least in part at the UI.projects.superalgos.spaces.chartingSpace.viewport */ + + point1 = { + x: 0, + y: 0 + } + + point3 = { + x: thisObject.width, + y: thisObject.height + } + + /* Now the transformations. */ + + point1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point1, thisObject.container) + point3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point3, thisObject.container) + + if (point1.x < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.x && point1.y < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y && point3.x > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.x && point3.y > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y) { + return true + } else { + return false + } + } + + function isCenterInViewPort() { + /* This function is usefull to know if the object who has this frame is currently appearing at least in part at the UI.projects.superalgos.spaces.chartingSpace.viewport */ + + point1 = { + x: thisObject.width * 40 / 100, + y: thisObject.height * 40 / 100 + } + + point3 = { + x: thisObject.width * 40 / 100, + y: thisObject.height * 40 / 100 + } + + /* Now the transformations. */ + + point1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point1, thisObject.container) + point3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point3, thisObject.container) + + if (point1.x < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topRight.x && point1.y < UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomRight.y && point3.x > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.bottomLeft.x && point3.y > UI.projects.superalgos.spaces.chartingSpace.viewport.visibleArea.topLeft.y) { + return true + } else { + return false + } + } + + function frameThisPoint(point) { + /* We need not to modify the point received, so me make a copy of it. */ + + let checkPoint = { + x: point.x, + y: point.y + } + + let parentPoint = { + x: 0, + y: 0 + } + + let returnPoint = { + x: 0, + y: 0 + } + + if (thisObject.parentFrame !== undefined) { + parentPoint = thisObject.parentFrame.frameThisPoint(checkPoint) + + returnPoint.x = parentPoint.x + thisObject.position.x + thisObject.offset.x + returnPoint.y = parentPoint.y + thisObject.position.y + thisObject.offset.y + } else { + returnPoint.x = checkPoint.x + thisObject.position.x + thisObject.offset.x + returnPoint.y = checkPoint.y + thisObject.position.y + thisObject.offset.y + } + + return returnPoint + } + + function unframeThisPoint(point) { + /* We need not to modify the point received, so me make a copy of it. */ + + let checkPoint = { + x: point.x, + y: point.y + } + + let parentPoint = { + x: 0, + y: 0 + } + + let returnPoint = { + x: 0, + y: 0 + } + + if (thisObject.parentFrame !== undefined) { + parentPoint = thisObject.parentFrame.unframeThisPoint(checkPoint) + + returnPoint.x = parentPoint.x - thisObject.position.x + returnPoint.y = parentPoint.y - thisObject.position.y + } else { + returnPoint.x = checkPoint.x - thisObject.position.x + returnPoint.y = checkPoint.y - thisObject.position.y + } + + return returnPoint + } + + function fitIntoFrame(point) { + /* Here we check the boundaries of the resulting points, so they dont go out of the visible area. */ + + if (point.x > thisObject.width) { + point.x = thisObject.width + } + + if (point.x < 0) { + point.x = 0 + } + + if (point.y > thisObject.height) { + point.y = thisObject.height + } + + if (point.y < 0) { + point.y = 0 + } + + return point + } + + function isThisPointHere(point, outsideViewPort, dontTransform, padding) { + // The second parameter is usefull when you want to check a point that you already know is outside the viewport. + // The padding is a distance to the borders of the container. Can be either negative (outside, to increase the cointainer size) or positive (inside to decrease the cointainer size) + + if (padding === undefined) { padding = 0 } + + /* We need not to modify the point received, so me make a copy of it. */ + let checkPoint = { + x: point.x, + y: point.y + } + + /* The point received is on the screen coordinates system, which already has zoom and displacement applied. We need to remove the zoom and displacement + in order to have the point on the containers coordinate system and be able to compare it with its dimmensions. */ + if (dontTransform === false || dontTransform === undefined) { + if (outsideViewPort === true) { + checkPoint = thisObject.container.frame.unframeThisPoint(checkPoint) + } else { + checkPoint = UI.projects.superalgos.utilities.coordinateTransformations.unTransformThisPoint(checkPoint, thisObject.container) + } + } + + /* Now we check if the resulting point is whin the current Frame. */ + if (isNaN(checkPoint.x) || isNaN(checkPoint.y)) { return false } + if (thisObject.type === 'Circle') { + let distance = Math.sqrt(Math.pow(thisObject.position.x - checkPoint.x, 2) + Math.pow(thisObject.position.y - checkPoint.y, 2)) + + if (distance < thisObject.radius) { + return true + } else { + return false + } + } + + if (thisObject.type === 'Rectangle') { + if (checkPoint.x < 0 + padding || checkPoint.y < 0 + padding || checkPoint.x > thisObject.width - padding || checkPoint.y > thisObject.height - padding) { + return false + } else { + return true + } + } + } + + function isThisScreenPointHere(point) { + let checkPoint = { + x: point.x, + y: point.y + } + + let thisPoint = { + x: thisObject.position.x, + y: thisObject.position.y + } + + if (thisObject.parentFrame !== undefined) { + thisPoint = thisObject.parentFrame.frameThisPoint(thisPoint) + } + + /* Now we check if the resulting point is whin the current Frame. */ + + if (thisObject.type === 'Circle') { + let distance = Math.sqrt(Math.pow(thisPoint.x - checkPoint.x, 2) + Math.pow(thisPoint.y - checkPoint.y, 2)) + + if (distance < thisObject.radius) { + return true + } else { + return false + } + } + + if (thisObject.type === 'Rectangle') { + if (checkPoint.x < 0 || checkPoint.y < 0 || checkPoint.x > thisObject.width || checkPoint.y > thisObject.height) { + return false + } else { + return true + } + } + } + + function draw(drawGrid, drawBorders, drawBackground, fitFunction, style) { + if (drawBorders === true) { + borders(fitFunction, style) + } + + if (drawGrid === true) { + grid(fitFunction) + } + + if (drawBackground === true) { + background(fitFunction) + } + } + + function background(fitFunction) { + let params = { + cornerRadius: 5, + lineWidth: 0.1, + opacity: 0.75, + container: thisObject.container, + borderColor: UI_COLOR.DARK, + backgroundColor: UI_COLOR.WHITE, + fitFunction: fitFunction + } + + UI.projects.superalgos.utilities.drawPrint.roundedCornersBackground(params) + + titleBarPoint1 = { + x: 0, + y: TITLE_BAR_HEIGHT + } + + titleBarPoint2 = { + x: thisObject.width, + y: TITLE_BAR_HEIGHT + } + + /* Now the transformations. */ + + titleBarPoint1 = frameThisPoint(titleBarPoint1) + titleBarPoint2 = frameThisPoint(titleBarPoint2) + + if (fitFunction !== undefined) { + titleBarPoint1 = fitFunction(titleBarPoint1) + titleBarPoint2 = fitFunction(titleBarPoint2) + } + + /* We paint the title bar now */ + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.DARK + ', 1)' + browserCanvasContext.beginPath() + + browserCanvasContext.moveTo(titleBarPoint1.x, titleBarPoint1.y) + browserCanvasContext.lineTo(borderPoint1.x - PANEL_CORNERS_RADIUS, borderPoint1.y) + browserCanvasContext.arc(borderPoint1.x, borderPoint1.y, PANEL_CORNERS_RADIUS, 1.0 * Math.PI, 1.5 * Math.PI) + browserCanvasContext.lineTo(borderPoint2.x, borderPoint2.y - PANEL_CORNERS_RADIUS) + browserCanvasContext.arc(borderPoint2.x, borderPoint2.y, PANEL_CORNERS_RADIUS, 1.5 * Math.PI, 2.0 * Math.PI) + browserCanvasContext.lineTo(titleBarPoint2.x, titleBarPoint2.y) + + browserCanvasContext.closePath() + browserCanvasContext.fill() + + browserCanvasContext.lineWidth = 0.1 + browserCanvasContext.strokeStyle = 'rgba(' + UI_COLOR.MANGANESE_PURPLE + ', 0.75)' + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke() + + /* print the title */ + + let labelPoint + let fontSize = 10 + + browserCanvasContext.font = fontSize + 'px ' + UI_FONT.PRIMARY + + let label = thisObject.containerName + + let xOffset = label.length / 2 * fontSize * FONT_ASPECT_RATIO + let yOffset = (TITLE_BAR_HEIGHT - fontSize) / 2 + 2 + + labelPoint = { + x: thisObject.width / 2 - xOffset, + y: TITLE_BAR_HEIGHT - yOffset + } + + labelPoint = thisObject.frameThisPoint(labelPoint) + + if (fitFunction !== undefined) { + labelPoint = fitFunction(labelPoint) + } + + browserCanvasContext.fillStyle = 'rgba(' + UI_COLOR.WHITE + ', 1)' + browserCanvasContext.fillText(label, labelPoint.x, labelPoint.y) + } + + function borders(fitFunction, style) { + /* Lest get the important points of the drawing so as to apply the needed transformations. */ + + let point1 + let point2 + let point3 + let point4 + + point1 = { + x: 0, + y: 0 + } + + point2 = { + x: thisObject.width, + y: 0 + } + + point3 = { + x: thisObject.width, + y: thisObject.height + } + + point4 = { + x: 0, + y: thisObject.height + } + + /* Now the transformations. */ + point1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point1, thisObject.container) + point2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point2, thisObject.container) + point3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point3, thisObject.container) + point4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point4, thisObject.container) + + if (fitFunction !== undefined) { + point1 = fitFunction(point1) + point2 = fitFunction(point2) + point3 = fitFunction(point3) + point4 = fitFunction(point4) + } + + /* 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(point4.x, point4.y) + browserCanvasContext.lineTo(point1.x, point1.y) + browserCanvasContext.closePath() + + browserCanvasContext.strokeStyle = 'rgba(' + style.color + ', ' + style.opacity + ')' + browserCanvasContext.lineWidth = style.lineWidth + browserCanvasContext.setLineDash(style.lineDash) + browserCanvasContext.stroke() + + browserCanvasContext.closePath() + } + + function grid(fitFunction, smallLines) { + if (smallLines === true) { + /* Small Lines */ + + let step = thisObject.width / 100 + + browserCanvasContext.beginPath() + + for (let i = 0; i < thisObject.width; i = i + step) { + for (let j = 0; j < thisObject.height; j = j + step) { + let point1 = { + x: 0, + y: j + } + + let point2 = { + x: thisObject.width, + y: j + } + + point1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point1, thisObject.container) + point2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point2, thisObject.container) + + if (fitFunction !== undefined) { + point1 = fitFunction(point1) + point2 = fitFunction(point2) + } + + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + } + + let point3 = { + x: thisObject.width, + y: thisObject.height + } + + let point4 = { + x: 0, + y: thisObject.height + } + + point3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point3, thisObject.container) + point4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point4, thisObject.container) + + if (fitFunction !== undefined) { + point3 = fitFunction(point3) + point4 = fitFunction(point4) + } + + browserCanvasContext.moveTo(point3.x, point3.y) + browserCanvasContext.lineTo(point4.x, point4.y) + } + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(150, 150, 150, 0.1)' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke() + } + + /* Main Lines */ + + let step = thisObject.width / 20 + + browserCanvasContext.beginPath() + + for (let i = 0; i < thisObject.width; i = i + step) { + for (let j = 0; j < thisObject.height; j = j + step) { + let point1 = { + x: 0, + y: j + } + + let point2 = { + x: thisObject.width, + y: j + } + + point1 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point1, thisObject.container) + point2 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point2, thisObject.container) + + if (fitFunction !== undefined) { + point1 = fitFunction(point1) + point2 = fitFunction(point2) + } + + browserCanvasContext.moveTo(point1.x, point1.y) + browserCanvasContext.lineTo(point2.x, point2.y) + } + + let point3 = { + x: i, + y: 0 + } + + let point4 = { + x: i, + y: thisObject.height + } + + point3 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point3, thisObject.container) + point4 = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(point4, thisObject.container) + + if (fitFunction !== undefined) { + point3 = fitFunction(point3) + point4 = fitFunction(point4) + } + + browserCanvasContext.moveTo(point3.x, point3.y) + browserCanvasContext.lineTo(point4.x, point4.y) + } + browserCanvasContext.closePath() + browserCanvasContext.strokeStyle = 'rgba(100, 100, 100, 0.2)' + browserCanvasContext.lineWidth = 1 + browserCanvasContext.setLineDash([0, 0]) + browserCanvasContext.stroke() + } +} diff --git a/Globals.js b/Globals.js new file mode 100644 index 000000000..a2d19421e --- /dev/null +++ b/Globals.js @@ -0,0 +1,230 @@ + +let canvas +let PROJECTS_ARRAY +let PROJECTS_SCHEMA +let SCHEMAS_BY_PROJECT = new Map() +/* New Root object*/ +let UI = { + projects: {} +} + +/* User Interface Colors */ +const UI_COLOR = { + DARK: "48, 48, 54", + LIGHT: "234, 226, 222", + GREY: "150, 150, 150", + LIGHT_GREY: "247, 247, 247", + WHITE: "255, 255, 255", + BLACK: "0, 0, 0", + GOLDEN_ORANGE: "240, 162, 2", + RUSTED_RED: "204, 88, 53", + GREEN: "188, 214, 67", + RED: "223, 70, 60", + PATINATED_TURQUOISE: "27,153,139", + TITANIUM_YELLOW: "244,228,9", + MANGANESE_PURPLE: "91,80,122", + TURQUOISE: "74,207,217", + DARK_TURQUOISE: "2,149,170" +}; + +/* User Interface Fonts */ +const UI_FONT = { + PRIMARY: "Saira Condensed", + SECONDARY: "Source Code Pro" +}; + +const MAC_AMOUNT_FACTOR = 5 +const IS_MAC = navigator.platform.toUpperCase().indexOf('MAC') >= 0 + +const DEBUG = {} + +let FONT_ASPECT_RATIO = 0.32; +let ANIMATION_FRAME_PER_SECONDS + +let DISABLE_BROWSER_RESIZE_EVENT = false + +const GET_CONTAINER_PURPOSE = { + MOUSE_OVER: 1, + MOUSE_WHEEL: 2, + MOUSE_CLICK: 3, + DRAGGING: 4 +} + +const ANGLE_TO_PARENT = { + NOT_FIXED: 0, + RANGE_360: 1, + RANGE_180: 2, + RANGE_90: 3, + RANGE_45: 4 +} + +const DISTANCE_TO_PARENT = { + NOT_FIXED: 0, + PARENT_025X: 1, + PARENT_050X: 2, + PARENT_100X: 3, + PARENT_150X: 4, + PARENT_200X: 5 +} + +const ARRANGEMENT_STYLE = { + CONCAVE: 0, + CONVEX: 1, + VERTICAL_RIGHT: 2, + VERTICAL_LEFT: 3, + HORIZONTAL_BOTTOM: 4, + HORIZONTAL_TOP: 5 +} + +/* User Interface Panels */ + +const UI_PANEL = { + WIDTH: { + SMALL: 100, + NORMAL: 150, + MEDIUM: 200, + LARGE: 250, + X_LARGE: 350 + }, + HEIGHT: { + SMALL: 65, + MEDIUM: 90, + NORMAL: 300, + LARGE: 450 + } +}; + + +let marketFilesPeriods = + '[' + + '[' + 24 * 60 * 60 * 1000 + ',' + '"24-hs"' + ']' + ',' + + '[' + 12 * 60 * 60 * 1000 + ',' + '"12-hs"' + ']' + ',' + + '[' + 8 * 60 * 60 * 1000 + ',' + '"08-hs"' + ']' + ',' + + '[' + 6 * 60 * 60 * 1000 + ',' + '"06-hs"' + ']' + ',' + + '[' + 4 * 60 * 60 * 1000 + ',' + '"04-hs"' + ']' + ',' + + '[' + 3 * 60 * 60 * 1000 + ',' + '"03-hs"' + ']' + ',' + + '[' + 2 * 60 * 60 * 1000 + ',' + '"02-hs"' + ']' + ',' + + '[' + 1 * 60 * 60 * 1000 + ',' + '"01-hs"' + ']' + ']'; + +marketFilesPeriods = JSON.parse(marketFilesPeriods); + +let dailyFilePeriods = + '[' + + '[' + 45 * 60 * 1000 + ',' + '"45-min"' + ']' + ',' + + '[' + 40 * 60 * 1000 + ',' + '"40-min"' + ']' + ',' + + '[' + 30 * 60 * 1000 + ',' + '"30-min"' + ']' + ',' + + '[' + 20 * 60 * 1000 + ',' + '"20-min"' + ']' + ',' + + '[' + 15 * 60 * 1000 + ',' + '"15-min"' + ']' + ',' + + '[' + 10 * 60 * 1000 + ',' + '"10-min"' + ']' + ',' + + '[' + 05 * 60 * 1000 + ',' + '"05-min"' + ']' + ',' + + '[' + 04 * 60 * 1000 + ',' + '"04-min"' + ']' + ',' + + '[' + 03 * 60 * 1000 + ',' + '"03-min"' + ']' + ',' + + '[' + 02 * 60 * 1000 + ',' + '"02-min"' + ']' + ',' + + '[' + 01 * 60 * 1000 + ',' + '"01-min"' + ']' + ']'; + +dailyFilePeriods = JSON.parse(dailyFilePeriods); + +let SHOW_ANIMATION_PERFORMACE = false; +let MENU_ITEM_ON_FOCUS +let EDITOR_ON_FOCUS + +let testUser = window.localStorage.getItem("test_user") +let LOGGED_IN_USER_LOCAL_STORAGE_KEY +let LOGGED_IN_ACCESS_TOKEN_LOCAL_STORAGE_KEY + +if (testUser !== null) { + LOGGED_IN_USER_LOCAL_STORAGE_KEY = "test_user" + LOGGED_IN_ACCESS_TOKEN_LOCAL_STORAGE_KEY = "test_access_token" +} else { + LOGGED_IN_USER_LOCAL_STORAGE_KEY = "user" + LOGGED_IN_ACCESS_TOKEN_LOCAL_STORAGE_KEY = "access_token" +} + +const MAX_DEFAULT_RATE_SCALE_VALUE = 35000; // This is needed to know the scale of the market time line. + +const WIDHTER_VOLUME_BAR_BASE_FACTOR = 2.5; +const LESS_WIDHTER_VOLUME_BAR_TOP_FACTOR = 1 / 4; + +const ONE_DAY_IN_MILISECONDS = 24 * 60 * 60 * 1000; +const _12_HOURS_IN_MILISECONDS = 12 * 60 * 60 * 1000; +const _8_HOURS_IN_MILISECONDS = 8 * 60 * 60 * 1000; +const _6_HOURS_IN_MILISECONDS = 6 * 60 * 60 * 1000; +const _4_HOURS_IN_MILISECONDS = 4 * 60 * 60 * 1000; +const _3_HOURS_IN_MILISECONDS = 3 * 60 * 60 * 1000; +const _2_HOURS_IN_MILISECONDS = 2 * 60 * 60 * 1000; +const _1_HOUR_IN_MILISECONDS = 1 * 60 * 60 * 1000; + +const _45_MINUTES_IN_MILISECONDS = 45 * 60 * 1000; +const _40_MINUTES_IN_MILISECONDS = 40 * 60 * 1000; +const _30_MINUTES_IN_MILISECONDS = 30 * 60 * 1000; +const _20_MINUTES_IN_MILISECONDS = 20 * 60 * 1000; +const _15_MINUTES_IN_MILISECONDS = 15 * 60 * 1000; +const _10_MINUTES_IN_MILISECONDS = 10 * 60 * 1000; +const _5_MINUTES_IN_MILISECONDS = 5 * 60 * 1000; +const _4_MINUTES_IN_MILISECONDS = 4 * 60 * 1000; +const _3_MINUTES_IN_MILISECONDS = 3 * 60 * 1000; +const _2_MINUTES_IN_MILISECONDS = 2 * 60 * 1000; +const _1_MINUTE_IN_MILISECONDS = 1 * 60 * 1000; + + + +let NEW_SESSION_INITIAL_DATE = new Date(); // This value will be overwritten at the UI.projects.superalgos.spaces.chartingSpace.viewport.initialize if the user had a prevous session with this same browser. +let INITIAL_ZOOM_LEVEL = -28.25 // This is the zoom level at the view port in which the APP starts. +let INITIAL_TIME_PERIOD = ONE_DAY_IN_MILISECONDS // This value will be overwritten at the UI.projects.superalgos.spaces.chartingSpace.viewport.initialize if the user had a prevous session with this same browser. +let VERY_LARGE_NUMBER = 100000000000000 + +const TOP_MARGIN = 0 +let CURRENT_TOP_MARGIN = TOP_MARGIN +let AT_FULL_SCREEN_MODE = false + +let maxDate = new Date(); +maxDate.setMilliseconds(0); +maxDate.setDate(maxDate.getDate() + 365 * 1); // We might have charts that projects data into the future. + +const MIN_PLOTABLE_DATE = new Date(2015, 0, 1, 0, 0, 0); +const MAX_PLOTABLE_DATE = maxDate; + +const TOP_SPACE_HEIGHT = 40 +const COCKPIT_SPACE_HEIGHT = 40; +const SIDE_PANEL_WIDTH = 350 +let COCKPIT_SPACE_POSITION = browserCanvas.height - COCKPIT_SPACE_HEIGHT + +const LAYER_STATUS = { + ON: 'on', + LOADING: 'loading', + OFF: 'off' +}; + +let spawnPosition // this is used in several places. + +/* Here we list the valid Time Periods: */ + +const PERIOD_24_HS = "24-hs"; +const PERIOD_12_HS = "12-hs"; +const PERIOD_06_HS = "06-hs"; +const PERIOD_03_HS = "03-hs"; +const PERIOD_01_HS = "01-hs"; +const PERIOD_30_MIN = "30-min"; +const PERIOD_10_MIN = "10-min"; +const PERIOD_05_MIN = "05-min"; +const PERIOD_01_MIN = "01-min"; + +/* +We define here the size of the chartingSpace. It has to bee enough big in order to accomodate all the charts we expect to display in this space. +*/ + +const TIME_MACHINE_WIDTH = 8; +const TIME_MACHINE_HEIGHT = 8; + +let mediaRecorder // to downloadText canvas animation as a mediaRecorder +let marketPanoramaCanvas +let ARE_WE_RECORDING_A_VIDEO = false +let ARE_WE_RECORDING_A_MARKET_PANORAMA = false +let PANORAMA_WAS_PANNED = false +let CURRENT_PANORAMA_POSITION = 0 + +let CAN_SPACES_DRAW = false // This global variable regulates when spaces can or can not draw their contents. + +function toRadians(angle) { + return angle * (Math.PI / 180); +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..faea7fc57 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work plugins a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/PostLoader.js b/PostLoader.js new file mode 100644 index 000000000..cfb5488db --- /dev/null +++ b/PostLoader.js @@ -0,0 +1,147 @@ + + +function newAppPostLoader() { + const MODULE_NAME = 'Post Loader' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + let thisObject = { + start: start + } + + return thisObject + + function start() { + try { + /* Moving PROJECTS_ARRAY to the global variable */ + PROJECTS_ARRAY = window.PROJECTS + window.PROJECTS = undefined + + setBrowserEvents() + setupProjectsSchema() + + function setupProjectsSchema() { + callWebServer(undefined, 'ProjectsSchema', onResponse) + + function onResponse(err, file) { + PROJECTS_SCHEMA = JSON.parse(file) + setupSchemas() + } + } + + function setupSchemas() { + + let totalWebServerCalls = 0 + let webServerResponses = 0 + + for (let i = 0; i < PROJECTS_ARRAY.length; i++) { + let project = PROJECTS_ARRAY[i] + let schemas = { + array: { + appSchema: [], + docSchema: [], + conceptSchema: [] + }, + map: { + appSchema: new Map(), + docSchema: new Map(), + conceptSchema: new Map() + } + } + SCHEMAS_BY_PROJECT.set(project, schemas) + + totalWebServerCalls++ + callWebServer(undefined, 'Schema/' + project + '/AppSchema', onResponseAppSchema) + + function onResponseAppSchema(err, schema) { + try { + schemas.array.appSchema = JSON.parse(schema) + + for (let j = 0; j < schemas.array.appSchema.length; j++) { + let nodeDefinition = schemas.array.appSchema[j] + let key = nodeDefinition.type + schemas.map.appSchema.set(key, nodeDefinition) + } + } catch (err) { + console.log(err.stack) + } + + webServerResponses++ + if (webServerResponses === totalWebServerCalls) { startCanvas() } + } + + totalWebServerCalls++ + callWebServer(undefined, 'Schema/' + project + '/DocSchema', onResponseDocSchema) + + function onResponseDocSchema(err, schema) { + try { + schemas.array.docSchema = JSON.parse(schema) + + for (let j = 0; j < schemas.array.docSchema.length; j++) { + let nodeDefinition = schemas.array.docSchema[j] + let key = nodeDefinition.type + schemas.map.docSchema.set(key, nodeDefinition) + } + } catch (err) { + console.log(err.stack) + } + + webServerResponses++ + if (webServerResponses === totalWebServerCalls) { startCanvas() } + } + + totalWebServerCalls++ + callWebServer(undefined, 'Schema/' + project + '/ConceptSchema', onResponseConceptSchema) + + function onResponseConceptSchema(err, schema) { + try { + schemas.array.conceptSchema = JSON.parse(schema) + + for (let j = 0; j < schemas.array.conceptSchema.length; j++) { + let nodeDefinition = schemas.array.conceptSchema[j] + let key = nodeDefinition.type + schemas.map.conceptSchema.set(key, nodeDefinition) + } + } catch (err) { + console.log(err.stack) + } + + webServerResponses++ + if (webServerResponses === totalWebServerCalls) { startCanvas() } + } + } + } + + function startCanvas() { + /* If this method is executed for a second time, it should finalize the current execution structure */ + if (canvas !== undefined) { canvas.finalize() } + + canvas = newCanvas() + canvas.initialize() + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] start -> err = ' + err.stack) } + } + } + + function setBrowserEvents() { + window.onbeforeunload = saveWorkspace + + /* handles backspace and refresh(F5) from keyboard */ + window.manageBackRefresh = function (event) { + var tag = event.target.tagName.toLowerCase() + if (event.keyCode === 8 && tag !== 'input' && tag !== 'textarea') { // Backbutton pressed + saveWorkspace() + } else if (event.keyCode === 116) { // F5 pressed + saveWorkspace() + } + } + + window.addEventListener('keydown', window.manageBackRefresh) + + function saveWorkspace() { + UI.projects.superalgos.spaces.designSpace.workspace.save() + } + } +} diff --git a/ProductStorage.js b/ProductStorage.js new file mode 100644 index 000000000..55d48088f --- /dev/null +++ b/ProductStorage.js @@ -0,0 +1,333 @@ + +function newProductStorage(name) { + const MODULE_NAME = 'Product Storage' + const ERROR_LOG = true + const logger = newWebDebugLog() + logger.fileName = MODULE_NAME + + /* + + This object will initialize children objects that will end up loading the data of each set defined at each product of the bot received at initialization. + Once all the underlaying objects are fully initialized it will callback. + + At the same time it will raise an event for each underlaying file being loaded, so that the UI can reflect the progress to the end user. + + Product Storage + | + |-----> SingleFile + | + |-----> FileSequence + | + |-----> DailyFiles -----> FileCursor + | + |-----> MarketFiles + + */ + + let thisObject = { + eventHandler: undefined, + marketFiles: [], + dailyFiles: [], + singleFile: [], + fileSequences: [], + setDatetime: setDatetime, + setTimeFrame: setTimeFrame, + initialize: initialize, + finalize: finalize + } + + thisObject.eventHandler = newEventHandler() + + /* We name the event Handler to easy debugging. */ + + thisObject.eventHandler.name = 'Storage-' + name + + let datetime + let timeFrame + + return thisObject + + function finalize() { + try { + for (let i = 0; i < thisObject.marketFiles.length; i++) { + let marketFile = thisObject.marketFiles[i] + marketFile.finalize() + } + + thisObject.marketFiles = undefined + + for (let i = 0; i < thisObject.dailyFiles.length; i++) { + let dailyFile = thisObject.dailyFiles[i] + dailyFile.finalize() + } + + thisObject.dailyFiles = undefined + + for (let i = 0; i < thisObject.fileSequences.length; i++) { + let fileSequence = thisObject.fileSequences[i] + fileSequence.finalize() + } + + thisObject.fileSequences = undefined + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] finalize -> err = ' + err.stack) } + } + } + + function initialize(mine, bot, session, product, exchange, market, pDatetime, pTimeFrame, host, port, eventsServerClient, callBackFunction) { + try { + datetime = pDatetime + timeFrame = pTimeFrame + + let dataSetsToLoad = 0 + let dataSetsLoaded = 0 + + for (let i = 0; i < product.datasets.length; i++) { + let dataset = product.datasets[i] + + switch (dataset.config.type) { + case 'Market Files': { + dataSetsToLoad++ + + let marketFiles = newMarketFiles() + marketFiles.initialize(mine, bot, session, product, dataset, exchange, market, host, port, eventsServerClient, onMarketFileReady) + thisObject.marketFiles.push(marketFiles) + } + break + + case 'Daily Files': { + dataSetsToLoad++ + + let dailyFiles = newDailyFiles() + dailyFiles.initialize(mine, bot, session, product, dataset, exchange, market, pDatetime, pTimeFrame, host, port, eventsServerClient, onDailyFileReady) + thisObject.dailyFiles.push(dailyFiles) + } + break + + case 'Single File': { + dataSetsToLoad++ + + let singleFile = newSingleFile() + singleFile.initialize(mine, bot, session, product, dataset, exchange, market, host, port, eventsServerClient, onSingleFileReady) + thisObject.singleFile.push(singleFile) + } + break + + case 'File Sequence': { + dataSetsToLoad++ + + let fileSequences = newFileSequence() + fileSequences.initialize(mine, bot, session, product, dataset, exchange, market, host, port, eventsServerClient, onFileSequenceReady) + thisObject.fileSequences.push(fileSequences) + } + break + default: + if (ERROR_LOG === true) { logger.write('[WARN] initialize -> dataset with no type defined or type not supported -> dataset.config.type = ' + dataset.config.type) } + } + + function onMarketFileReady(err, pCaller) { + try { + switch (err.result) { + case GLOBAL.DEFAULT_OK_RESPONSE.result: { + break + } + case GLOBAL.DEFAULT_FAIL_RESPONSE.result: { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + } + let event = { + totalValue: pCaller.getExpectedFiles(), + currentValue: pCaller.getFilesLoaded(), + filesNotLoaded: pCaller.getFilesNotLoaded() + } + + thisObject.eventHandler.raiseEvent('Market File Loaded', event) + + if (event.filesNotLoaded === event.totalValue) { + dataSetsToLoad-- + checkInitializeComplete() + return + } + + if (event.currentValue + event.filesNotLoaded === event.totalValue) { + dataSetsLoaded++ + checkInitializeComplete() + } + } catch (err) { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + + function onDailyFileReady(err, pCaller) { + try { + switch (err.result) { + case GLOBAL.DEFAULT_OK_RESPONSE.result: { + break + } + + case GLOBAL.DEFAULT_FAIL_RESPONSE.result: { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + + case GLOBAL.CUSTOM_FAIL_RESPONSE.result: { + if (err.message === 'Dataset Unavailable.') { + dataSetsToLoad-- + checkInitializeComplete() + return + } else { + callBackFunction(err) + return + } + } + default: { + callBackFunction(err) + return + } + } + + let event = { + totalValue: pCaller.getExpectedFiles(), + currentValue: pCaller.getFilesLoaded() + } + + thisObject.eventHandler.raiseEvent('Daily File Loaded', event) + + if (event.currentValue === event.totalValue) { + dataSetsLoaded++ + checkInitializeComplete() + } + } catch (err) { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + + function onSingleFileReady(err, pCaller) { + try { + switch (err.result) { + case GLOBAL.DEFAULT_OK_RESPONSE.result: { + break + } + + case GLOBAL.DEFAULT_FAIL_RESPONSE.result: { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + + case GLOBAL.CUSTOM_FAIL_RESPONSE.result: { + callBackFunction(err) + return + } + + default: { + callBackFunction(err) + return + } + } + let event = { + totalValue: 1, + currentValue: 1 + } + thisObject.eventHandler.raiseEvent('Single File Loaded', event) + + if (event.currentValue === event.totalValue) { + dataSetsLoaded++ + checkInitializeComplete() + } + } catch (err) { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + + function onFileSequenceReady(err, pCaller) { + try { + switch (err.result) { + case GLOBAL.DEFAULT_OK_RESPONSE.result: { + break + } + case GLOBAL.DEFAULT_FAIL_RESPONSE.result: { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + return + } + case GLOBAL.CUSTOM_FAIL_RESPONSE.result: { + callBackFunction(err) + return + } + default: { + callBackFunction(err) + return + } + } + let event + if (pCaller !== undefined) { + event = { + totalValue: pCaller.getExpectedFiles(), + currentValue: pCaller.getFilesLoaded() + } + } else { // When there are no data the reference to the caller wont arrive here. + event = { + totalValue: 1, + currentValue: 1 + } + } + + thisObject.eventHandler.raiseEvent('File Sequence Loaded', event) + + if (event.currentValue === event.totalValue) { + dataSetsLoaded++ + checkInitializeComplete() + } + } catch (err) { + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + + function checkInitializeComplete() { + try { + if (dataSetsLoaded === dataSetsToLoad) { + callBackFunction(GLOBAL.DEFAULT_OK_RESPONSE) + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> checkInitializeComplete -> err = ' + err.stack) } + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + } + } catch (err) { + if (ERROR_LOG === true) { logger.write('[ERROR] initialize -> err = ' + err.stack) } + callBackFunction(GLOBAL.DEFAULT_FAIL_RESPONSE) + } + } + + function setDatetime(pDatetime) { + /* If there is a change in the day, then we take some actions, otherwise, we dont. */ + + let currentDate = Math.trunc(datetime.valueOf() / ONE_DAY_IN_MILISECONDS) + let newDate = Math.trunc(pDatetime.valueOf() / ONE_DAY_IN_MILISECONDS) + + datetime = new Date(pDatetime) + + if (currentDate !== newDate) { + if (timeFrame <= _1_HOUR_IN_MILISECONDS) { + for (let i = 0; i < thisObject.dailyFiles.length; i++) { + thisObject.dailyFiles[i].setDatetime(pDatetime) + } + } + } + } + + function setTimeFrame(pTimeFrame) { + /* We are going to filter out the cases in which the timeFrame received is the same that the one we already know. */ + + if (timeFrame !== pTimeFrame) { + timeFrame = pTimeFrame + + if (timeFrame <= _1_HOUR_IN_MILISECONDS) { + for (let i = 0; i < thisObject.dailyFiles.length; i++) { + thisObject.dailyFiles[i].setTimeFrame(pTimeFrame, datetime) + } + } + } + } +} diff --git a/Project.njsproj b/Project.njsproj new file mode 100644 index 000000000..299f685a9 --- /dev/null +++ b/Project.njsproj @@ -0,0 +1,226 @@ + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + Project + browser + Debug|Any CPU + + + + Debug + 2.0 + 3ef11619-de26-4b8b-819d-a413df220b76 + + + + + + + . + . + v4.0 + {3AF33F2E-1136-4D97-BBB7-1795711AC8B8};{349c5851-65df-11da-9384-00065b846f21};{9092AA53-FB77-4645-B42D-1CCCA6BD08BD} + 1337 + true + + + true + + + true + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + + + + + + + Code + + + + + + + + + Code + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + False + True + 0 + / + http://localhost:48022/ + False + True + http://localhost:1337 + False + + + + + + + CurrentPage + True + False + False + False + + + + + + + + + False + False + + + + + diff --git a/README.md b/README.md new file mode 100644 index 000000000..ebef6da4c --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Web App UI + +This repo is one of the repositories built into Superalgos. For more information [read the docs](https://docs.superalgos.org/) or check the [Readme](https://github.com/Superalgos/Superalgos/) at the main repository of the project. + +## Copyright and License Notice + +© Copyright 2020 Superalgos. Please see the [License](/LICENSE) for information on how you can use this software. + +## Disclaimer + +THE SOFTWARE AND ITS ASSOCIATED PRODUCTS AND SERVICES ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, SUITABILITY FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. + +IN NO EVENT WILL SUPERALGOS BE LIABLE TO ANY PARTY FOR ANY DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY USE OF THE SOFTWARE, OR ANY OTHER ASSOCIATED SERVICE OR WEB SITE, INCLUDING, WITHOUT LIMITATION, ANY LOST PROFITS, BUSINESS INTERRUPTION, LOSS OF FUNDS, PROGRAMS OR OTHER DATA OR OTHERWISE, EVEN IF WE ARE EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +# Maintainer + +[![Luis-Fernando-Molina](https://avatars1.githubusercontent.com/u/9479367?s=130&v=4)](https://github.com/Luis-Fernando-Molina) | +---| +[Luis-Fernando-Molina](https://github.com/Luis-Fernando-Molina) | + diff --git a/Utilities.js b/Utilities.js new file mode 100644 index 000000000..c3104adad --- /dev/null +++ b/Utilities.js @@ -0,0 +1,131 @@ +function getNodeDefinition(node, project) { + if (project === undefined) { project = node.project } + + let APP_SCHEMA = SCHEMAS_BY_PROJECT.get(project) + if (APP_SCHEMA !== undefined) { + return SCHEMAS_BY_PROJECT.get(project).map.appSchema.get(node.type) + } else { + console.log('[Error] Could not get APP Scchema for project ' + node.project) + } +} + +function dynamicDecimals(value, minDecimals) { + if (minDecimals === undefined) { + minDecimals = 0 + } + let decimals = 0 + if (value < 1) { decimals = 2 } + + if (Math.abs(Math.trunc(value * 10)) < 1) { decimals = 4 } + if (Math.abs(Math.trunc(value * 100)) < 1) { decimals = 5 } + if (Math.abs(Math.trunc(value * 1000)) < 1) { decimals = 6 } + if (Math.abs(Math.trunc(value * 10000)) < 1) { decimals = 7 } + if (Math.abs(Math.trunc(value * 100000)) < 1) { decimals = 8 } + if (Math.abs(Math.trunc(value * 1000000)) < 1) { decimals = 9 } + if (Math.abs(Math.trunc(value * 10000000)) < 1) { decimals = 10 } + if (Math.abs(Math.trunc(value * 100000000000)) < 1) { value = 0; decimals = 0 } + + if (decimals < minDecimals) { decimals = minDecimals } + + let returnValue = Number(value).toFixed(decimals) + return returnValue +} + +function convertTimeFrameToName(pTimeFrame) { + for (let i = 0; i < dailyFilePeriods.length; i++) { + let period = dailyFilePeriods[i] + if (period[0] === pTimeFrame) { + return period[1] + } + } + + for (let i = 0; i < marketFilesPeriods.length; i++) { + let period = marketFilesPeriods[i] + if (period[0] === pTimeFrame) { + return period[1] + } + } +} + +function nextPorwerOf10(number) { + for (let i = -10; i <= 10; i++) { + if (number < Math.pow(10, i)) { + return Math.pow(10, i) + } + } +} + +function pad(str, max) { + str = str.toString() + return str.length < max ? pad('0' + str, max) : str +} + +function moveToUserPosition(container, currentDate, currentRate, coordinateSystem, ignoreX, ignoreY, mousePosition, fitFunction) { + let targetPoint = { + x: currentDate.valueOf(), + y: currentRate + } + + /* Put this point in the coordinate system of the UI.projects.superalgos.spaces.chartingSpace.viewport */ + targetPoint = coordinateSystem.transformThisPoint(targetPoint) + targetPoint = UI.projects.superalgos.utilities.coordinateTransformations.transformThisPoint(targetPoint, container) + + let displaceVector + + let targetNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(targetPoint) + let mouseNoZoom = UI.projects.superalgos.spaces.chartingSpace.viewport.unTransformThisPoint(mousePosition) + + displaceVector = { + x: mouseNoZoom.x - targetNoZoom.x, + y: mouseNoZoom.y - targetNoZoom.y + } + + if (ignoreX) { displaceVector.x = 0 } + if (ignoreY) { displaceVector.y = 0 } + + container.displace(displaceVector) +} + +function removeTime(datetime) { + if (datetime === undefined) { return } + + let dateOnly = new Date(Math.trunc(datetime.valueOf() / ONE_DAY_IN_MILISECONDS) * ONE_DAY_IN_MILISECONDS) + + return dateOnly +} + +function newUniqueId() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8) + return v.toString(16) + }) +} + +function splitTextIntoPhrases(text, wordsPerLine) { + let splittedLabel = text.split(' ') + let phrases = [] + let phrase = '' + let wordCount = 0 + for (let i = 0; i < splittedLabel.length; i++) { + phrase = phrase + splittedLabel[i] + wordCount++ + + if (i < splittedLabel.length - 1 && wordCount < wordsPerLine) { + phrase = phrase + ' ' + } + if (wordCount >= wordsPerLine) { + phrases.push(phrase) + phrase = '' + wordCount = 0 + } + } + if (wordCount > 0) { + phrases.push(phrase) + } + return phrases +} + +function getTextWidth(text) { + var metrics = browserCanvasContext.measureText(text); + return metrics.width; +} \ No newline at end of file diff --git a/VideoRecorder.js b/VideoRecorder.js new file mode 100644 index 000000000..829e59e72 --- /dev/null +++ b/VideoRecorder.js @@ -0,0 +1,218 @@ +function newVideoRecorder() { + let thisObject = { + recordCanvas: recordCanvas, + physics: physics + } + let lastAction + let wheelAnimationCounter = 0 + let lastWheelPointerIcon + return thisObject + + function physics() { + wheelAnimationCounter-- + if (wheelAnimationCounter < 0) { + wheelAnimationCounter = 0 + } + } + + function recordCanvas() { + { + const DISTANCE_BETWEEN_ICONS = 35 + const ICON_SIZES = 50 + + let mousePointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'mouse-pointer') + let draggingIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'hand-drag') + + let leftClickIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'mouse-left-click') + let rightClickIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'mouse-right-click') + let wheelClickIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'mouse-wheel-click') + + let imagePosition + let buttonPressedIcon + + if (canvas.mouse.action === 'key down' && lastAction === 'dragging') { + canvas.mouse.action = 'dragging' + } + switch (canvas.mouse.action) { + case 'moving': { + pointerIcon = mousePointerIcon + break + } + case 'dragging': { + pointerIcon = draggingIcon + break + } + case 'wheel': { + wheelAnimationCounter = 25 + if (canvas.mouse.event.delta > 0) { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'mouse-wheel-up') + lastWheelPointerIcon = pointerIcon + } else { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'mouse-wheel-down') + lastWheelPointerIcon = pointerIcon + } + + break + } + case 'key down': { + if (wheelAnimationCounter > 0) { + pointerIcon = lastWheelPointerIcon + } else { + pointerIcon = mousePointerIcon + } + let key = canvas.mouse.event.key + if (key === undefined) { break } + switch (key.toLowerCase()) { + case 'escape': + { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-m') + break + } + case 'm': + { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-m') + break + } + case 'a': + { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-a') + break + } + case 'r': + { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-r') + break + } + case 'c': + { + pointerIcon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-r') + break + } + } + break + } + case 'key up': { + pointerIcon = mousePointerIcon + break + } + } + + lastAction = canvas.mouse.action + + imagePosition = { + x: canvas.mouse.position.x, + y: canvas.mouse.position.y + } + drawMousePointer(pointerIcon, imagePosition, ICON_SIZES) + + switch (canvas.mouse.event.buttons) { + case 0: { + buttonPressedIcon = undefined + break + } + case 1: { + buttonPressedIcon = leftClickIcon + break + } + case 2: { + buttonPressedIcon = rightClickIcon + break + } + case 3: { + buttonPressedIcon = undefined + break + } + case 4: { + buttonPressedIcon = wheelClickIcon + break + } + } + + let firstRow = DISTANCE_BETWEEN_ICONS * 1 + ICON_SIZES * 1 + let secondRow = DISTANCE_BETWEEN_ICONS * 1 + ICON_SIZES * 2 + let icon + + imagePosition = { + x: canvas.mouse.position.x + DISTANCE_BETWEEN_ICONS * 2, + y: canvas.mouse.position.y + } + drawMousePointer(buttonPressedIcon, imagePosition, ICON_SIZES) + + if (canvas.mouse.event.shiftKey === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-shift') + imagePosition = { + x: canvas.mouse.position.x - DISTANCE_BETWEEN_ICONS * 3, + y: canvas.mouse.position.y + firstRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + if (canvas.mouse.event.ctrlKey === true || canvas.mouse.event.metaKey === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-ctrl') + imagePosition = { + x: canvas.mouse.position.x - DISTANCE_BETWEEN_ICONS * 3, + y: canvas.mouse.position.y + secondRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + if (canvas.mouse.event.altKey === true) { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-alt') + imagePosition = { + x: canvas.mouse.position.x + DISTANCE_BETWEEN_ICONS * 3 + ICON_SIZES * 0, + y: canvas.mouse.position.y + secondRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + if (canvas.mouse.event.code === 'ArrowUp') { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-up') + imagePosition = { + x: canvas.mouse.position.x - 0, + y: canvas.mouse.position.y + firstRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + if (canvas.mouse.event.code === 'ArrowDown') { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-down') + imagePosition = { + x: canvas.mouse.position.x - 0, + y: canvas.mouse.position.y + secondRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + if (canvas.mouse.event.code === 'ArrowLeft') { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-left') + imagePosition = { + x: canvas.mouse.position.x - DISTANCE_BETWEEN_ICONS * 0 - ICON_SIZES * 1, + y: canvas.mouse.position.y + secondRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + if (canvas.mouse.event.code === 'ArrowRight') { + icon = UI.projects.superalgos.spaces.designSpace.getIconByProjectAndName( 'Superalgos', 'key-right') + imagePosition = { + x: canvas.mouse.position.x + DISTANCE_BETWEEN_ICONS * 0 + ICON_SIZES * 1, + y: canvas.mouse.position.y + secondRow + } + drawMousePointer(icon, imagePosition, ICON_SIZES) + } + + function drawMousePointer(icon, imagePosition, imageSize) { + if (icon !== undefined) { + if (icon.canDrawIcon === true) { + browserCanvasContext.drawImage( + icon, imagePosition.x, + imagePosition.y, + imageSize, + imageSize) + } + } + } + mediaRecorder.capture(browserCanvas) + } + } +} diff --git a/WebDebugLog.js b/WebDebugLog.js new file mode 100644 index 000000000..2a6bc1af1 --- /dev/null +++ b/WebDebugLog.js @@ -0,0 +1,21 @@ +function newWebDebugLog() { + try { + let thisObject = { + write: write, + fileName: 'undefined' + } + + return thisObject + + function write(pText) { + console.log(spacePad(thisObject.fileName, 50) + ' : ' + pText) + + function spacePad(str, max) { + str = str.toString() + return str.length < max ? spacePad(' ' + str, max) : str + } + } + } catch (err) { + console.log(spacePad(MODULE_NAME, 50) + ' : ' + '[ERROR] WebDebugLog --> err = ' + err.stack) + } +}