|
@@ -1,5 +1,9 @@
|
|
|
/// <reference path="raphael.js" />
|
|
|
/// <reference path="action.js" />
|
|
|
+/// <reference path="contextmenu.js" />
|
|
|
+/// <reference path="viewertoolbar.js" />
|
|
|
+/// <reference path="parametersManager.js" />
|
|
|
+/// <reference path="utils.js" />
|
|
|
|
|
|
var AB;
|
|
|
(function (AB) {
|
|
@@ -13,145 +17,358 @@ var AB;
|
|
|
var scope = this;
|
|
|
|
|
|
// Members
|
|
|
- this.element = document.getElementById('Graph');
|
|
|
- this.graph = Raphael('Graph', (50 * screen.width) / 100, screen.height);
|
|
|
- this.root = this._createNode('Object name', Raphael.rgb(200, 200, 200), true);
|
|
|
- this.parametersElement = document.getElementById('Parameters');
|
|
|
+ this.element = document.getElementById("Graph");
|
|
|
+ this.graph = Raphael("Graph", (50 * screen.width) / 100, screen.height);
|
|
|
+ this.root = this._createNode("Object name", Raphael.rgb(255, 255, 255), true);
|
|
|
+
|
|
|
+ this.utils = new AB.Utils(this);
|
|
|
+ this.contextMenu = new AB.ContextMenu(this);
|
|
|
+ this.toolbar = new AB.ToolBar(this);
|
|
|
+ this.parametersManager = new AB.ParametersManager(this);
|
|
|
|
|
|
this.mousex = 0;
|
|
|
this.mousey = 0;
|
|
|
- this.objectName = '';
|
|
|
+ this.objectName = "";
|
|
|
+ this.editing = false;
|
|
|
+ this.zoom = 1.0;
|
|
|
|
|
|
- // Context menu (to remove a node)
|
|
|
- this.contextMenu = null;
|
|
|
this.selectedNode = null;
|
|
|
|
|
|
- this.graph.canvas.addEventListener('contextmenu', function (event) {
|
|
|
- var result = scope.traverseGraph(null, scope.mousex, scope.mousey);
|
|
|
- if (result.hit && result.element != scope.root.action) {
|
|
|
- scope.selectedNode = result.element;
|
|
|
- scope.contextMenu = scope._createNode('Remove', Raphael.rgb(255, 255, 255), true);
|
|
|
- scope._setNodePosition(scope.contextMenu, scope.mousex, scope.mousey);
|
|
|
- }
|
|
|
+ this.element.onmouseover = function () {
|
|
|
+ scope.editing = false;
|
|
|
+ };
|
|
|
+ this.element.onmouseout = function () {
|
|
|
+ scope.editing = true;
|
|
|
+ };
|
|
|
|
|
|
- window.event.returnValue = false;
|
|
|
+ document.addEventListener("click", function (event) {
|
|
|
+ if (!scope.contextMenu.showing) {
|
|
|
+ scope.onClick(event);
|
|
|
+ }
|
|
|
});
|
|
|
- document.onclick = function (event) {
|
|
|
- if (scope.contextMenu) {
|
|
|
- if (scope.contextMenu.isPointInside(scope.mousex, scope.mousey)) {
|
|
|
- scope.selectedNode.parent.removeChild(scope.selectedNode);
|
|
|
- scope._removeAction(scope.selectedNode, true);
|
|
|
- scope.update();
|
|
|
- }
|
|
|
-
|
|
|
- scope.selectedNode = null;
|
|
|
- scope._removeNode(scope.contextMenu);
|
|
|
+ document.ondblclick = function (event) {
|
|
|
+ var result = scope.traverseGraph(null, scope.mousex, scope.mousey);
|
|
|
+ if (result.hit && result.element.node != scope.root) {
|
|
|
+ scope.onReduce();
|
|
|
}
|
|
|
-
|
|
|
- scope.contextMenu = null;
|
|
|
};
|
|
|
document.onmousemove = function (event) {
|
|
|
scope.mousex = event.clientX - scope.graph.canvas.getBoundingClientRect().left;
|
|
|
scope.mousey = event.clientY - scope.graph.canvas.getBoundingClientRect().top;
|
|
|
-
|
|
|
- if (!scope.contextMenu)
|
|
|
- return;
|
|
|
-
|
|
|
- if (scope.contextMenu.isPointInside(scope.mousex, scope.mousey))
|
|
|
- scope.contextMenu.attr(scope.contextMenu.rect, 'fill', Raphael.rgb(140, 200, 230));
|
|
|
- else
|
|
|
- scope.contextMenu.attr(scope.contextMenu.rect, 'fill', Raphael.rgb(255, 255, 255));
|
|
|
};
|
|
|
|
|
|
// Set properties
|
|
|
this.element.scrollLeft = (this.element.getBoundingClientRect().width / 2) + (Graph.NODE_WIDTH / 2);
|
|
|
}
|
|
|
|
|
|
+ // Handles the click event. Called by this and this.contextmenu
|
|
|
+ // event: the event object
|
|
|
+ Graph.prototype.onClick = function (event) {
|
|
|
+ var result = this.traverseGraph(null, this.mousex, this.mousey, true);
|
|
|
+
|
|
|
+ if (!this.editing) {
|
|
|
+ if (this.selectedNode != null) {
|
|
|
+ var detached = this.isParentDetached(this.selectedNode);
|
|
|
+ this.selectedNode.node.attr(this.selectedNode.node.rect, "fill", this.getNodeColor(this.selectedNode, detached));
|
|
|
+ this.selectedNode = null;
|
|
|
+ if (!result.hit)
|
|
|
+ this.parametersManager.clearParameters();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (result.hit && result.element.node != this.root) {
|
|
|
+ this.selectedNode = result.element;
|
|
|
+ this.selectedNode.node.attr(this.selectedNode.node.rect, "fill", this.getSelectedNodeColor(this.selectedNode, detached));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!result.hit)
|
|
|
+ this.selectedNode = null;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Returns the node's color for a given type
|
|
|
+ // action : the action to test
|
|
|
+ // returns the node color
|
|
|
+ Graph.prototype.getSelectedNodeColor = function (action, detached) {
|
|
|
+ // Detached
|
|
|
+ if (action.detached || (action.node && action.node.detached) || detached)
|
|
|
+ return Raphael.rgb(96, 122, 14);
|
|
|
+
|
|
|
+ // Get proper color
|
|
|
+ var color = Raphael.rgb(255, 255, 255); // Default color for root
|
|
|
+ switch (action.type) {
|
|
|
+ case AB.ActionsBuilder.Type.TRIGGER: color = Raphael.rgb(41, 129, 255); break;
|
|
|
+ case AB.ActionsBuilder.Type.ACTION: color = Raphael.rgb(255, 220, 42); break;
|
|
|
+ case AB.ActionsBuilder.Type.FLOW_CONTROL: color = Raphael.rgb(255, 41, 53); break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return color;
|
|
|
+ }
|
|
|
+
|
|
|
+ Graph.prototype.getNodeColor = function (action, detached) {
|
|
|
+ if (action.detached || (action.node && action.node.detached) || detached)
|
|
|
+ return Raphael.rgb(96, 122, 14);
|
|
|
+
|
|
|
+ // Get proper color
|
|
|
+ var color = Raphael.rgb(255, 255, 255); // Default color for root
|
|
|
+ switch (action.type) {
|
|
|
+ case AB.ActionsBuilder.Type.TRIGGER: color = Raphael.rgb(133, 154, 185); break;
|
|
|
+ case AB.ActionsBuilder.Type.ACTION: color = Raphael.rgb(182, 185, 132); break;
|
|
|
+ case AB.ActionsBuilder.Type.FLOW_CONTROL: color = Raphael.rgb(185, 132, 140); break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return color;
|
|
|
+ }
|
|
|
+
|
|
|
+ Graph.prototype.isParentDetached = function (node) {
|
|
|
+ var parent = node.parent;
|
|
|
+ while (parent != null) {
|
|
|
+ if (parent.node.detached)
|
|
|
+ return true;
|
|
|
+ parent = parent.parent;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Adds a node to the graph
|
|
|
+ // parent : the parent of the added node
|
|
|
+ // listElement : the values
|
|
|
Graph.prototype.addNode = function (parent, listElement) {
|
|
|
- var color = Raphael.rgb(200, 200, 200);
|
|
|
- switch (listElement.type) {
|
|
|
- case AB.ActionsBuilder.Type.TRIGGER: color = Raphael.rgb(100, 149, 237); break;
|
|
|
- case AB.ActionsBuilder.Type.ACTION: color = Raphael.rgb(240, 230, 140); break;
|
|
|
- case AB.ActionsBuilder.Type.FLOW_CONTROL: color = Raphael.rgb(205, 92, 92); break;
|
|
|
+ var color = this.getNodeColor(listElement);
|
|
|
+
|
|
|
+ var n = this._createNode(listElement.name, color, parent.combine ? true : false);
|
|
|
+ if (listElement.name == "CombineAction") {
|
|
|
+ n.action.combine = true;
|
|
|
+
|
|
|
+ var hubElement = AB.ActionsBuilder.FlowAction.Hub;
|
|
|
+ var hubType = AB.ActionsBuilder.Type.FLOW_CONTROL;
|
|
|
+ n.action.hub = this._createNode(hubElement.name, this.getNodeColor({ type: hubType }), false);
|
|
|
+ n.action.hub.action.type = hubType;
|
|
|
+ n.action.addChild(n.action.hub.action);
|
|
|
+ this._createNodeAnimation(n.action.hub);
|
|
|
}
|
|
|
|
|
|
- var n = this._createNode(listElement.name, color, false);
|
|
|
n.action.name = listElement.name;
|
|
|
n.action.type = listElement.type;
|
|
|
+ n.detached = listElement.detached || false;
|
|
|
n.action.properties = listElement.properties;
|
|
|
|
|
|
for (var i = 0; i < listElement.properties.length; i++)
|
|
|
n.action.propertiesResults.push(listElement.properties[i].value);
|
|
|
|
|
|
- if (parent)
|
|
|
+ if (parent && !parent.combine) {
|
|
|
parent.addChild(n.action);
|
|
|
+ }
|
|
|
+ else if (parent.combine) {
|
|
|
+ // Create hub
|
|
|
+ parent.combineArray.push(n.action);
|
|
|
+ n.action.parent = parent;
|
|
|
+ parent.node.attr(parent.node.text, "text", "");
|
|
|
+ }
|
|
|
|
|
|
this._createNodeAnimation(n);
|
|
|
- }
|
|
|
|
|
|
- Graph.prototype.update = function (node, yOffset, childID, rootChildID) {
|
|
|
- if (!yOffset)
|
|
|
- yOffset = 10;
|
|
|
- else
|
|
|
- yOffset += Graph.VERTICAL_OFFSET;
|
|
|
+ return n;
|
|
|
+ }
|
|
|
|
|
|
- if (!node) node = this.root;
|
|
|
+ // Updates the graph
|
|
|
+ Graph.prototype.update = function () {
|
|
|
+ var scope = this;
|
|
|
+ var rootChildID = 0;
|
|
|
|
|
|
- if (node == this.root) {
|
|
|
- this._setNodePosition(node, (this.graph.width / 2) - (Graph.NODE_WIDTH / 2), yOffset);
|
|
|
- }
|
|
|
- else {
|
|
|
- var length = node.action.parent.children.length;
|
|
|
- var parentx = node.action.parent.node.attr(node.action.parent.node.rect, 'x');
|
|
|
- var totalLength = Graph.NODE_WIDTH * length;
|
|
|
- var offset = ( Graph.NODE_WIDTH * (length - childID - 1) );
|
|
|
- var posx = parentx;
|
|
|
- posx += offset - ((Graph.NODE_WIDTH / 2) * (length - 1));
|
|
|
-
|
|
|
- this._setNodePosition(node, posx, yOffset);
|
|
|
- this._setLine(node.action);
|
|
|
+ var setNodeSize = function(node) {
|
|
|
+ if (node.minimized) {
|
|
|
+ node.attr(node.rect, "width", Graph.NODE_MINIMIZED_WIDTH * scope.zoom);
|
|
|
+ node.attr(node.rect, "height", Graph.NODE_HEIGHT * scope.zoom);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ node.attr(node.rect, "width", Graph.NODE_WIDTH * scope.zoom);
|
|
|
+ node.attr(node.rect, "height", Graph.NODE_HEIGHT * scope.zoom);
|
|
|
+ }
|
|
|
+ node.attr(node.text, "font-size", 11 * scope.zoom);
|
|
|
}
|
|
|
|
|
|
- for (var i = 0; i < node.action.children.length; i++) {
|
|
|
- if (node == this.root)
|
|
|
- rootChildID = i;
|
|
|
+ var onUpdate = function (action, yOffset) {
|
|
|
+ var node = action.node;
|
|
|
+ var parent = action.parent ? action.parent.node : null;
|
|
|
+
|
|
|
+ // Set size of node according to zoom
|
|
|
+ if (action.combine) {
|
|
|
+ var length = 0;
|
|
|
+ for (var i = 0; i < action.combineArray.length; i++) {
|
|
|
+ var n = action.combineArray[i].node;
|
|
|
+
|
|
|
+ setNodeSize(n);
|
|
|
+
|
|
|
+ length += n.attr(n.rect, "width");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ setNodeSize(node);
|
|
|
+
|
|
|
+ // Set position
|
|
|
+ if (parent) {
|
|
|
+ scope._setNodePosition(node, parent.attr(parent.rect, "x"), yOffset);
|
|
|
+ scope._setLine(action);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Calculate total width
|
|
|
+ var totalWidth = 0;
|
|
|
+ for (var i = 0; i < action.children.length; i++) {
|
|
|
+ var n = action.children[i].node;
|
|
|
+ totalWidth += n.attr(n.rect, "width");
|
|
|
+ }
|
|
|
+
|
|
|
+ var nodeWidth = node.attr(node.rect, "width");
|
|
|
+ var nodex = node.attr(node.rect, "x");
|
|
|
+
|
|
|
+ var startingXPos = nodex + (nodeWidth / 2) - (totalWidth / 2);
|
|
|
|
|
|
- this.update(node.action.children[i].node, yOffset, i, rootChildID);
|
|
|
+ // Recursively set position of children
|
|
|
+ for (var i = 0; i < action.children.length; i++) {
|
|
|
|
|
|
- var n = node.action.children[i].node;
|
|
|
- if (n.action.children.length > 1) {
|
|
|
- for (var j = rootChildID; j >= 0; j--) {
|
|
|
- if (node.action.children.length < 2 || n.action.children.length < 2)
|
|
|
- continue;
|
|
|
+ var n = action.children[i].node;
|
|
|
+ var newx = startingXPos;
|
|
|
+ var newy = yOffset + Graph.VERTICAL_OFFSET * scope.zoom;
|
|
|
|
|
|
- var rx = this.root.attr(this.root.rect, 'x');
|
|
|
- var x = this.root.action.children[j].node.attr(this.root.action.children[j].node.rect, 'x');
|
|
|
- var y = this.root.action.children[j].node.attr(this.root.action.children[j].node.rect, 'y');
|
|
|
+ onUpdate(n.action, newy);
|
|
|
|
|
|
- x -= ((Graph.NODE_WIDTH / 2) * (node.action.children.length - 1)) * (x > rx ? -1 : 1);
|
|
|
+ scope._setNodePosition(n, newx, newy);
|
|
|
+ scope._setLine(n.action);
|
|
|
+ startingXPos += n.attr(n.rect, "width");
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- this._setNodePosition(this.root.action.children[j].node, x, y);
|
|
|
- this._setLine(this.root.action.children[j]);
|
|
|
+ var onPosition = function (action, maxWidth) {
|
|
|
|
|
|
+ var total = 0;
|
|
|
+ var start = action.combine && action.combineArray.length > 1 ? 0 : 1;
|
|
|
+
|
|
|
+ for (var i = start; i < action.children.length; i++) {
|
|
|
+ var n = action.children[i].node;
|
|
|
+
|
|
|
+ if (action.combine) {
|
|
|
+ for (var j = 1; j < action.combineArray.length; j++) {
|
|
|
+ var cn = action.combineArray[j].node;
|
|
|
+ total += cn.attr(cn.rect, "width");
|
|
|
+ }
|
|
|
}
|
|
|
+ else
|
|
|
+ total += n.attr(n.rect, "width");
|
|
|
+ }
|
|
|
+
|
|
|
+ if (total > maxWidth) {
|
|
|
+ maxWidth = total;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (var i = 0; i < action.children.length; i++) {
|
|
|
+ maxWidth = onPosition(action.children[i], maxWidth);
|
|
|
}
|
|
|
-
|
|
|
+
|
|
|
+ return maxWidth;
|
|
|
+
|
|
|
+ };
|
|
|
+
|
|
|
+ // Set root node position / scale and recursively set position of its children
|
|
|
+ this._setNodePosition(this.root, (this.graph.width / 2) - (Graph.NODE_WIDTH / 2) * this.zoom, 10);
|
|
|
+ this.root.attr(this.root.rect, "width", Graph.NODE_WIDTH * scope.zoom);
|
|
|
+ this.root.attr(this.root.rect, "height", Graph.NODE_HEIGHT * scope.zoom);
|
|
|
+
|
|
|
+ onUpdate(this.root.action, 10 * this.zoom);
|
|
|
+
|
|
|
+ // Get total widths
|
|
|
+ var widths = new Array();
|
|
|
+ /*
|
|
|
+ object:
|
|
|
+ {
|
|
|
+ triggerWidth: number
|
|
|
+ childrenWidths: new Array<number>()
|
|
|
+ }
|
|
|
+ */
|
|
|
+
|
|
|
+ for (var i = 0; i < scope.root.action.children.length; i++) {
|
|
|
+ var a = scope.root.action.children[i];
|
|
|
+
|
|
|
+ var obj = {
|
|
|
+ triggerWidth: onPosition(a, 0),
|
|
|
+ childrenWidths: new Array()
|
|
|
+ };
|
|
|
+
|
|
|
+ for (var j = 0; j < a.children.length; j++) {
|
|
|
+ var cw = onPosition(a.children[j], 0);
|
|
|
+ obj.childrenWidths.push(cw);
|
|
|
+ obj.triggerWidth += cw;
|
|
|
+ }
|
|
|
+
|
|
|
+ widths.push(obj);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Set new position of children
|
|
|
+ var rx = scope.root.attr(scope.root.rect, "x");
|
|
|
+ var rwidth = 0;
|
|
|
+ var cwidth = 0;
|
|
|
+
|
|
|
+ for (var i = 0; i < scope.root.action.children.length; i++) {
|
|
|
+ var a = scope.root.action.children[i];
|
|
|
+ var tx = a.node.attr(a.node.rect, "x");
|
|
|
+
|
|
|
+ for (var j = 0; j < a.children.length; j++) {
|
|
|
+ var n = a.children[j].node;
|
|
|
+ var x = n.attr(n.rect, "x");
|
|
|
+ var y = n.attr(n.rect, "y");
|
|
|
+ var inverse = x >= tx ? 1 : -1;
|
|
|
+
|
|
|
+ scope._setNodePosition(n, x + (scope.root.action.children.length > 1 ? widths[i].childrenWidths[j] / 1.8 : 0) * inverse, y);
|
|
|
+ scope._setLine(n.action);
|
|
|
+
|
|
|
+ scope._resizeCanvas(n);
|
|
|
+ cwidth += widths[i].childrenWidths[j] / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (scope.root.action.children.length > 1) {
|
|
|
+ var n = a.node;
|
|
|
+ var x = n.attr(n.rect, "x");
|
|
|
+ var y = n.attr(n.rect, "y");
|
|
|
+ var inverse = x >= rx && i == 0 && !n.minimized ? 1 : -1;
|
|
|
+
|
|
|
+ scope._setNodePosition(n, x + rwidth + (i > 1 ? widths[i - 1].triggerWidth / 1.8 : 0) + (widths[i].triggerWidth / 1.8) * inverse, y);
|
|
|
+ scope._setLine(n.action);
|
|
|
+
|
|
|
+ scope._resizeCanvas(n);
|
|
|
+ }
|
|
|
+
|
|
|
+ rwidth += widths[i].triggerWidth / 2;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Graph.prototype.createJSON = function (root, graph) {
|
|
|
+ // Creates the JSON according to the graph
|
|
|
+ // root: the root object to start with
|
|
|
+ Graph.prototype.createJSON = function (root) {
|
|
|
if (!root) root = this.root.action;
|
|
|
- if (!graph) graph = {};
|
|
|
|
|
|
var action = {};
|
|
|
action.type = root.type;
|
|
|
action.name = root.name;
|
|
|
+ action.detached = root.node.detached;
|
|
|
action.children = new Array();
|
|
|
+ action.combine = new Array();
|
|
|
|
|
|
action.properties = new Array();
|
|
|
- for (var i = 0; i < root.properties.length; i++)
|
|
|
+ for (var i = 0; i < root.properties.length; i++) {
|
|
|
action.properties[i] = { name: root.properties[i].text, value: root.propertiesResults[i] };
|
|
|
+ if (root.properties[i].targetType != null)
|
|
|
+ action.properties[i].targetType = root.properties[i].targetType;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (root.combine) {
|
|
|
+ for (var i = 0; i < root.combineArray.length; i++) {
|
|
|
+ var combinedAction = root.combineArray[i];
|
|
|
+ action.combine.push(this.createJSON(combinedAction, action));
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ if (root.combine)
|
|
|
+ root = root.children[0]; // The hub
|
|
|
+
|
|
|
for (var i = 0; i < root.children.length; i++) {
|
|
|
action.children.push(this.createJSON(root.children[i], action));
|
|
|
}
|
|
@@ -159,45 +376,80 @@ var AB;
|
|
|
return action;
|
|
|
}
|
|
|
|
|
|
- Graph.prototype.loadFromJSON = function (graph) {
|
|
|
+ // Loads a graph from a JSON
|
|
|
+ // Graph: the root object's graph
|
|
|
+ // startNode: the start node to where begin the load
|
|
|
+ Graph.prototype.loadFromJSON = function (graph, startNode) {
|
|
|
var scope = this;
|
|
|
|
|
|
- for (var i = 0; i < this.root.action.children.length; i++)
|
|
|
- this._removeAction(this.root.action.children[i], true);
|
|
|
-
|
|
|
- this.root.action.clearChildren();
|
|
|
+ // If startNode is null, means it replaces all the graph
|
|
|
+ // If not, it comes from a copy/paste
|
|
|
+ if (startNode == null) {
|
|
|
+ for (var i = 0; i < this.root.action.children.length; i++)
|
|
|
+ this._removeAction(this.root.action.children[i], true);
|
|
|
|
|
|
- graph = JSON.parse(graph);
|
|
|
- console.log(graph);
|
|
|
+ this.root.action.clearChildren();
|
|
|
+ }
|
|
|
|
|
|
- var load = function (root, parent) {
|
|
|
+ var load = function (root, parent, detached, combine) {
|
|
|
if (!parent) parent = scope.root.action;
|
|
|
if (!root) root = graph;
|
|
|
+ if (!detached) detached = false;
|
|
|
+ if (!combine) combine = false;
|
|
|
|
|
|
- if (root.type != AB.ActionsBuilder.Type.OBJECT) { // Means it is the root (the edited object)
|
|
|
+ var n = null; // Not going to be created
|
|
|
+
|
|
|
+ if (root.type != AB.ActionsBuilder.Type.OBJECT) { // Means it is not the root (the edited object)
|
|
|
var e = {};
|
|
|
e.type = root.type;
|
|
|
e.name = root.name;
|
|
|
+ e.detached = root.detached;
|
|
|
+ e.combine = root.combine.length > 0;
|
|
|
e.properties = new Array();
|
|
|
+ e.combineArray = new Array();
|
|
|
|
|
|
- for (var i = 0; i < root.properties.length; i++)
|
|
|
+ for (var i = 0; i < root.properties.length; i++) {
|
|
|
e.properties.push({ text: root.properties[i].name, value: root.properties[i].value });
|
|
|
+ if (root.properties[i].targetType != null) {
|
|
|
+ e.properties[e.properties.length - 1].targetType = root.properties[i].targetType;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- var n = scope.addNode(parent, e);
|
|
|
- parent = parent.children[parent.children.length - 1];
|
|
|
+ n = scope.addNode(parent, e);
|
|
|
+ if (detached)
|
|
|
+ n.attr(n.rect, "fill", scope.getNodeColor(n.action));
|
|
|
+ else
|
|
|
+ detached = n.detached;
|
|
|
+
|
|
|
+ // If combine array length > 0, it is a combine action
|
|
|
+ for (var i = 0; i < root.combine.length; i++) {
|
|
|
+ load(root.combine[i], n.action, detached, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!combine)
|
|
|
+ parent = parent.children[parent.children.length - 1];
|
|
|
+ else
|
|
|
+ n.action.parent = null;
|
|
|
}
|
|
|
|
|
|
for (var i = 0; i < root.children.length; i++) {
|
|
|
- load(root.children[i], parent);
|
|
|
+ load(root.children[i], n && n.action.combine ? n.action.hub.action : parent, root.detached, false);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- load();
|
|
|
+ load(graph, startNode);
|
|
|
this.update();
|
|
|
}
|
|
|
|
|
|
- Graph.prototype.traverseGraph = function (start, x, y) {
|
|
|
+ // Traverse the graph and returns if hit a node
|
|
|
+ // start: the start node to start traverse
|
|
|
+ // x: the mouse's x position
|
|
|
+ // y: the mouse's y position
|
|
|
+ // traverseCombine: if check the combine nodes
|
|
|
+ Graph.prototype.traverseGraph = function (start, x, y, traverseCombine) {
|
|
|
if (!start) start = this.root.action;
|
|
|
+ if (traverseCombine == null) traverseCombine = false;
|
|
|
+
|
|
|
var result = {
|
|
|
hit: true,
|
|
|
element: start
|
|
@@ -211,10 +463,21 @@ var AB;
|
|
|
if (start.children[i].node.isPointInside(x, y)) {
|
|
|
result.hit = true;
|
|
|
result.element = start.children[i];
|
|
|
+
|
|
|
+ if (start.children[i].combine && traverseCombine) {
|
|
|
+ var a = start.children[i];
|
|
|
+ for (var j = 0; j < a.combineArray.length; j++) {
|
|
|
+ if (a.combineArray[j].node.isPointInside(x, y)) {
|
|
|
+ result.element = a.combineArray[j];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
- result = this.traverseGraph(start.children[i], x, y);
|
|
|
+ result = this.traverseGraph(start.children[i], x, y, traverseCombine);
|
|
|
if (result.hit)
|
|
|
return result;
|
|
|
|
|
@@ -228,43 +491,120 @@ var AB;
|
|
|
//
|
|
|
// Private functions
|
|
|
//
|
|
|
+
|
|
|
+ // Sets the given node's line
|
|
|
+ // If commented, the line isn't setted by hidden
|
|
|
Graph.prototype._setLine = function (element) {
|
|
|
- var linex = element.node.attr(element.node.rect, 'x') + Graph.NODE_WIDTH / 2;
|
|
|
- var liney = element.node.attr(element.node.rect, 'y');
|
|
|
- var linex2 = element.parent.node.attr(element.parent.node.rect, 'x') + Graph.NODE_WIDTH / 2;
|
|
|
- var liney2 = element.parent.node.attr(element.parent.node.rect, 'y') + Graph.NODE_HEIGHT;
|
|
|
+ var n = element.node;
|
|
|
+ var nodeWidth = n.attr(n.rect, "width");
|
|
|
+ var nodeHeight = n.attr(n.rect, "height");
|
|
|
+ var nodex = n.attr(n.rect, "x");
|
|
|
+ var nodey = n.attr(n.rect, "y");
|
|
|
+
|
|
|
+ if (n.detached) {
|
|
|
+ n.attr(n.line, "path", ["M", nodex, nodey, "L", nodex, nodey]);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ var linex = n.attr(n.rect, "x") + nodeWidth / 2;
|
|
|
+ var liney = n.attr(n.rect, "y");
|
|
|
+
|
|
|
+ var p = element.parent.node;
|
|
|
+ var parentWidth = p.attr(p.rect, "width");
|
|
|
+ var parentHeight = p.attr(p.rect, "height");
|
|
|
+ var parentx = p.attr(p.rect, "x");
|
|
|
+ var parenty = p.attr(p.rect, "y");
|
|
|
+
|
|
|
+ var liney2 = liney - (liney - parenty - parentHeight) / 2;
|
|
|
+ var linex3 = parentx + parentWidth / 2;
|
|
|
+ var liney4 = parenty + parentHeight;
|
|
|
+
|
|
|
+ n.attr(n.line, "path", ["M", linex, liney, "L", linex, liney2, "L", linex3, liney2, "L", linex3, liney4]);
|
|
|
+ n.attr(n.line, "stroke", this.getSelectedNodeColor(element, element.node.detached));
|
|
|
|
|
|
- element.node.attr(element.node.line, 'path', 'M' + linex + ' ' + liney + 'L' + linex2 + ' ' + liney2);
|
|
|
}
|
|
|
|
|
|
+ // Sets the given node's position
|
|
|
+ // Applies changements on its children
|
|
|
Graph.prototype._setNodePosition = function (node, x, y) {
|
|
|
- var offsetx = node.attr(node.rect, 'x') - x;
|
|
|
+ var offsetx = node.attr(node.rect, "x") - x;
|
|
|
+
|
|
|
+ node.attr(node.rect, "x", x);
|
|
|
+ node.attr(node.rect, "y", y);
|
|
|
|
|
|
- node.attr(node.rect, 'x', x);
|
|
|
- node.attr(node.rect, 'y', y);
|
|
|
+ var bbox = node.text.getBBox();
|
|
|
+ var textWidth = 0;
|
|
|
+ if (bbox)
|
|
|
+ textWidth = node.text.getBBox().width;
|
|
|
|
|
|
- node.attr(node.text, 'x', x + 5);
|
|
|
- node.attr(node.text, 'y', y + Graph.NODE_HEIGHT / 2);
|
|
|
+ node.attr(node.text, "x", x + node.attr(node.rect, "width") / 2 - textWidth / 2);
|
|
|
+ node.attr(node.text, "y", y + node.attr(node.rect, "height") / 2);
|
|
|
+
|
|
|
+ // Set combine nodes positions
|
|
|
+ if (node.action.combine) {
|
|
|
+ var length = 0;
|
|
|
+ for (var i = 0; i < node.action.combineArray.length; i++) {
|
|
|
+ var a = node.action.combineArray[i];
|
|
|
+ var n = a.node;
|
|
|
+
|
|
|
+ n.attr(n.rect, "x", node.attr(node.rect, "x") + length);
|
|
|
+ n.attr(n.rect, "y", node.attr(node.rect, "y"));
|
|
|
+
|
|
|
+ textWidth = n.text.getBBox().width;
|
|
|
+ n.attr(n.text, "x", n.attr(n.rect, "x") + n.attr(n.rect, "width") / 2 - textWidth / 2);
|
|
|
+ n.attr(n.text, "y", y + Graph.NODE_HEIGHT / 2);
|
|
|
+
|
|
|
+ length += n.attr(n.rect, "width");
|
|
|
+ }
|
|
|
+
|
|
|
+ node.attr(node.rect, "width", length);
|
|
|
+ }
|
|
|
|
|
|
for (var i = 0; i < node.action.children.length; i++) {
|
|
|
- this._setNodePosition(node.action.children[i].node, node.action.children[i].node.attr(node.action.children[i].node.rect, 'x') - offsetx, y + Graph.VERTICAL_OFFSET);
|
|
|
+ this._setNodePosition(node.action.children[i].node, node.action.children[i].node.attr(node.action.children[i].node.rect, "x") - offsetx, y + Graph.VERTICAL_OFFSET);
|
|
|
this._setLine(node.action.children[i]);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- Graph.prototype._removeNode = function (element) {
|
|
|
- element.rect.remove();
|
|
|
- element.text.remove();
|
|
|
- if (element.line)
|
|
|
- element.line.remove();
|
|
|
+ // Resizes the canvas if the node is outside the current canvas size
|
|
|
+ Graph.prototype._resizeCanvas = function (node) {
|
|
|
+ var x = node.attr(node.rect, "x");
|
|
|
+ var y = node.attr(node.rect, "y");
|
|
|
+
|
|
|
+ if (x > 0 + Graph.NODE_WIDTH && x < this.graph.width - Graph.NODE_WIDTH && y < this.graph.height)
|
|
|
+ return;
|
|
|
+
|
|
|
+ this.graph.setSize(this.graph.width + 500, this.graph.height + 500);
|
|
|
+ this._setNodePosition(this.root, (this.graph.width / 2) - (Graph.NODE_WIDTH / 2), 10);
|
|
|
+
|
|
|
+ this.element.scrollLeft = (this.graph.width / 2) - this.element.getBoundingClientRect().width / 2;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Removes a node
|
|
|
+ // node : the node to remove
|
|
|
+ Graph.prototype._removeNode = function (node) {
|
|
|
+ node.rect.remove();
|
|
|
+ node.text.remove();
|
|
|
+ if (node.line)
|
|
|
+ node.line.remove();
|
|
|
}
|
|
|
|
|
|
+ // Remove an action from the graph
|
|
|
+ // action : the action to remove
|
|
|
+ // removeChildren : if true, it deletes the branch
|
|
|
Graph.prototype._removeAction = function (action, removeChildren) {
|
|
|
if (!removeChildren)
|
|
|
removeChildren = false;
|
|
|
|
|
|
this._removeNode(action.node);
|
|
|
|
|
|
+ if (action.combine) {
|
|
|
+ for (var i = 0; i < action.combineArray.length; i++) {
|
|
|
+ this._removeNode(action.combineArray[i].node);
|
|
|
+ }
|
|
|
+ action.combineArray = new Array();
|
|
|
+ }
|
|
|
+
|
|
|
if (removeChildren) {
|
|
|
for (var i = 0; i < action.children.length; i++)
|
|
|
this._removeAction(action.children[i], removeChildren);
|
|
@@ -272,57 +612,39 @@ var AB;
|
|
|
action.clearChildren();
|
|
|
}
|
|
|
else {
|
|
|
- for (var i = 0; i < action.children.length; i++)
|
|
|
+ for (var i = 0; i < action.children.length; i++) {
|
|
|
+ action.parent.addChild(action.children[i]);
|
|
|
action.children[i].parent = action.parent;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // Creates a node
|
|
|
+ // text : the text/name of the node
|
|
|
+ // color : the node's color
|
|
|
+ // noLine : if the node has parent then draw a line, or not
|
|
|
Graph.prototype._createNode = function (text, color, noLine) {
|
|
|
var n = new AB.Node();
|
|
|
- n.rect = this.graph.rect(0, 0, Graph.NODE_WIDTH, Graph.NODE_HEIGHT, 5);
|
|
|
+ n.rect = this.graph.rect(20, 20, Graph.NODE_WIDTH, Graph.NODE_HEIGHT, 0);
|
|
|
n.text = this.graph.text(0, 0, text);
|
|
|
- if (!noLine)
|
|
|
- n.line = this.graph.path('M10 10L90 90');
|
|
|
- n.action = new AB.Action(n);
|
|
|
-
|
|
|
- n.rect.attr('fill', color);
|
|
|
- n.text.attr('font-size', 12);
|
|
|
- n.text.attr('text-anchor', 'start');
|
|
|
-
|
|
|
- return n;
|
|
|
- }
|
|
|
|
|
|
- Graph.prototype._createParameters = function (element) {
|
|
|
- var onChange = function (input, propertyID) {
|
|
|
- return function () {
|
|
|
- var value = input.value;
|
|
|
- element.action.propertiesResults[propertyID] = value;
|
|
|
- }
|
|
|
+ if (!noLine) {
|
|
|
+ n.line = this.graph.path("M10 10L90 90");
|
|
|
+ n.line.attr("stroke", color);
|
|
|
}
|
|
|
|
|
|
- while (this.parametersElement.childNodes.length)
|
|
|
- this.parametersElement.removeChild(this.parametersElement.firstChild);
|
|
|
+ n.action = new AB.Action(n);
|
|
|
|
|
|
- var p = element.action.properties;
|
|
|
- var pr = element.action.propertiesResults;
|
|
|
+ n.rect.attr("fill", color);
|
|
|
+ n.text.attr("font-size", 11);
|
|
|
+ n.text.attr("text-anchor", "start");
|
|
|
+ n.text.attr("font-family", "Sinkin Sans Light");
|
|
|
|
|
|
- if (p.length == 0)
|
|
|
- return;
|
|
|
-
|
|
|
- for (var i = 0; i < p.length; i++) {
|
|
|
- var el = document.createElement('input');
|
|
|
- el.setAttribute('value', pr[i]);
|
|
|
- el.onchange = onChange(el, i);
|
|
|
-
|
|
|
- var text = document.createElement('a');
|
|
|
- text.text = p[i].text;
|
|
|
- this.parametersElement.appendChild(document.createElement('br'));
|
|
|
- this.parametersElement.appendChild(text);
|
|
|
- this.parametersElement.appendChild(document.createElement('br'));
|
|
|
- this.parametersElement.appendChild(el);
|
|
|
- }
|
|
|
+ return n;
|
|
|
}
|
|
|
|
|
|
+ // Creates the animations for a node
|
|
|
+ // element: the node
|
|
|
Graph.prototype._createNodeAnimation = function (element) {
|
|
|
var scope = this;
|
|
|
var mousex, mousey;
|
|
@@ -336,62 +658,78 @@ var AB;
|
|
|
};
|
|
|
|
|
|
var onStart = function (x, y, event) {
|
|
|
+ if (element.minimized)
|
|
|
+ return;
|
|
|
+
|
|
|
mousex = x;
|
|
|
mousey = y;
|
|
|
|
|
|
if (finished) {
|
|
|
- elementx = element.attr(element.rect, 'x');
|
|
|
- elementy = element.attr(element.rect, 'y');
|
|
|
+ elementx = element.attr(element.rect, "x");
|
|
|
+ elementy = element.attr(element.rect, "y");
|
|
|
}
|
|
|
finished = false;
|
|
|
|
|
|
- element.rect.animate({
|
|
|
- x: element.attr(element.rect, 'x') - 10,
|
|
|
- y: element.attr(element.rect, 'y') - 5,
|
|
|
- width: Graph.NODE_WIDTH + 20,
|
|
|
- height: Graph.NODE_HEIGHT + 10,
|
|
|
- opacity: 0.25
|
|
|
- }, 500, '>');
|
|
|
+ if (!element.minimized) {
|
|
|
+ element.rect.animate({
|
|
|
+ x: element.attr(element.rect, "x") - 10,
|
|
|
+ y: element.attr(element.rect, "y") - 5,
|
|
|
+ width: element.minimized ? Graph.NODE_MINIMIZED_WIDTH + 20 : Graph.NODE_WIDTH + 20,
|
|
|
+ height: Graph.NODE_HEIGHT + 10,
|
|
|
+ opacity: 0.25
|
|
|
+ }, 500, ">");
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
var onEnd = function (event) {
|
|
|
- element.rect.animate({
|
|
|
- x: elementx,
|
|
|
- y: elementy,
|
|
|
- width: Graph.NODE_WIDTH,
|
|
|
- height: Graph.NODE_HEIGHT,
|
|
|
- opacity: 1.0
|
|
|
- }, 500, '>', function () { finished = true; });
|
|
|
|
|
|
+ if (!element.minimized) {
|
|
|
+ element.rect.animate({
|
|
|
+ x: elementx,
|
|
|
+ y: elementy,
|
|
|
+ width: element.minimized ? Graph.NODE_MINIMIZED_WIDTH : Graph.NODE_WIDTH,
|
|
|
+ height: Graph.NODE_HEIGHT,
|
|
|
+ opacity: 1.0
|
|
|
+ }, 500, ">", function () { finished = true; });
|
|
|
+ }
|
|
|
var x = mousex - scope.graph.canvas.getBoundingClientRect().left;
|
|
|
var y = mousey - scope.graph.canvas.getBoundingClientRect().top;
|
|
|
- var dragResult = scope.traverseGraph(null, x, y);
|
|
|
-
|
|
|
- var json = JSON.stringify(scope.createJSON());
|
|
|
- console.log(json);
|
|
|
+ var dragResult = scope.traverseGraph(null, x, y, true);
|
|
|
|
|
|
if (dragResult.hit && dragResult.element == element.action || !dragResult.hit) {
|
|
|
- scope._createParameters(element);
|
|
|
+ scope.parametersManager.createParameters(element);
|
|
|
}
|
|
|
else {
|
|
|
+ if (dragResult.element.children.length > 0 && dragResult.element.type != AB.ActionsBuilder.Type.TRIGGER)
|
|
|
+ return;
|
|
|
+
|
|
|
if (element.action.type == AB.ActionsBuilder.Type.TRIGGER && dragResult.element != scope.root.action)
|
|
|
return;
|
|
|
|
|
|
if (element.action.type == AB.ActionsBuilder.Type.ACTION && dragResult.element == scope.root.action)
|
|
|
return;
|
|
|
|
|
|
- if (element.action.type == AB.ActionsBuilder.Type.FLOW_CONTROL && dragResult.element == scope.root.action)
|
|
|
+ if (element.action.type == AB.ActionsBuilder.Type.FLOW_CONTROL && (dragResult.element == scope.root.action || dragResult.element.type == AB.ActionsBuilder.Type.FLOW_CONTROL))
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (element.action.parent && element.action.parent.combine) // Musn't move hubs
|
|
|
+ return;
|
|
|
+
|
|
|
+ if (element.action == dragResult.element.parent)
|
|
|
return;
|
|
|
|
|
|
// Reset node
|
|
|
element.rect.stop(element.rect.animation);
|
|
|
- element.attr(element.rect, 'opacity', 1.0);
|
|
|
- element.attr(element.rect, 'width', Graph.NODE_WIDTH);
|
|
|
- element.attr(element.rect, 'height', Graph.NODE_HEIGHT);
|
|
|
+ element.attr(element.rect, "opacity", 1.0);
|
|
|
+ element.attr(element.rect, "width", Graph.NODE_WIDTH);
|
|
|
+ element.attr(element.rect, "height", Graph.NODE_HEIGHT);
|
|
|
|
|
|
- element.action.parent.removeChild(element.action);
|
|
|
- dragResult.element.addChild(element.action);
|
|
|
- scope.update();
|
|
|
+ if (element.action.parent) {
|
|
|
+ element.action.parent.removeChild(element.action);
|
|
|
+ dragResult.element.addChild(element.action);
|
|
|
+ scope.update();
|
|
|
+ scope._createNodeAnimation(element);
|
|
|
+ }
|
|
|
}
|
|
|
};
|
|
|
|
|
@@ -399,6 +737,7 @@ var AB;
|
|
|
element.text.drag(onMove, onStart, onEnd);
|
|
|
}
|
|
|
|
|
|
+ Graph.NODE_MINIMIZED_WIDTH = 50;
|
|
|
Graph.NODE_WIDTH = 150;
|
|
|
Graph.NODE_HEIGHT = 25;
|
|
|
Graph.VERTICAL_OFFSET = 70;
|