actionsbuilder.viewer.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  1. module ActionsBuilder {
  2. export interface TraverseResult {
  3. action: Action;
  4. hit: boolean;
  5. }
  6. export interface UpdateResult {
  7. triggerWidth: number;
  8. childrenWidths: Array<UpdateResult>;
  9. }
  10. export class Viewer {
  11. // Statics
  12. private static _NODE_WIDTH: number = 150;
  13. private static _NODE_HEIGHT: number = 25;
  14. private static _NODE_MINIMIZE_WIDTH: number = 50;
  15. private static _VERTICAL_OFFSET: number = 70;
  16. private static _DEFAULT_INFO_MESSAGE = "Select or add a node to customize actions";
  17. public static get NODE_WIDTH(): number {
  18. return Viewer._NODE_WIDTH;
  19. }
  20. public static get NODE_HEIGHT(): number {
  21. return Viewer._NODE_HEIGHT;
  22. }
  23. public static get NODE_MINIMIZED_WIDTH(): number {
  24. return Viewer._NODE_MINIMIZE_WIDTH;
  25. }
  26. public static get VERTICAL_OFFSET(): number {
  27. return Viewer._VERTICAL_OFFSET;
  28. }
  29. // Members
  30. // Public
  31. public viewerContainer: HTMLElement;
  32. public viewerElement: HTMLElement;
  33. public objectName: string = "Unnamed Object";
  34. public zoom: number = 1.0;
  35. public mousex: number;
  36. public mousey: number;
  37. public paper: Paper;
  38. public root: Action;
  39. public selectedNode: Action;
  40. public parameters: Parameters;
  41. public utils: Utils;
  42. // Private
  43. private _toolbar: Toolbar;
  44. private _contextMenu: ContextMenu;
  45. private _firstUpdate: boolean = true;
  46. /*
  47. * Constructor
  48. * @param type: the root type object (OBJECT or SCENE)
  49. */
  50. constructor(type: number) {
  51. // Get HTML elements
  52. this.viewerContainer = document.getElementById("GraphContainerID");
  53. this.viewerElement = document.getElementById("GraphElementID");
  54. // Create element
  55. this.paper = Raphael("GraphElementID", screen.width, screen.height);
  56. // Configure this
  57. //var name = type === Type.OBJECT ? "Unnamed object" : "Scene";
  58. this.root = this.addAction(null, type, { name: this.objectName, text: this.objectName, properties: [], description: "" });
  59. this.selectedNode = null;
  60. // Configure events
  61. window.addEventListener("resize", (event: Event) => {
  62. this.onResize(event);
  63. });
  64. window.addEventListener("mousemove", (event: MouseEvent) => {
  65. this.onMove(event);
  66. });
  67. this.paper.canvas.addEventListener("click", (event: MouseEvent) => {
  68. this.onClick(event);
  69. });
  70. // Load modules
  71. this._toolbar = new Toolbar(this);
  72. this._contextMenu = new ContextMenu(this);
  73. this.parameters = new Parameters(this);
  74. this.utils = new Utils(this);
  75. // Finish
  76. this.parameters.parametersHelpElement.textContent = Viewer._DEFAULT_INFO_MESSAGE;
  77. this.onResize(null);
  78. }
  79. /*
  80. * Resize event
  81. * @param event: the resize event
  82. */
  83. public onResize(event?: Event): void {
  84. var tools = document.getElementById("ToolsButtonsID");
  85. this.viewerContainer.style.height = window.innerHeight - tools.getBoundingClientRect().height - 25 - 50 + "px";
  86. this.viewerElement.style.height = window.innerHeight - tools.getBoundingClientRect().height - 25 - 50 + "px";
  87. this.parameters.onResize();
  88. this._toolbar.onResize();
  89. if (this.paper.height < window.innerHeight) {
  90. this.paper.setSize(this.paper.width, window.innerHeight);
  91. }
  92. if (this._firstUpdate) {
  93. this.viewerElement.scrollLeft = ((this.viewerElement.scrollWidth / 2) - (this.viewerElement.getBoundingClientRect().width / 2));
  94. this._firstUpdate = false;
  95. }
  96. }
  97. /*
  98. * Handles the onMove event
  99. * @param event: the onMove mouse event
  100. */
  101. public onMove(event: MouseEvent): void {
  102. this.mousex = event.clientX - this.paper.canvas.getBoundingClientRect().left;
  103. this.mousey = event.clientY - this.paper.canvas.getBoundingClientRect().top;
  104. }
  105. /*
  106. * Handles the onClick event to get selected node
  107. * @param event: the onClick mouse event
  108. */
  109. public onClick(event: MouseEvent): void {
  110. if (this._contextMenu.showing) {
  111. return;
  112. }
  113. // Reset selected node
  114. if (this.selectedNode !== null) {
  115. var node = this.selectedNode.node;
  116. node.rect.attr("fill", this.getNodeColor(this.selectedNode.type, node.detached));
  117. }
  118. // Configure new selected node
  119. var result = this.traverseGraph(null, this.mousex, this.mousey, true);
  120. if (result.hit) {
  121. this.selectedNode = result.action;
  122. var node = this.selectedNode.node;
  123. node.rect.attr("fill", this.getSelectedNodeColor(this.selectedNode.type, node.detached));
  124. }
  125. else {
  126. this.selectedNode = null;
  127. this.parameters.clearParameters();
  128. this.parameters.parametersHelpElement.textContent = Viewer._DEFAULT_INFO_MESSAGE;
  129. }
  130. }
  131. /*
  132. * Set the color theme of the viewer
  133. * @param color: the color theme ( ex: "rgb(64, 64, 64)" )
  134. */
  135. public setColorTheme(color: string): void {
  136. this.paper.canvas.style.background = color;
  137. }
  138. /*
  139. * Returns the color according to the given parameters
  140. * @param action: the action used to select the color
  141. * @param detached: if the node is attached to its parent or not
  142. */
  143. public getNodeColor(type: number, detached: boolean): RaphaelColor {
  144. if (detached) {
  145. return Raphael.rgb(96, 122, 14);
  146. }
  147. switch (type) {
  148. case Type.TRIGGER: return Raphael.rgb(133, 154, 185); break;
  149. case Type.ACTION: return Raphael.rgb(182, 185, 132); break;
  150. case Type.FLOW_CONTROL: return Raphael.rgb(185, 132, 140); break;
  151. case Type.OBJECT:
  152. case Type.SCENE: return Raphael.rgb(255, 255, 255); break;
  153. default: break;
  154. }
  155. return null;
  156. }
  157. /*
  158. * Returns the selected node color according to the given parameters
  159. * @param action: the action used to select the color
  160. * @param detached: if the node is attached to its parent or not
  161. */
  162. public getSelectedNodeColor(type: number, detached: boolean): RaphaelColor {
  163. if (detached) {
  164. return Raphael.rgb(96, 122, 14);
  165. }
  166. switch (type) {
  167. case Type.TRIGGER: return Raphael.rgb(41, 129, 255); break;
  168. case Type.ACTION: return Raphael.rgb(255, 220, 42); break;
  169. case Type.FLOW_CONTROL: return Raphael.rgb(255, 41, 53); break;
  170. case Type.OBJECT:
  171. case Type.SCENE: return Raphael.rgb(255, 255, 255); break;
  172. default: break;
  173. }
  174. return null;
  175. }
  176. /*
  177. * Removes the given action from the graph
  178. * @param action: the action to remove
  179. * @param removeChildren: if remove the branch or not
  180. */
  181. public removeAction(action: Action, removeChildren: boolean): void {
  182. // If selected node is combine
  183. if (action.parent !== null && action.parent.hub === action) {
  184. this.removeAction(action.parent, false);
  185. return;
  186. }
  187. // Basic suppress
  188. this.removeNode(action.node);
  189. if (action.combineArray !== null) {
  190. this.removeNode(action.hub.node);
  191. // Remove combine array
  192. for (var i = 0; i < action.combineArray.length; i++) {
  193. this.removeNode(action.combineArray[i].node);
  194. }
  195. action.combineArray.length = 0;
  196. }
  197. if (removeChildren) {
  198. for (var i = 0; i < action.children.length; i++) {
  199. this.removeAction(action.children[i], removeChildren);
  200. }
  201. action.clearChildren();
  202. }
  203. else {
  204. for (var i = 0; i < action.children.length; i++) {
  205. action.parent.addChild(action.children[i]);
  206. action.children[i].parent = action.parent;
  207. }
  208. }
  209. }
  210. /*
  211. * Removes the given node (not the action)
  212. * @param node: the node to remove
  213. */
  214. public removeNode(node: Node): void {
  215. node.rect.remove();
  216. node.text.remove();
  217. if (node.line !== null) {
  218. node.line.remove();
  219. }
  220. }
  221. /*
  222. * Updates the graph viewer
  223. */
  224. public update(): void {
  225. // Set root position
  226. this._setActionPosition(this.root, (this.paper.width / 2) - (Viewer.NODE_WIDTH / 2) * this.zoom, 10);
  227. // Sets node size
  228. var onSetNodeSize = (node: Node) => {
  229. node.rect.attr("width", node.minimized ? Viewer.NODE_MINIMIZED_WIDTH : Viewer.NODE_WIDTH * this.zoom);
  230. node.rect.attr("height", Viewer.NODE_HEIGHT * this.zoom);
  231. node.text.attr("font-size", 11 * this.zoom);
  232. };
  233. // First pass: set actions positions according to parents
  234. var onSetPositionPass = (action: Action, yPosition: number): void => {
  235. var node = action.node;
  236. var parent = action.parent !== null ? action.parent : null;
  237. // Set node properties (size, text size, etc.)
  238. if (action.combineArray !== null) {
  239. for (var i = 0; i < action.combineArray.length; i++) {
  240. var combinedNode = action.combineArray[i].node;
  241. onSetNodeSize(combinedNode);
  242. }
  243. }
  244. onSetNodeSize(node);
  245. // Set position from parent
  246. if (parent) {
  247. var parentx = parent.node.rect.attr("x");
  248. if (parent.combineArray !== null && parent.combineArray.length > 1) {
  249. parentx += parent.node.rect.attr("width") / 2;
  250. }
  251. this._setActionPosition(action, parentx, yPosition);
  252. this._setActionLine(action);
  253. }
  254. // Calculate total width for current action
  255. var totalSize = 0;
  256. for (var i = 0; i < action.children.length; i++) {
  257. var childNode = action.children[i].node;
  258. totalSize += childNode.rect.attr("width");
  259. }
  260. // Get values to place nodes according to the parent position
  261. var nodeWidth = node.rect.attr("width");
  262. var startingPositionX = node.rect.attr("x");
  263. // Set children positions
  264. for (var i = 0; i < action.children.length; i++) {
  265. var childAction = action.children[i];
  266. var childNode = childAction.node;
  267. var newPositionX = startingPositionX;
  268. if (childAction.combineArray !== null && childAction.combineArray.length > 1) {
  269. newPositionX -= (childNode.rect.attr("width") / 2) - nodeWidth / 2;
  270. }
  271. var newPositionY = yPosition + Viewer.VERTICAL_OFFSET * this.zoom;
  272. onSetPositionPass(childAction, newPositionY);
  273. this._setActionPosition(childAction, newPositionX, newPositionY);
  274. this._setActionLine(childAction);
  275. }
  276. };
  277. onSetPositionPass(this.root, 10 * this.zoom);
  278. // Seconds pass, get sizes of groups
  279. var onGetSizePass = (action: Action, maxSize: number): number => {
  280. var mySize = 0;
  281. if (action.combineArray !== null) {
  282. for (var i = 0; i < action.combineArray.length; i++) {
  283. mySize += action.combineArray[i].node.rect.attr("width");
  284. }
  285. }
  286. else {
  287. mySize = action.node.rect.attr("width");
  288. }
  289. if (mySize > maxSize) {
  290. maxSize = mySize;
  291. }
  292. for (var i = 0; i < action.children.length; i++) {
  293. maxSize = onGetSizePass(action.children[i], maxSize);
  294. }
  295. return maxSize;
  296. };
  297. // Resize canvas
  298. var onResizeCanvas = (action: Action): void => {
  299. var node = action.node;
  300. var nodex = node.rect.attr("x");
  301. var nodey = node.rect.attr("y");
  302. if (nodex < 0 || nodex > this.paper.width) {
  303. this.paper.setSize(this.paper.width + 1000, this.paper.height);
  304. this._setActionPosition(this.root, (this.paper.width / 2) - (Viewer.NODE_WIDTH / 2) * this.zoom, 10);
  305. }
  306. if (nodey > this.paper.height) {
  307. this.paper.setSize(this.paper.width, this.paper.height + 1000);
  308. this._setActionPosition(this.root, (this.paper.width / 2) - (Viewer.NODE_WIDTH / 2) * this.zoom, 10);
  309. }
  310. };
  311. var widths = new Array<UpdateResult>();
  312. for (var i = 0; i < this.root.children.length; i++) {
  313. var trigger = this.root.children[i];
  314. var triggerResult: UpdateResult = { triggerWidth: onGetSizePass(trigger, 0), childrenWidths: new Array<UpdateResult>() };
  315. if (trigger.children.length > 0) {
  316. triggerResult.triggerWidth = 0;
  317. }
  318. for (var j = 0; j < trigger.children.length; j++) {
  319. var actionWidth = onGetSizePass(trigger.children[j], 0);
  320. triggerResult.triggerWidth += actionWidth + 15;
  321. triggerResult.childrenWidths.push({
  322. triggerWidth: actionWidth,
  323. childrenWidths: null
  324. });
  325. }
  326. widths.push(triggerResult);
  327. }
  328. // Third pass, set positions of nodes
  329. var onSetNodePosition = (action: Action, widthArray: Array<UpdateResult>, isChild: boolean) => {
  330. var actionsCount = action.children.length;
  331. var actionsMiddle = actionsCount % 2;
  332. var actionsHasMiddle = actionsMiddle !== 0;
  333. var actionsLeftOffset = 0;
  334. var actionsRightOffset = 0;
  335. var actionWidth = action.node.rect.attr("width");
  336. if (actionsHasMiddle && actionsCount > 1) {
  337. var middle = Math.floor(actionsCount / 2);
  338. actionsLeftOffset += widthArray[middle].triggerWidth / 2;
  339. actionsRightOffset += widthArray[middle].triggerWidth / 2;
  340. }
  341. // Move left
  342. var leftStart = actionsHasMiddle ? Math.floor(actionsCount / 2) - 1 : (actionsCount / 2) - 1;
  343. for (var i = leftStart; i >= 0; i--) {
  344. var child = action.children[i];
  345. var node = child.node;
  346. var width = (widthArray[i].triggerWidth) + 15;
  347. this._setActionPosition(action.children[i], node.rect.attr("x") - actionsLeftOffset - (width / 2), node.rect.attr("y"));
  348. this._setActionLine(child);
  349. onResizeCanvas(child);
  350. actionsLeftOffset += width;
  351. }
  352. // Move right
  353. var rightStart = actionsHasMiddle ? Math.round(actionsCount / 2) : actionsCount / 2;
  354. for (var i = rightStart; i < actionsCount; i++) {
  355. var child = action.children[i];
  356. var node = child.node;
  357. var width = (widthArray[i].triggerWidth) + 15;
  358. this._setActionPosition(action.children[i], node.rect.attr("x") + actionsRightOffset + (width / 2), node.rect.attr("y"));
  359. this._setActionLine(child);
  360. onResizeCanvas(child);
  361. actionsRightOffset += width;
  362. }
  363. };
  364. onSetNodePosition(this.root, widths, false);
  365. for (var i = 0; i < this.root.children.length; i++) {
  366. onSetNodePosition(this.root.children[i], widths[i].childrenWidths, true);
  367. }
  368. }
  369. /*
  370. * Adds an action to the graph viewer and returns it
  371. * @param parent: the parent action
  372. * @param type: the action type
  373. * @param element: the Actions Builder type (TRIGGERS, ACTIONS, FLOW_CONTROLS)
  374. */
  375. public addAction(parent: Action, type: number, element: Element): Action {
  376. var node = this._createNode(element.text, type, parent === null);
  377. var action = new Action(node);
  378. if (element.name === "CombineAction") {
  379. action.combineArray = new Array<Action>();
  380. var hubElement = Elements.FLOW_CONTROLS[Elements.FLOW_CONTROLS.length - 1];
  381. var hub = this.addAction(action, Type.FLOW_CONTROL, hubElement);
  382. action.hub = hub;
  383. action.addChild(hub);
  384. this._createActionAnimation(hub);
  385. }
  386. action.name = element.name;
  387. action.properties = element.properties;
  388. action.type = type;
  389. // Configure properties
  390. for (var i = 0; i < action.properties.length; i++) {
  391. action.propertiesResults.push({ targetType: action.properties[i].targetType, value: action.properties[i].value });
  392. }
  393. if (action.properties !== null && action.properties.length > 0) {
  394. if (action.properties[0].text === "target") {
  395. action.propertiesResults[0].value = this.objectName;
  396. }
  397. }
  398. if (parent !== null) {
  399. if (parent.combineArray === null) {
  400. parent.addChild(action);
  401. }
  402. else if (parent.combineArray !== null && action.name !== "Hub") {
  403. parent.combineArray.push(action);
  404. action.parent = parent;
  405. action.combineAction = parent;
  406. parent.node.text.attr("text", "");
  407. }
  408. }
  409. // Create animation
  410. this._createActionAnimation(action);
  411. return action;
  412. }
  413. /*
  414. * Traverses the graph viewer and returns if an action
  415. * is selected at coordinates (x, y)
  416. * @param start: the start node. Can be null
  417. * @param x: the x coordinate
  418. * @param y: the y coordinate
  419. * @param traverseCombine: if we traverse combine actions children
  420. */
  421. public traverseGraph(start: Action, x: number, y: number, traverseCombine: boolean): TraverseResult {
  422. if (start === null) start = this.root;
  423. var result: TraverseResult = { action: start, hit: true };
  424. if (start.node.isPointInside(x, y)) {
  425. return result;
  426. }
  427. for (var i = 0; i < start.children.length; i++) {
  428. var action = start.children[i];
  429. if (action.node.isPointInside(x, y)) {
  430. result.hit = true;
  431. result.action = start.children[i];
  432. if (traverseCombine && action.combineArray !== null) {
  433. for (var j = 0; j < action.combineArray.length; j++) {
  434. if (action.combineArray[j].node.isPointInside(x, y)) {
  435. result.action = action.combineArray[j];
  436. break;
  437. }
  438. }
  439. }
  440. return result;
  441. }
  442. result = this.traverseGraph(action, x, y, traverseCombine);
  443. if (result.hit) {
  444. return result;
  445. }
  446. }
  447. result.hit = false;
  448. result.action = null;
  449. return result;
  450. }
  451. /*
  452. * Sets the action's position (node)
  453. * @param action: the action to place
  454. * @param x: the x position of the action
  455. * @param y: the y position of the action
  456. */
  457. public _setActionPosition(action: Action, x: number, y: number): void {
  458. var node = action.node;
  459. var offsetx = node.rect.attr("x") - x;
  460. var parent = action.parent;
  461. if (parent !== null && parent.combineArray !== null && parent.combineArray.length > 1) {
  462. var parentNode = parent.node;
  463. x = parentNode.rect.attr("x") + (parent.node.rect.attr("width") / 2) - (node.rect.attr("width") / 2);
  464. }
  465. node.rect.attr("x", x);
  466. node.rect.attr("y", y);
  467. var textBBox = node.text.getBBox();
  468. var textWidth = 0;
  469. if (textBBox !== null && textBBox !== undefined) {
  470. textWidth = textBBox.width;
  471. }
  472. node.text.attr("x", x + node.rect.attr("width") / 2 - textWidth / 2);
  473. node.text.attr("y", y + node.rect.attr("height") / 2);
  474. if (action.combineArray !== null && action.combineArray.length > 0) {
  475. var length = 0;
  476. for (var i = 0; i < action.combineArray.length; i++) {
  477. var combinedAction = action.combineArray[i];
  478. var combinedNode = combinedAction.node;
  479. combinedNode.rect.attr("x", node.rect.attr("x") + length);
  480. combinedNode.rect.attr("y", node.rect.attr("y"));
  481. textBBox = combinedNode.text.getBBox();
  482. if (textBBox !== null) {
  483. textWidth = textBBox.width;
  484. }
  485. combinedNode.text.attr("x", combinedNode.rect.attr("x") + combinedNode.rect.attr("width") / 2 - textWidth / 2);
  486. combinedNode.text.attr("y", y + combinedNode.rect.attr("height") / 2);
  487. length += combinedNode.rect.attr("width");
  488. }
  489. node.rect.attr("width", length);
  490. }
  491. for (var i = 0; i < action.children.length; i++) {
  492. var child = action.children[i];
  493. this._setActionPosition(child, child.node.rect.attr("x") - offsetx, y + Viewer.VERTICAL_OFFSET * this.zoom);
  494. this._setActionLine(child);
  495. }
  496. }
  497. /*
  498. * Configures the line (link) between the action and its parent
  499. * @param action: the action to configure
  500. */
  501. private _setActionLine(action: Action): void {
  502. if (action.node.line === null) {
  503. return;
  504. }
  505. var node = action.node;
  506. var nodex = node.rect.attr("x");
  507. var nodey = node.rect.attr("y");
  508. var nodeWidth = node.rect.attr("width");
  509. var nodeHeight = node.rect.attr("height");
  510. var parent = action.parent.node;
  511. var parentx = parent.rect.attr("x");
  512. var parenty = parent.rect.attr("y");
  513. var parentWidth = parent.rect.attr("width");
  514. var parentHeight = parent.rect.attr("height");
  515. if (node.detached) {
  516. node.line.attr("path", ["M", nodex, nodey, "L", nodex, nodey]);
  517. return;
  518. }
  519. var line1x = nodex + (nodeWidth / 2);
  520. var line1y = nodey;
  521. var line2y = line1y - (line1y - parenty - parentHeight) / 2;
  522. var line3x = parentx + (parentWidth / 2);
  523. var line4y = parenty + parentHeight;
  524. node.line.attr("path", ["M", line1x, line1y, "L", line1x, line2y, "L", line3x, line2y, "L", line3x, line4y]);
  525. }
  526. /*
  527. * Creates and returns a node
  528. * @param text: the text to draw in the nde
  529. * @param color: the node's color
  530. * @param noLine: if draw a line to the parent or not
  531. */
  532. public _createNode(text: string, type: number, noLine: boolean): Node {
  533. var node = new Node();
  534. var color = this.getNodeColor(type, false);
  535. node.rect = this.paper.rect(20, 20, Viewer.NODE_WIDTH, Viewer.NODE_HEIGHT, 0);
  536. node.rect.attr("fill", color);
  537. node.text = this.paper.text(20, 20, text);
  538. node.text.attr("font-size", 11);
  539. node.text.attr("text-anchor", "start");
  540. node.text.attr("font-family", "Sinkin Sans Light");
  541. if (!noLine) {
  542. node.line = this.paper.path("");
  543. node.line.attr("stroke", color);
  544. }
  545. return node;
  546. }
  547. /*
  548. * Creates the drag animation
  549. * @param action: the action to animate
  550. */
  551. private _createActionAnimation(action: Action): void {
  552. var node = action.node;
  553. var finished = true;
  554. var nodex: number = 0;
  555. var nodey: number = 0;
  556. var onMove = (dx: number, dy: number, x: number, y: number) =>
  557. { };
  558. var onStart = (x: number, y: number, event: MouseEvent) => {
  559. if (node.minimized) {
  560. return;
  561. }
  562. if (finished) {
  563. nodex = node.rect.attr("x");
  564. nodey = node.rect.attr("y");
  565. }
  566. finished = false;
  567. node.rect.animate({
  568. x: node.rect.attr("x") - 10,
  569. y: node.rect.attr("y"),
  570. width: (Viewer.NODE_WIDTH + 20) * this.zoom,
  571. height: (Viewer.NODE_HEIGHT + 10) * this.zoom,
  572. opacity: 0.25
  573. }, 500, ">");
  574. };
  575. var onEnd = (event: MouseEvent) => {
  576. if (!node.minimized) {
  577. node.rect.animate({
  578. x: nodex,
  579. y: nodey,
  580. width: Viewer.NODE_WIDTH * this.zoom,
  581. height: Viewer.NODE_HEIGHT * this.zoom,
  582. opacity: 1.0
  583. }, 500, ">", () => { finished = true; });
  584. }
  585. var dragResult = this.traverseGraph(null, this.mousex, this.mousey, true);
  586. if (dragResult.hit && dragResult.action === action || !dragResult.hit) {
  587. // Create parameters. Action can be null
  588. this.parameters.createParameters(action);
  589. }
  590. else {
  591. // Manage drag'n'drop
  592. if (dragResult.action.children.length > 0 && action.type !== Type.TRIGGER) {
  593. return;
  594. }
  595. if (action.type === Type.TRIGGER && dragResult.action !== this.root) {
  596. return;
  597. }
  598. if (action.type === Type.ACTION && dragResult.action === this.root) {
  599. return;
  600. }
  601. if (action.type === Type.FLOW_CONTROL && (dragResult.action === this.root || dragResult.action.type === Type.FLOW_CONTROL)) {
  602. return;
  603. }
  604. if (action === dragResult.action.parent) {
  605. return;
  606. }
  607. if (action.parent !== null && action.parent.combineArray !== null) { // Musn't move hubs of combine actions
  608. return;
  609. }
  610. // Reset node
  611. node.rect.stop(node.rect.animation);
  612. node.text.stop(node.text.animation);
  613. node.rect.undrag();
  614. node.text.undrag();
  615. node.rect.attr("opacity", 1.0);
  616. node.rect.attr("width", Viewer.NODE_WIDTH);
  617. node.rect.attr("height", Viewer.NODE_HEIGHT);
  618. if (action.parent !== null) {
  619. // Configure drag'n'drop
  620. action.parent.removeChild(action);
  621. dragResult.action.addChild(action);
  622. this.update();
  623. this._createActionAnimation(action);
  624. }
  625. }
  626. };
  627. node.rect.drag(onMove, onStart, onEnd);
  628. node.text.drag(onMove, onStart, onEnd);
  629. }
  630. }
  631. }