From deeeda290c78cbbf106e5fbd5f0c9ed40140cb2c Mon Sep 17 00:00:00 2001 From: healthire Date: Fri, 31 Mar 2017 10:11:25 +0100 Subject: [PATCH] Add support for parameter types and explicitly setting parameters as blackboard keys --- src/app/menu/menu.js | 3 +- src/app/node/modal-editnode.html | 16 +++- src/app/node/node.js | 159 +++++++++++++++++++++---------- src/app/property/property.html | 6 +- src/app/property/property.js | 119 +++++++++++++++-------- src/dev.html | 1 + src/editor/editor.js | 128 ++++++++++++++++++++----- src/editor/models/Project.js | 2 +- src/editor/utils/CustomJson.js | 55 +++++++++++ 9 files changed, 364 insertions(+), 125 deletions(-) create mode 100644 src/editor/utils/CustomJson.js diff --git a/src/app/menu/menu.js b/src/app/menu/menu.js index 04f1aaa..764fa83 100644 --- a/src/app/menu/menu.js +++ b/src/app/menu/menu.js @@ -116,14 +116,13 @@ angular.module('app.menu', ['app.modal']) } for (category in nodes) { - fs.writeFileSync(path.join(editor.project.nodesPath, category + ".json"), nodes[category]); + fs.writeFileSync(path.join(editor.project.nodesPath, category + ".nodes"), nodes[category]); } return false; } $scope.onButtonNewProject = function(e) { if (e) e.preventDefault(); - console.log("New project"); dialog.showOpenDialog({ title: "Select path for nodes", filters : [ diff --git a/src/app/node/modal-editnode.html b/src/app/node/modal-editnode.html index b24a46f..11a841d 100644 --- a/src/app/node/modal-editnode.html +++ b/src/app/node/modal-editnode.html @@ -29,8 +29,13 @@

Edit Custom Node

+ - + @@ -46,8 +51,13 @@

Edit Custom Node

+ + -
+ - + @@ -77,4 +87,4 @@

Edit Custom Node

- \ No newline at end of file + diff --git a/src/app/node/node.js b/src/app/node/node.js index 3c7abc3..8de48c4 100644 --- a/src/app/node/node.js +++ b/src/app/node/node.js @@ -10,7 +10,7 @@ angular.module('app.node', ['app.modal']) $scope.types = ['composite', 'decorator', 'action', 'module']; $scope.nodes = {}; $scope.categories = {}; - + $scope.showAddNodeModal = function() { ModalService.showModal({ templateUrl: "app/node/modal-addnode.html", @@ -98,36 +98,47 @@ angular.module('app.node', ['app.modal']) // .controller('AddNodeModalController', function($scope, $window, $compile, close) { $scope.close = function(result) { close(result); }; - + // DYNAMIC TABLE ------------------------------------------------------------ - + + $scope.types = ['composite', 'decorator', 'action', 'module']; + $scope.valueTypes = [ + {id: 'json', name: 'json'}, + {id: "entity", name: "entity"}, + {id: "position", name: "position"}, + {id: "vec2", name: "vec2"}, + {id: "number", name: "number"}, + {id: "bool", name: "bool"}, + {id: "list", name: "list"}, + {id: "table", name: "table"}, + {id: "string", name: "string"} + ]; + this.propertyTemplate = '\ \ - \ - \ + \ + \ + \ \ \ '; - $scope.types = ['composite', 'decorator', 'action', 'module']; - var this_ = this; - $scope.addProperty = function(key, value) { - if (typeof key == 'undefined') key = ''; - if (typeof value == 'undefined') value = ''; - value = JSON.stringify(value).replace(/["]/g, """).replace(/['"']/g, "'"); - var template = this_.propertyTemplate.format(key, value); + $scope.addProperty = function() { + var template = this_.propertyTemplate.format(); var propertiesTable = angular.element( document.querySelectorAll('#addnode-properties-table>tbody') ); propertiesTable.append($compile(template)($scope)); } - $scope.addOutput = function(key, value) { - if (typeof key == 'undefined') key = ''; - if (typeof value == 'undefined') value = ''; - var template = this_.propertyTemplate.format(key, value); + $scope.addOutput = function() { + var template = this_.propertyTemplate.format(); var outputTable = angular.element( document.querySelectorAll('#addnode-output-table>tbody') ); @@ -155,6 +166,7 @@ angular.module('app.node', ['app.modal']) var domType = document.querySelector('#addnode-modal #type'); var domName = document.querySelector('#addnode-modal #name'); var domTitle = document.querySelector('#addnode-modal #title'); + var domPropertyTypes = document.querySelectorAll('#addnode-properties #type'); var domPropertyKeys = document.querySelectorAll('#addnode-properties #key'); var domPropertyValues = document.querySelectorAll('#addnode-properties #value'); @@ -166,48 +178,58 @@ angular.module('app.node', ['app.modal']) } if (newNode.type == 'action'){ + var domOutputTypes = document.querySelectorAll('#addnode-output #type'); var domOutputKeys = document.querySelectorAll('#addnode-output #key'); var domOutputValues = document.querySelectorAll('#addnode-output #value'); var domCategory = document.querySelector('#addnode-modal #category'); var domScript = document.querySelector('#addnode-modal #script'); - + newNode.script = domScript.value; newNode.category = domCategory.value; newNode.output = {}; for (var i=0; i' + e - }); + if (type != 'string' && value != '') { + try { + value = JSON.parse(value); + } catch (e){ + $window.app.editor.trigger('notification', name, { + level: 'error', + message: 'Invalid JSON value in property \'' + key + '\'.
' + e + }); + } } + if (value === '') value = null; if (key) { - newNode.properties[key] = value; + newNode.properties[key] = { + type: type, + value: value + }; } } - + if (newNode.name) { $window.app.editor.addNode(newNode); } } }) -.directive('propertyremovable', function() { +.directive('propertyremovable', function() { return { restrict: 'A', link: function(scope, element, attrs) { @@ -226,43 +248,64 @@ angular.module('app.node', ['app.modal']) $scope.close = function(result) { close(result); }; $scope.node = $window.app.editor.nodes[node]; + $scope.valueTypes = [ + {id: 'json', name: 'json'}, + {id: "entity", name: "entity"}, + {id: "position", name: "position"}, + {id: "vec2", name: "vec2"}, + {id: "number", name: "number"}, + {id: "bool", name: "bool"}, + {id: "list", name: "list"}, + {id: "table", name: "table"}, + {id: "string", name: "string"} + ]; this.jsonProperties = function(properties){ var props = {} for (key in properties) { - props[key] = JSON.stringify(properties[key]); + props[key] = { + type: properties[key].type + } + if (properties[key].type != 'string') { + if (properties[key].value === null) + props[key].value = ''; + else + props[key].value = JSON.stringify(properties[key].value); + } else { + props[key].value = properties[key].value; + } } return props; } $scope.properties = this.jsonProperties($scope.node.prototype.properties); $scope.output = $scope.node.prototype.output; - + this.propertyTemplate = '\
\ - \ - \ + \ + \ + \ \ \ '; var this_ = this; - $scope.addProperty = function(key, value) { - if (typeof key == 'undefined') key = ''; - if (typeof value == 'undefined') value = ''; - value = JSON.stringify(value).replace(/["]/g, """).replace(/['"']/g, "'"); - var template = this_.propertyTemplate.format(key, value); + $scope.addProperty = function() { + var template = this_.propertyTemplate.format(); var propertiesTable = angular.element( document.querySelectorAll('#editnode-properties-table>tbody') ); propertiesTable.append($compile(template)($scope)); } - $scope.addOutput = function(key, value) { - if (typeof key == 'undefined') key = ''; - if (typeof value == 'undefined') value = ''; - var template = this_.propertyTemplate.format(key, value); + $scope.addOutput = function() { + var template = this_.propertyTemplate.format(type, key, value); var outputTable = angular.element( document.querySelectorAll('#editnode-output-table>tbody') ); @@ -272,9 +315,10 @@ angular.module('app.node', ['app.modal']) $scope.saveNode = function() { var domName = document.querySelector('#editnode-form #name'); var domTitle = document.querySelector('#editnode-form #title'); + var domTypes = document.querySelectorAll('#editnode-properties #type'); var domKeys = document.querySelectorAll('#editnode-properties #key'); var domValues = document.querySelectorAll('#editnode-properties #value'); - + var newNode = { name: domName.value, title: domTitle.value, @@ -282,6 +326,7 @@ angular.module('app.node', ['app.modal']) } if ($scope.node.prototype.type == 'action'){ + var domOutputTypes = document.querySelectorAll('#editnode-output #type'); var domOutputKeys = document.querySelectorAll('#editnode-output #key'); var domOutputValues = document.querySelectorAll('#editnode-output #value'); var domCategory = document.querySelector('#editnode-modal #category'); @@ -295,28 +340,40 @@ angular.module('app.node', ['app.modal']) newNode.output = {}; for (var i=0; i' + e - }); + if (type != 'string' && value != '') { + try { + value = JSON.parse(value); + } catch (e){ + $window.app.editor.trigger('notification', name, { + level: 'error', + message: 'Invalid JSON value in property \'' + key + '\'.
' + e + }); + } } + if (value === '') value = null; if (key) { - newNode.properties[key] = value; + newNode.properties[key] = { + type: type, + value: value + }; } } @@ -328,4 +385,4 @@ angular.module('app.node', ['app.modal']) $scope.removeNode = function() { $window.app.editor.removeNode(node); } -}); \ No newline at end of file +}); diff --git a/src/app/property/property.html b/src/app/property/property.html index 22e44aa..50d7715 100644 --- a/src/app/property/property.html +++ b/src/app/property/property.html @@ -1,10 +1,10 @@
- +

Select a single block to change its properties.

NOTE: The root node represents a tree. Therefore, changes applied to this node will persist on the tree object.

- +
@@ -41,4 +41,4 @@
-
\ No newline at end of file + diff --git a/src/app/property/property.js b/src/app/property/property.js index c8570bf..a84d8aa 100644 --- a/src/app/property/property.js +++ b/src/app/property/property.js @@ -13,8 +13,13 @@ angular.module('app.property', []) ); this.template = '\
\ - \ - \ + \ + \ + \ + '; + this.outputTemplate = '\ + \ + \ \ '; this.rootTemplate ='\ @@ -24,17 +29,28 @@ angular.module('app.property', []) \ \ '; - + var this_ = this; $scope.addRow = function(key, value) { - if (typeof key == 'undefined') key = ''; - if (typeof value == 'undefined') value = null; - value = JSON.stringify(value).replace(/["]/g, """).replace(/['"']/g, "'"); + if (value == undefined) value = {type: 'json', value: null}; + var isKey = value.key === undefined ? false : true; - if (value == "null") - value = ""; + var valString = value.value + if (valString === undefined) { + valString = value.key; + } + if (valString == null) valString = ''; + if (valString !== '' && !isKey && value.type != 'string') { + valString = JSON.stringify(valString); + } + valString = valString.replace(/["]/g, """).replace(/['"']/g, "'"); - var template = this_.template.format(key, value); + if (isKey) { + isKey = "checked"; + } else { + isKey = ""; + } + var template = this_.template.format(isKey, key, valString); this_.table.append($compile(template)($scope)); } $scope.addRootProperty = function(key, value) { @@ -46,9 +62,9 @@ angular.module('app.property', []) } $scope.addOutput = function(key, value) { - if (typeof key == 'undefined') key = ''; - if (typeof value == 'undefined') value = '';; - var template = this_.template.format(key, value); + if (key == undefined) key = ''; + if (value == undefined) value = ''; + var template = this_.outputTemplate.format(key, value); this_.outputs.append($compile(template)($scope)); } @@ -67,28 +83,24 @@ angular.module('app.property', []) domTitle.value = block.title || ''; for (key in block.node.prototype.properties) { - var value = block.properties[key] !== '' ? block.properties[key] : ''; - $scope.addRow(key, value); + $scope.addRow(key, block.properties[key]); } if (block.type == 'root') { for (key in block.properties) { - var value = block.properties[key] !== '' ? block.properties[key] : ''; - $scope.addRootProperty(key, value); + $scope.addRootProperty(key, block.properties[key].value); } } if (block.type == 'action') { for (key in block.node.prototype.output) { - var value = block.output[key] !== '' ? block.output[key] : ''; - $scope.addOutput(key, value); + $scope.addOutput(key, block.output[key].key); } } } else { var block = null; } - // timeout needed due to apply function // apply is used to update the view automatically when the scope is changed $timeout(function() { @@ -104,36 +116,52 @@ angular.module('app.property', []) // UPDATE PROPERTIES ON NODE $scope.updateProperties = function() { + var node = $scope.block.node; + var domTitle = document.querySelector('#property-panel #title'); var domKeys = document.querySelectorAll('#property-properties-table #key'); var domValues = document.querySelectorAll('#property-properties-table #value'); + var domIsKeys = [] + if (node.prototype.type != 'root') { + domIsKeys = document.querySelectorAll('#property-properties-table #is_key'); + } var newNode = { title: domTitle.value, properties: {} } + var isRoot = node.prototype.type == 'root'; for (var i=0; i' + e - }); + if (value == '') value = null; + + var valueType = isRoot ? 'json' : node.prototype.properties[key].type; + if (!isKey && valueType != 'string') { + try { + value = JSON.parse(value); + } catch (e){ + $window.app.editor.trigger('notification', name, { + level: 'error', + message: 'Invalid JSON value in property \'' + key + '\'.
' + e + }); + } } if (key) { - newNode.properties[key] = value; + newNode.properties[key] = { + type: valueType + } + if (isKey) { + newNode.properties[key].key = value + } else { + newNode.properties[key].value = value + } } } - + if ($scope.block.type == 'action') { var domKeys = document.querySelectorAll('#property-output-table #key'); var domValues = document.querySelectorAll('#property-output-table #value'); @@ -141,20 +169,33 @@ angular.module('app.property', []) newNode.output = {}; for (var i=0; i' + e + }); + } + } - if (key && value != '') { - newNode.output[key] = value; + newNode.output[key] = { + type: node.prototype.output[key].type, + key: value || null } } } - + $window.app.editor.editBlock($scope.block, newNode) } }) -.directive('propertyremovable', function() { +.directive('propertyremovable', function() { return { restrict: 'A', link: function(scope, element, attrs) { @@ -172,4 +213,4 @@ angular.module('app.property', []) controller: 'PropertyController', templateUrl: 'app/property/property.html' } -}); \ No newline at end of file +}); diff --git a/src/dev.html b/src/dev.html index db38eef..7b756d8 100644 --- a/src/dev.html +++ b/src/dev.html @@ -41,6 +41,7 @@ + diff --git a/src/editor/editor.js b/src/editor/editor.js index 1cae986..d6cfdc1 100644 --- a/src/editor/editor.js +++ b/src/editor/editor.js @@ -208,10 +208,24 @@ this.b3editor = this.b3editor || {}; block.id = b3.createUUID(); block.title = node.title; block.description = node.description; - block.properties = node.parameters || {}; + block.properties = {}; + var proto = this.nodes[node.name].prototype; + for (var key in proto.properties) { + if (node.parameters[key] !== undefined) { + block.properties[key] = JSON.parse(JSON.stringify(node.parameters[key])) + } else { + block.properties[key] = {value: null} + } + block.properties[key].type = proto.properties[key].type + } - if (node.type == 'action') - block.output = node.output || {}; + if (node.type == 'action') { + block.output = {}; + node.output = node.output || {}; + for (var key in proto.output) { + block.output[key] = {type: proto.output[key].type, key: node.output[key] || null} + } + } // Import properties for (var key in block.properties) { @@ -277,6 +291,31 @@ this.b3editor = this.b3editor || {}; root.properties = data.parameters || {}; this.addConnection(root, dataRoot); + var editor = this + var treeModuleParameters = function(treeParameters) { + var params = {} + for (var key in treeParameters) { + params[key] = { + type: 'json', + value: treeParameters[key] + } + } + return params + } + + var moduleNode = this.nodes[data.name]; + if (moduleNode != undefined && moduleNode.prototype.type == 'module') { + moduleNode.prototype.properties = treeModuleParameters(data.parameters); + } else { + var newNode = { + name: data.name, + type: 'module', + title: '', + properties: treeModuleParameters(data.parameters) + } + this.addNode(newNode); + } + this.organize(true); return true; } @@ -295,7 +334,9 @@ this.b3editor = this.b3editor || {}; var editor = this; var data = fs.readFileSync(filename); - editor.importFromJSON(data); + if (editor.importFromJSON(data)) { + //editor.writeTreeFile(); + } } p.exportBlock = function(block, scripts) { var data = {}; @@ -304,11 +345,13 @@ this.b3editor = this.b3editor || {}; data.type = block.type; data.name = block.name; data.parameters = {}; - var parameterKeys = Object.keys(block.properties) - for (var i=0; i 0) - data.output = block.output; + if (block.type == 'action') { + for (var key in block.output) { + if (block.output[key] != null && block.output[key].key != null) { + data.output = data.output || {} + data.output[key] = block.output[key].key; + } + } + } var children = block.getOutNodeIdsByOrder(); if (children.length > 0) { @@ -353,14 +402,22 @@ this.b3editor = this.b3editor || {}; if (rootBlock) { data.root = this.exportBlock(this.getBlockById(rootBlock), data.scripts); - return JSON.stringify(data, null, 2); + var replacer = function(k, v, spaces, depth) { + if (k == "parameters" || k == "output") { + // each parameter on one line + return CustomJSON.stringify(v, function(k2, v2, spaces, depth) { + return CustomJSON.stringify(v2, null, 0); + }, spaces, depth + 1) + } else { + return CustomJSON.stringify(v, replacer, spaces, depth + 1); + } + } + return CustomJSON.stringify(data, replacer, 2); } else { return "{}"; } } - p.getScripts = function() { - } p.writeTreeFile = function() { var json = this.exportToJSON(); var path = this.tree.path; @@ -370,7 +427,7 @@ this.b3editor = this.b3editor || {}; fs.writeFile(path, json, function(err){ if (err) throw err; - this.logger.info("Saved tree "+name) + editor.logger.info("Saved tree "+name) editor.trigger('notification', name, { level: 'success', message: 'Saved' @@ -431,9 +488,11 @@ this.b3editor = this.b3editor || {}; data[name].type = node.prototype.type; data[name].name = node.prototype.name; data[name].title = node.prototype.title; - if (node.prototype.properties) + if (node.prototype.properties) { data[name].properties = JSON.parse(JSON.stringify(node.prototype.properties)); + } + if (node.prototype.type == "action") { if (node.prototype.category) data[name].category = node.prototype.category; @@ -445,7 +504,17 @@ this.b3editor = this.b3editor || {}; } } } - return JSON.stringify(data, null, 2) + var replacer = function(k, v, spaces, depth) { + if (k == "properties" || k == "output") { + // each parameter on one line + return CustomJSON.stringify(v, function(k2, v2, spaces, depth) { + return CustomJSON.stringify(v2, null, 0); + }, spaces, depth + 1) + } else { + return CustomJSON.stringify(v, replacer, spaces, depth + 1); + } + } + return CustomJSON.stringify(data, replacer, 2); } p.exportNodes = function() { var nodes = {} @@ -484,18 +553,30 @@ this.b3editor = this.b3editor || {}; var tempClass = b3.Class(cls); tempClass.prototype.name = node.name; tempClass.prototype.title = node.title; - tempClass.prototype.properties = node.properties ? JSON.parse(JSON.stringify(node.properties)) : {}; + tempClass.prototype.properties = {} + if (node.properties) { + for (var key in node.properties) { + tempClass.prototype.properties[key] = JSON.parse(JSON.stringify(node.properties[key])) + } + } if (node.type == "action") { tempClass.prototype.category = node.category || ''; tempClass.prototype.script = node.script || ''; - tempClass.prototype.output = node.output ? JSON.parse(JSON.stringify(node.output)) : {}; + + tempClass.prototype.output = {} + if (node.output) { + for (var key in node.output) { + if (node.output[key].key === '') + tempClass.prototype.output[key] = {type: node.output[key].type, key: null} + else + tempClass.prototype.output[key] = JSON.parse(JSON.stringify(node.output[key])) + } + } } this.registerNode(tempClass); this.trigger('nodeadded', tempClass); - - this.exportNodes(); } p.editNode = function(oldName, newNode) { var node = this.nodes[oldName]; @@ -534,10 +615,7 @@ this.b3editor = this.b3editor || {}; } } - this.exportNodes(); this.trigger('nodechanged', node); - - this.exportNodes(); } p.removeNode = function(name) { // TODO: verify if it is b3 node @@ -554,8 +632,6 @@ this.b3editor = this.b3editor || {}; delete this.nodes[name]; this.trigger('noderemoved', node); - - this.exportNodes(); } p.addTree = function() { var block = new b3editor.Block(this.nodes['Root']); diff --git a/src/editor/models/Project.js b/src/editor/models/Project.js index aefb466..4046b47 100644 --- a/src/editor/models/Project.js +++ b/src/editor/models/Project.js @@ -40,7 +40,7 @@ this.b3editor = this.b3editor || {}; } p.findNodes = function() { - return this.walk(this.nodesPath, /\.json$/); + return this.walk(this.nodesPath, /\.nodes$/); } p.findTrees = function() { diff --git a/src/editor/utils/CustomJson.js b/src/editor/utils/CustomJson.js new file mode 100644 index 0000000..c9ca4d1 --- /dev/null +++ b/src/editor/utils/CustomJson.js @@ -0,0 +1,55 @@ +CustomJSON = {} + +CustomJSON.stringify = function(value, replacer, spaces, depth) { + if (!spaces) spaces = 0; + if (!depth) depth = 0; + var indent = ' '.repeat(spaces).repeat(depth); + var type = typeof value + var output = ''; + if (type == 'boolean') { + output += value.toString(); + } else if (type == 'number') { + output += value.toString(); + } else if (type == 'string') { + output += JSON.stringify(value); + } else if (value == null|| type == 'undefined') { + output += 'null'; + } else if (type == 'object') { + var newIndent = indent + ' '.repeat(spaces); + var nl = spaces > 0 ? '\n' : ''; + if (Array.isArray(value)) { + output += '['; + for (var i = 0; i < value.length; i++) { + output += nl + newIndent + CustomJSON.stringify(value[i], replacer, spaces, depth + 1); + if (i != value.length - 1) { + output += ','; + if (spaces == 0) + output += ' '; + } else { + output += nl+indent; + } + } + output += ']'; + } else { + output += '{'; + var keys = Object.keys(value); + for (var i = 0; i < keys.length; i++) { + var k = keys[i] + if (replacer) { + output += nl + newIndent + '"'+k+'": ' + replacer(k, value[k], spaces, depth); + } else { + output += nl + newIndent + '"'+k+'": ' + CustomJSON.stringify(value[k], replacer, spaces, depth + 1); + } + if (i != keys.length - 1) { + output += ','; + if (spaces == 0) + output += ' '; + } else { + output += nl+indent; + } + } + output += '}'; + } + } + return output +}
+ + -
\ + \ + -
\ + \ + -
key
-