/// /// 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 = {}));