actionsbuilder.utils.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. interface Window {
  2. clipboardData: any;
  3. }
  4. module ActionsBuilder {
  5. export interface JSONObjectProperty {
  6. targetType?: string;
  7. name: string;
  8. value: string;
  9. }
  10. export interface JSONObject {
  11. type: number;
  12. name: string;
  13. detached: boolean;
  14. children: Array<JSONObject>;
  15. combine: Array<JSONObject>;
  16. properties: Array<JSONObjectProperty>;
  17. }
  18. export class Utils {
  19. // Members
  20. public copiedStructure: string = null;
  21. private _viewer: Viewer;
  22. /*
  23. * Constructor
  24. * @param viewer: the viewer instance
  25. */
  26. constructor(viewer: Viewer) {
  27. // Configure this
  28. this._viewer = viewer;
  29. }
  30. /*
  31. * Tests the graph and reports errors
  32. */
  33. public onTestGraph(): void {
  34. if (this._viewer.root.children.length === 0) {
  35. alert("Please add at least a Trigger and an Action to test the graph");
  36. }
  37. var onTestTarget = (targetType: string, target: any) => {
  38. var targetExists = false;
  39. var array = this._viewer.parameters._getTargetFromType(targetType);
  40. if (array === null) {
  41. return targetExists;
  42. }
  43. for (var i = 0; i < array.length; i++) {
  44. if (array[i] === target) {
  45. targetExists = true;
  46. break;
  47. }
  48. }
  49. return targetExists;
  50. };
  51. var onNodeError = (action: Action): boolean => {
  52. var node = action.node;
  53. node.rect.attr("fill", Raphael.rgb(255, 0, 0));
  54. return false;
  55. };
  56. var onTestAction = (action: Action): boolean => {
  57. console.log("Testing " + action.name);
  58. if (action.combineArray !== null) {
  59. var foundError = false;
  60. for (var i = 0; i < action.combineArray.length; i++) {
  61. if (!onTestAction(action.combineArray[i])) {
  62. foundError = true;
  63. }
  64. }
  65. if (foundError) {
  66. return false;
  67. }
  68. }
  69. else {
  70. // Test properties
  71. var properties = action.properties;
  72. var propertiesResults = action.propertiesResults;
  73. if (properties !== null) {
  74. var object: any = null;
  75. var propertyPath: any = null;
  76. for (var i = 0; i < properties.length; i++) {
  77. // Target
  78. if (properties[i].text === "target" || properties[i].text === "parent") {
  79. object = this._viewer.parameters._getObjectFromType(properties[i].targetType);
  80. var targetExists = onTestTarget(propertiesResults[i].targetType, propertiesResults[i].value);
  81. if (!targetExists) {
  82. return onNodeError(action);
  83. }
  84. }
  85. // Property Path
  86. else if (properties[i].text === "propertyPath") {
  87. var property = <string>propertiesResults[i].value;
  88. var effectiveProperty = object;
  89. var p = property.split(".");
  90. for (var j = 0; j < p.length && effectiveProperty !== undefined; j++) {
  91. effectiveProperty = effectiveProperty[p[j]];
  92. }
  93. if (effectiveProperty === undefined) {
  94. return onNodeError(action);
  95. }
  96. else {
  97. propertyPath = effectiveProperty;
  98. }
  99. }
  100. // value
  101. else if (properties[i].text == "value" && propertyPath != null) {
  102. var value = propertiesResults[i].value;
  103. if (!isNaN(propertyPath)) {
  104. var num = parseFloat(value);
  105. if (isNaN(num) || value === "") {
  106. return onNodeError(action);
  107. }
  108. }
  109. }
  110. }
  111. }
  112. var foundError = false;
  113. for (var i = 0; i < action.children.length; i++) {
  114. if (!onTestAction(action.children[i])) {
  115. foundError = true;
  116. }
  117. }
  118. return !foundError;
  119. }
  120. };
  121. var root = this._viewer.root;
  122. var foundError = false;
  123. for (var i = 0; i < root.children.length; i++) {
  124. var trigger = root.children[i];
  125. var properties = trigger.properties;
  126. // Test properties of trigger (parameter)
  127. if (properties !== null && properties.length > 0) {
  128. // Only one property
  129. var parameter = trigger.propertiesResults[0].value;
  130. if (properties[0].targetType !== null) {
  131. // Intersection trigger
  132. if (!onTestTarget("MeshProperties", parameter)) {
  133. foundError = onNodeError(trigger);
  134. }
  135. }
  136. else {
  137. // Key trigger
  138. if (!parameter.match(/[a-z]/)) {
  139. foundError = onNodeError(trigger);
  140. }
  141. }
  142. }
  143. for (var j = 0; j < trigger.children.length; j++) {
  144. var child = trigger.children[j];
  145. var result = onTestAction(child);
  146. if (!result) {
  147. foundError = true;
  148. }
  149. }
  150. }
  151. if (foundError) {
  152. alert("Found error(s). the red nodes contain the error.");
  153. }
  154. else {
  155. alert("No error found.");
  156. }
  157. }
  158. /*
  159. * Recursively reduce/expand nodes
  160. */
  161. public onReduceAll(forceExpand: boolean = false): void {
  162. if (this._viewer.selectedNode === null) {
  163. return;
  164. }
  165. var action = this._viewer.selectedNode;
  166. if (action.combineArray !== null) {
  167. for (var i = 0; i < action.combineArray.length; i++) {
  168. this._viewer.selectedNode = action.combineArray[i];
  169. this.onReduce(forceExpand, !forceExpand);
  170. }
  171. }
  172. else {
  173. this.onReduce(forceExpand, !forceExpand);
  174. }
  175. for (var i = 0; i < action.children.length; i++) {
  176. this._viewer.selectedNode = action.children[i];
  177. this.onReduceAll(forceExpand);
  178. }
  179. }
  180. /*
  181. * Reduces the selected node
  182. */
  183. public onReduce(forceExpand: boolean = false, forceReduce: boolean = false): void {
  184. if (this._viewer.selectedNode === null) {
  185. return;
  186. }
  187. var node = this._viewer.selectedNode.node;
  188. node.rect.stop(node.rect.animation);
  189. // Set minimized
  190. if (forceExpand === true) {
  191. node.minimized = false;
  192. }
  193. else if (forceReduce === true) {
  194. node.minimized = true;
  195. }
  196. else {
  197. node.minimized = !node.minimized;
  198. }
  199. // Set size
  200. if (node.minimized) {
  201. node.text.hide();
  202. node.rect.attr("width", Viewer.NODE_MINIMIZED_WIDTH * this._viewer.zoom);
  203. }
  204. else {
  205. node.text.show();
  206. node.rect.attr("width", Viewer.NODE_WIDTH * this._viewer.zoom);
  207. }
  208. }
  209. /*
  210. * Detaches the selected action
  211. */
  212. public onDetachAction(forceDetach: boolean = false, forceAttach: boolean = false): void {
  213. if (this._viewer.selectedNode === null) {
  214. return;
  215. }
  216. var action = this._viewer.selectedNode;
  217. if (forceDetach === true) {
  218. action.node.detached = true;
  219. }
  220. else if (forceAttach === true) {
  221. action.node.detached = false;
  222. }
  223. else {
  224. action.node.detached = !action.node.detached;
  225. }
  226. var onSetColor = (root: Action, detached: boolean) => {
  227. var rootNode = root.node;
  228. rootNode.rect.attr("fill", this._viewer.getNodeColor(root.type, detached));
  229. if (root.combineArray !== null) {
  230. for (var i = 0; i < root.combineArray.length; i++) {
  231. var combineNode = root.combineArray[i].node;
  232. combineNode.rect.attr("fill", this._viewer.getNodeColor(root.combineArray[i].type, detached));
  233. }
  234. }
  235. for (var i = 0; i < root.children.length; i++) {
  236. onSetColor(root.children[i], detached);
  237. }
  238. };
  239. onSetColor(action, action.node.detached);
  240. }
  241. /*
  242. * Removes the selected node
  243. */
  244. public onRemoveNode(): void {
  245. if (this._viewer.selectedNode === null) {
  246. return;
  247. }
  248. var action = this._viewer.selectedNode;
  249. var parent = action.parent;
  250. // If trigger, remove branch
  251. if (action.type === Type.TRIGGER) {
  252. this.onRemoveBranch();
  253. return;
  254. }
  255. // If it is a combine hub
  256. if (action.type === Type.FLOW_CONTROL && parent !== null && parent.combineArray !== null) {
  257. action = parent;
  258. parent = action.parent;
  259. }
  260. // Remove
  261. if (parent !== null && parent.combineArray !== null) {
  262. parent.removeCombinedAction(action);
  263. if (parent.combineArray.length === 0) {
  264. parent.node.text.attr("text", "combine");
  265. }
  266. }
  267. else {
  268. if (action.combineArray !== null) {
  269. action.removeChild(action.hub);
  270. }
  271. action.parent.removeChild(action);
  272. }
  273. if (action.combineArray !== null) {
  274. this._viewer.removeAction(action.hub, false);
  275. }
  276. this._viewer.removeAction(action, false);
  277. // Finish
  278. this._viewer.update();
  279. this._viewer.parameters.clearParameters();
  280. this._viewer.selectedNode = null;
  281. }
  282. /*
  283. * Removes a branch starting from the selected node
  284. */
  285. public onRemoveBranch(): void {
  286. if (this._viewer.selectedNode === null) {
  287. return;
  288. }
  289. if (this._viewer.selectedNode === this._viewer.root) {
  290. alert("Cannot remove the root node");
  291. return;
  292. }
  293. var action = this._viewer.selectedNode;
  294. var parent = action.parent;
  295. // If combine
  296. if (action.parent !== null && action.parent.combineArray !== null) {
  297. action = parent;
  298. parent = action.parent;
  299. }
  300. // Remove
  301. if (action.combineArray !== null) {
  302. action.removeChild(action.hub);
  303. }
  304. action.parent.removeChild(action);
  305. this._viewer.removeAction(action, true);
  306. // Finish
  307. this._viewer.update();
  308. this._viewer.parameters.clearParameters();
  309. this._viewer.selectedNode = null;
  310. }
  311. /*
  312. * Copies the selected structure
  313. */
  314. public onCopyStructure(): void {
  315. if (this._viewer.selectedNode === null) {
  316. return;
  317. }
  318. var structure = this.createJSON(this._viewer.selectedNode);
  319. var asText = JSON.stringify(structure);
  320. if (window.clipboardData !== undefined) {
  321. window.clipboardData.setData("text", asText);
  322. }
  323. else {
  324. this.copiedStructure = asText;
  325. }
  326. }
  327. /*
  328. * Pastes the graph structure previously copied
  329. */
  330. public onPasteStructure(): void {
  331. if (this._viewer.selectedNode === null) {
  332. return;
  333. }
  334. var asText = (window.clipboardData !== undefined) ? window.clipboardData.getData("text") : this.copiedStructure;
  335. var isJson = asText.length > 0 && asText[0] == "{" && asText[asText.length - 1] == "}";
  336. var structure: JSONObject = JSON.parse(asText);
  337. var action = this._viewer.selectedNode;
  338. if (structure.type === Type.TRIGGER && action !== this._viewer.root) {
  339. alert("You can't paste a trigger if the selected node isn't the root object");
  340. return;
  341. }
  342. if (structure.type !== Type.TRIGGER && action === this._viewer.root) {
  343. alert("You can't paste an action or condition if the selected node is the root object");
  344. return;
  345. }
  346. this.loadFromJSON(structure, action);
  347. this._viewer.update();
  348. }
  349. /*
  350. * Loads a graph from JSON
  351. * @pram graph: the graph structure
  352. * @param startAction: the action to start load
  353. */
  354. public loadFromJSON(graph: JSONObject, startAction: Action): void {
  355. // If startNode is null, means it replaces all the graph
  356. // If not, it comes from a copy/paste
  357. if (startAction === null) {
  358. for (var i = 0; i < this._viewer.root.children.length; i++) {
  359. this._viewer.removeAction(this._viewer.root.children[i], true);
  360. }
  361. this._viewer.root.clearChildren();
  362. }
  363. var load = (root: JSONObject, parent: Action, detached: boolean, combine: boolean) => {
  364. if (parent === null) {
  365. parent = this._viewer.root;
  366. }
  367. var newAction: Action = null;
  368. if (root.type !== Type.OBJECT && root.type !== Type.SCENE) {
  369. var action = this._viewer.addAction(parent, root.type, Elements.GetElementFromName(root.name));
  370. for (var i = 0; i < root.properties.length; i++) {
  371. var targetType = root.properties[i].targetType;
  372. if (targetType === undefined) {
  373. targetType = "MeshProperties"; // Default is mesh properties
  374. }
  375. action.propertiesResults[i] = { value: root.properties[i].value, targetType: targetType };
  376. }
  377. var node = action.node;
  378. node.detached = root.detached;
  379. if (detached) {
  380. node.rect.attr("fill", this._viewer.getNodeColor(action.type, detached));
  381. }
  382. // If combine array
  383. if (root.combine !== undefined) {
  384. for (var i = 0; i < root.combine.length; i++) {
  385. load(root.combine[i], action, detached, true);
  386. }
  387. }
  388. if (!combine) {
  389. parent = parent.children[parent.children.length - 1];
  390. }
  391. }
  392. for (var i = 0; i < root.children.length; i++) {
  393. load(root.children[i], newAction !== null && newAction.combineArray !== null ? newAction.hub : parent, root.detached, false);
  394. }
  395. };
  396. // Finish
  397. load(graph, startAction, false, false);
  398. this._viewer.update();
  399. }
  400. /*
  401. * Creates a JSON object starting from a root action
  402. * @param root: the root action
  403. */
  404. public createJSON(root: Action): JSONObject {
  405. var action: JSONObject = {
  406. type: root.type,
  407. name: root.name,
  408. detached: root.node.detached,
  409. children: new Array<JSONObject>(),
  410. combine: new Array<JSONObject>(),
  411. properties: new Array<JSONObjectProperty>()
  412. };
  413. // Set properties
  414. for (var i = 0; i < root.properties.length; i++) {
  415. action.properties.push({
  416. name: root.properties[i].text,
  417. value: root.propertiesResults[i].value,
  418. targetType: root.propertiesResults[i].targetType
  419. });
  420. }
  421. // If combine
  422. if (root.combineArray !== null) {
  423. for (var i = 0; i < root.combineArray.length; i++) {
  424. var combinedAction = root.combineArray[i];
  425. action.combine.push(this.createJSON(combinedAction));
  426. }
  427. root = root.children[0]; // Hub
  428. }
  429. for (var i = 0; i < root.children.length; i++) {
  430. action.children.push(this.createJSON(root.children[i]));
  431. }
  432. return action;
  433. }
  434. /*
  435. *
  436. */
  437. public setElementVisible(element: HTMLElement, visible: boolean): void {
  438. element.style.display = visible ? "block" : "none";
  439. }
  440. }
  441. }