///
///
var AB;
(function (AB) {
var Graph = (function () {
//
// Public functions
//
function Graph() {
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.mousex = 0;
this.mousey = 0;
this.objectName = '';
// 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);
}
window.event.returnValue = false;
});
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);
}
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);
}
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 n = this._createNode(listElement.name, color, false);
n.action.name = listElement.name;
n.action.type = listElement.type;
n.action.properties = listElement.properties;
for (var i = 0; i < listElement.properties.length; i++)
n.action.propertiesResults.push(listElement.properties[i].value);
if (parent)
parent.addChild(n.action);
this._createNodeAnimation(n);
}
Graph.prototype.update = function (node, yOffset, childID, rootChildID) {
if (!yOffset)
yOffset = 10;
else
yOffset += Graph.VERTICAL_OFFSET;
if (!node) node = this.root;
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);
}
for (var i = 0; i < node.action.children.length; i++) {
if (node == this.root)
rootChildID = i;
this.update(node.action.children[i].node, yOffset, i, rootChildID);
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 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');
x -= ((Graph.NODE_WIDTH / 2) * (node.action.children.length - 1)) * (x > rx ? -1 : 1);
this._setNodePosition(this.root.action.children[j].node, x, y);
this._setLine(this.root.action.children[j]);
}
}
}
}
Graph.prototype.createJSON = function (root, graph) {
if (!root) root = this.root.action;
if (!graph) graph = {};
var action = {};
action.type = root.type;
action.name = root.name;
action.children = new Array();
action.properties = new Array();
for (var i = 0; i < root.properties.length; i++)
action.properties[i] = { name: root.properties[i].text, value: root.propertiesResults[i] };
for (var i = 0; i < root.children.length; i++) {
action.children.push(this.createJSON(root.children[i], action));
}
return action;
}
Graph.prototype.loadFromJSON = function (graph) {
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();
graph = JSON.parse(graph);
console.log(graph);
var load = function (root, parent) {
if (!parent) parent = scope.root.action;
if (!root) root = graph;
if (root.type != AB.ActionsBuilder.Type.OBJECT) { // Means it is the root (the edited object)
var e = {};
e.type = root.type;
e.name = root.name;
e.properties = new Array();
for (var i = 0; i < root.properties.length; i++)
e.properties.push({ text: root.properties[i].name, value: root.properties[i].value });
var n = scope.addNode(parent, e);
parent = parent.children[parent.children.length - 1];
}
for (var i = 0; i < root.children.length; i++) {
load(root.children[i], parent);
}
}
load();
this.update();
}
Graph.prototype.traverseGraph = function (start, x, y) {
if (!start) start = this.root.action;
var result = {
hit: true,
element: start
};
if (start.node.isPointInside(x, y))
return result;
for (var i = 0; i < start.children.length; i++) {
if (start.children[i].node.isPointInside(x, y)) {
result.hit = true;
result.element = start.children[i];
return result;
}
result = this.traverseGraph(start.children[i], x, y);
if (result.hit)
return result;
}
result.hit = false;
result.element = null;
return result;
}
//
// Private functions
//
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;
element.node.attr(element.node.line, 'path', 'M' + linex + ' ' + liney + 'L' + linex2 + ' ' + liney2);
}
Graph.prototype._setNodePosition = function (node, x, y) {
var offsetx = node.attr(node.rect, 'x') - x;
node.attr(node.rect, 'x', x);
node.attr(node.rect, 'y', y);
node.attr(node.text, 'x', x + 5);
node.attr(node.text, 'y', y + Graph.NODE_HEIGHT / 2);
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._setLine(node.action.children[i]);
}
}
Graph.prototype._removeNode = function (element) {
element.rect.remove();
element.text.remove();
if (element.line)
element.line.remove();
}
Graph.prototype._removeAction = function (action, removeChildren) {
if (!removeChildren)
removeChildren = false;
this._removeNode(action.node);
if (removeChildren) {
for (var i = 0; i < action.children.length; i++)
this._removeAction(action.children[i], removeChildren);
action.clearChildren();
}
else {
for (var i = 0; i < action.children.length; i++)
action.children[i].parent = action.parent;
}
}
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.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;
}
}
while (this.parametersElement.childNodes.length)
this.parametersElement.removeChild(this.parametersElement.firstChild);
var p = element.action.properties;
var pr = element.action.propertiesResults;
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);
}
}
Graph.prototype._createNodeAnimation = function (element) {
var scope = this;
var mousex, mousey;
var finished = true;
var elementx = 0;
var elementy = 0;
var onMove = function (dx, dy, x, y, event) {
mousex = x;
mousey = y;
};
var onStart = function (x, y, event) {
mousex = x;
mousey = y;
if (finished) {
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, '>');
};
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; });
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);
if (dragResult.hit && dragResult.element == element.action || !dragResult.hit) {
scope._createParameters(element);
}
else {
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)
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.action.parent.removeChild(element.action);
dragResult.element.addChild(element.action);
scope.update();
}
};
element.rect.drag(onMove, onStart, onEnd);
element.text.drag(onMove, onStart, onEnd);
}
Graph.NODE_WIDTH = 150;
Graph.NODE_HEIGHT = 25;
Graph.VERTICAL_OFFSET = 70;
return Graph;
})();
AB.Graph = Graph;
})(AB || (AB = {}));