dialogger.js 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329
  1. var fs = require('fs');
  2. // Constants
  3. var con =
  4. {
  5. allowable_connections:
  6. [
  7. ['dialogue.Text', 'dialogue.Text'],
  8. ['dialogue.Text', 'dialogue.Node'],
  9. ['dialogue.Text', 'dialogue.Choice'],
  10. ['dialogue.Text', 'dialogue.Set'],
  11. ['dialogue.Text', 'dialogue.Branch'],
  12. ['dialogue.Text', 'dialogue.Function'],
  13. ['dialogue.Text', 'dialogue.Tree'],
  14. ['dialogue.Text', 'dialogue.GoToLabel'],
  15. ['dialogue.Text', 'dialogue.ConditionalBranch'],
  16. ['dialogue.Node', 'dialogue.Text'],
  17. ['dialogue.Node', 'dialogue.Node'],
  18. ['dialogue.Node', 'dialogue.Choice'],
  19. ['dialogue.Node', 'dialogue.Set'],
  20. ['dialogue.Node', 'dialogue.Branch'],
  21. ['dialogue.Node', 'dialogue.Function'],
  22. ['dialogue.Node', 'dialogue.Tree'],
  23. ['dialogue.Node', 'dialogue.GoToLabel'],
  24. ['dialogue.Node', 'dialogue.ConditionalBranch'],
  25. ['dialogue.Tree', 'dialogue.Node'],
  26. ['dialogue.Tree', 'dialogue.Text'],
  27. ['dialogue.Tree', 'dialogue.Choice'],
  28. ['dialogue.Tree', 'dialogue.Set'],
  29. ['dialogue.Tree', 'dialogue.Branch'],
  30. ['dialogue.Tree', 'dialogue.Function'],
  31. ['dialogue.Tree', 'dialogue.Tree'],
  32. ['dialogue.Tree', 'dialogue.GoToLabel'],
  33. ['dialogue.Tree', 'dialogue.ConditionalBranch'],
  34. ['dialogue.StartNode', 'dialogue.Text'],
  35. ['dialogue.StartNode', 'dialogue.Node'],
  36. ['dialogue.StartNode', 'dialogue.Choice'],
  37. ['dialogue.StartNode', 'dialogue.Set'],
  38. ['dialogue.StartNode', 'dialogue.Branch'],
  39. ['dialogue.StartNode', 'dialogue.Function'],
  40. ['dialogue.StartNode', 'dialogue.Tree'],
  41. ['dialogue.StartNode', 'dialogue.GoToLabel'],
  42. ['dialogue.StartNode', 'dialogue.ConditionalBranch'],
  43. ['dialogue.Choice', 'dialogue.Text'],
  44. ['dialogue.Choice', 'dialogue.Node'],
  45. ['dialogue.Choice', 'dialogue.Set'],
  46. ['dialogue.Choice', 'dialogue.Branch'],
  47. ['dialogue.Choice', 'dialogue.Function'],
  48. ['dialogue.Choice', 'dialogue.Tree'],
  49. ['dialogue.Choice', 'dialogue.GoToLabel'],
  50. ['dialogue.Choice', 'dialogue.ConditionalBranch'],
  51. ['dialogue.Set', 'dialogue.Text'],
  52. ['dialogue.Set', 'dialogue.Node'],
  53. ['dialogue.Set', 'dialogue.Set'],
  54. ['dialogue.Set', 'dialogue.Branch'],
  55. ['dialogue.Set', 'dialogue.Function'],
  56. ['dialogue.Set', 'dialogue.Tree'],
  57. ['dialogue.Set', 'dialogue.GoToLabel'],
  58. ['dialogue.Set', 'dialogue.ConditionalBranch'],
  59. ['dialogue.Function', 'dialogue.Text'],
  60. ['dialogue.Function', 'dialogue.Node'],
  61. ['dialogue.Function', 'dialogue.Set'],
  62. ['dialogue.Function', 'dialogue.Branch'],
  63. ['dialogue.Function', 'dialogue.Function'],
  64. ['dialogue.Function', 'dialogue.Tree'],
  65. ['dialogue.Function', 'dialogue.GoToLabel'],
  66. ['dialogue.Function', 'dialogue.ConditionalBranch'],
  67. ['dialogue.Branch', 'dialogue.Text'],
  68. ['dialogue.Branch', 'dialogue.Node'],
  69. ['dialogue.Branch', 'dialogue.Set'],
  70. ['dialogue.Branch', 'dialogue.Branch'],
  71. ['dialogue.Branch', 'dialogue.Function'],
  72. ['dialogue.Branch', 'dialogue.Tree'],
  73. ['dialogue.Branch', 'dialogue.GoToLabel'],
  74. ['dialogue.Branch', 'dialogue.ConditionalBranch'],
  75. ['dialogue.ConditionalBranch', 'dialogue.Text'],
  76. ['dialogue.ConditionalBranch', 'dialogue.Node'],
  77. ['dialogue.ConditionalBranch', 'dialogue.Set'],
  78. ['dialogue.ConditionalBranch', 'dialogue.ConditionalBranch'],
  79. ['dialogue.ConditionalBranch', 'dialogue.Branch'],
  80. ['dialogue.ConditionalBranch', 'dialogue.Function'],
  81. ['dialogue.ConditionalBranch', 'dialogue.Tree'],
  82. ['dialogue.ConditionalBranch', 'dialogue.GoToLabel'],
  83. ],
  84. default_link: new joint.dia.Link(
  85. {
  86. attrs:
  87. {
  88. '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', },
  89. '.link-tools .tool-remove circle, .marker-vertex': { r: 8 },
  90. },
  91. }),
  92. };
  93. con.default_link.set('smooth', true);
  94. // State
  95. var state =
  96. {
  97. graph: new joint.dia.Graph(),
  98. paper: null,
  99. filepath: null,
  100. panning: false,
  101. mouse_position: { x: 0, y: 0 },
  102. context_position: { x: 0, y: 0 },
  103. menu: null,
  104. };
  105. // Models
  106. joint.shapes.dialogue = {};
  107. joint.shapes.dialogue.Base = joint.shapes.devs.Model.extend(
  108. {
  109. defaults: joint.util.deepSupplement
  110. (
  111. {
  112. type: 'dialogue.Base',
  113. size: { width: 100, height: 64 },
  114. name: '',
  115. attrs:
  116. {
  117. rect: { stroke: 'none', 'fill-opacity': 0 },
  118. text: { display: 'none' },
  119. },
  120. },
  121. joint.shapes.devs.Model.prototype.defaults
  122. ),
  123. });
  124. joint.shapes.dialogue.BaseView = joint.shapes.devs.ModelView.extend(
  125. {
  126. template:
  127. [
  128. '<div class="node">',
  129. '<span class="label"></span>',
  130. '<button class="delete">x</button>',
  131. '<textarea class="name" placeholder="Text"></textarea>',
  132. '</div>',
  133. ].join(''),
  134. initialize: function()
  135. {
  136. _.bindAll(this, 'updateBox');
  137. joint.shapes.devs.ModelView.prototype.initialize.apply(this, arguments);
  138. this.$box = $(_.template(this.template)());
  139. // Prevent paper from handling pointerdown.
  140. this.$box.find('textarea, input').on('mousedown click', function(evt) { evt.stopPropagation(); });
  141. // This is an example of reacting on the input change and storing the input data in the cell model.
  142. this.$box.find('textarea.name, input.name').on('change', _.bind(function(evt)
  143. {
  144. this.model.set('name', $(evt.target).val());
  145. }, this));
  146. this.$box.find('.delete').on('click', _.bind(this.model.remove, this.model));
  147. // Update the box position whenever the underlying model changes.
  148. this.model.on('change', this.updateBox, this);
  149. // Remove the box when the model gets removed from the graph.
  150. this.model.on('remove', this.removeBox, this);
  151. this.updateBox();
  152. },
  153. render: function()
  154. {
  155. joint.shapes.devs.ModelView.prototype.render.apply(this, arguments);
  156. this.paper.$el.prepend(this.$box);
  157. this.updateBox();
  158. return this;
  159. },
  160. updateBox: function()
  161. {
  162. // Set the position and dimension of the box so that it covers the JointJS element.
  163. var bbox = this.model.getBBox();
  164. // Example of updating the HTML with a data stored in the cell model.
  165. var nameField = this.$box.find('textarea.name, input.name');
  166. if (!nameField.is(':focus'))
  167. nameField.val(this.model.get('name'));
  168. var label = this.$box.find('.label');
  169. var type = this.model.get('type').slice('dialogue.'.length);
  170. label.text(type);
  171. label.attr('class', 'label ' + type);
  172. this.$box[0].classList.add(type);
  173. this.$box.css({ width: bbox.width, left: bbox.x, top: bbox.y, transform: 'rotate(' + (this.model.get('angle') || 0) + 'deg)' });
  174. },
  175. removeBox: function(evt)
  176. {
  177. this.$box.remove();
  178. },
  179. });
  180. joint.shapes.dialogue.Node = joint.shapes.devs.Model.extend(
  181. {
  182. defaults: joint.util.deepSupplement
  183. (
  184. {
  185. type: 'dialogue.Node',
  186. size: { width: 100, height: 65, },
  187. inPorts: ['input'],
  188. outPorts: ['output'],
  189. attrs:
  190. {
  191. '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
  192. },
  193. },
  194. joint.shapes.dialogue.Base.prototype.defaults
  195. ),
  196. });
  197. joint.shapes.dialogue.NodeView = joint.shapes.dialogue.BaseView;
  198. joint.shapes.dialogue.Tree = joint.shapes.devs.Model.extend(
  199. {
  200. defaults: joint.util.deepSupplement
  201. (
  202. {
  203. type: 'dialogue.Tree',
  204. size: { width: 150, height: 65, },
  205. inPorts: ['input'],
  206. outPorts: ['output'],
  207. attrs:
  208. {
  209. '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
  210. },
  211. },
  212. joint.shapes.dialogue.Base.prototype.defaults
  213. ),
  214. });
  215. joint.shapes.dialogue.TreeView = joint.shapes.dialogue.BaseView;
  216. joint.shapes.dialogue.Text = joint.shapes.devs.Model.extend(
  217. {
  218. defaults: joint.util.deepSupplement
  219. (
  220. {
  221. type: 'dialogue.Text',
  222. size: { width: 350 },
  223. inPorts: ['input'],
  224. outPorts: ['output'],
  225. attrs:
  226. {
  227. '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
  228. },
  229. },
  230. joint.shapes.dialogue.Base.prototype.defaults
  231. ),
  232. });
  233. joint.shapes.dialogue.TextView = joint.shapes.dialogue.BaseView;
  234. joint.shapes.dialogue.Function = joint.shapes.devs.Model.extend(
  235. {
  236. defaults: joint.util.deepSupplement
  237. (
  238. {
  239. type: 'dialogue.Function',
  240. size: { width: 200, height: 65, },
  241. inPorts: ['input'],
  242. outPorts: ['output'],
  243. attrs:
  244. {
  245. '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
  246. },
  247. },
  248. joint.shapes.dialogue.Base.prototype.defaults
  249. ),
  250. });
  251. joint.shapes.dialogue.FunctionView = joint.shapes.dialogue.BaseView;
  252. joint.shapes.dialogue.GoToLabel = joint.shapes.devs.Model.extend(
  253. {
  254. defaults: joint.util.deepSupplement
  255. (
  256. {
  257. type: 'dialogue.GoToLabel',
  258. size: { width: 125, height: 65, },
  259. inPorts: ['input'],
  260. outPorts: ['output'],
  261. attrs:
  262. {
  263. '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
  264. },
  265. },
  266. joint.shapes.dialogue.Base.prototype.defaults
  267. ),
  268. });
  269. joint.shapes.dialogue.GoToLabelView = joint.shapes.dialogue.BaseView;
  270. joint.shapes.dialogue.Branch = joint.shapes.devs.Model.extend(
  271. {
  272. defaults: joint.util.deepSupplement
  273. (
  274. {
  275. type: 'dialogue.Branch',
  276. size: { width: 150, height: 95, },
  277. inPorts: ['input'],
  278. outPorts: ['output0'],
  279. values: [],
  280. },
  281. joint.shapes.dialogue.Base.prototype.defaults
  282. ),
  283. });
  284. joint.shapes.dialogue.BranchView = joint.shapes.dialogue.BaseView.extend(
  285. {
  286. template:
  287. [
  288. '<div class="node">',
  289. '<span class="label"></span>',
  290. '<button class="delete">x</button>',
  291. '<button class="add">+</button>',
  292. '<button class="remove">-</button>',
  293. '<input type="text" class="name" placeholder="Variable" />',
  294. '<input type="text" value="Default" readonly/>',
  295. '</div>',
  296. ].join(''),
  297. initialize: function()
  298. {
  299. joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
  300. this.$box.find('.add').on('click', _.bind(this.addPort, this));
  301. this.$box.find('.remove').on('click', _.bind(this.removePort, this));
  302. },
  303. removePort: function()
  304. {
  305. if (this.model.get('outPorts').length > 1)
  306. {
  307. var outPorts = this.model.get('outPorts').slice(0);
  308. outPorts.pop();
  309. this.model.set('outPorts', outPorts);
  310. var values = this.model.get('values').slice(0);
  311. values.pop();
  312. this.model.set('values', values);
  313. this.updateSize();
  314. }
  315. },
  316. addPort: function()
  317. {
  318. var outPorts = this.model.get('outPorts').slice(0);
  319. outPorts.push('output' + outPorts.length.toString());
  320. this.model.set('outPorts', outPorts);
  321. var values = this.model.get('values').slice(0);
  322. values.push(null);
  323. this.model.set('values', values);
  324. this.updateSize();
  325. },
  326. updateBox: function()
  327. {
  328. joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
  329. var values = this.model.get('values');
  330. var valueFields = this.$box.find('input.value');
  331. // Add value fields if necessary
  332. for (var i = valueFields.length; i < values.length; i++)
  333. {
  334. // Prevent paper from handling pointerdown.
  335. var $field = $('<input type="text" class="value" />');
  336. $field.attr('placeholder', 'Value ' + (i + 1).toString());
  337. $field.attr('index', i);
  338. this.$box.append($field);
  339. $field.on('mousedown click', function(evt) { evt.stopPropagation(); });
  340. // This is an example of reacting on the input change and storing the input data in the cell model.
  341. $field.on('change', _.bind(function(evt)
  342. {
  343. var values = this.model.get('values').slice(0);
  344. values[$(evt.target).attr('index')] = $(evt.target).val();
  345. this.model.set('values', values);
  346. }, this));
  347. }
  348. // Remove value fields if necessary
  349. for (var i = values.length; i < valueFields.length; i++)
  350. $(valueFields[i]).remove();
  351. // Update value fields
  352. valueFields = this.$box.find('input.value');
  353. for (var i = 0; i < valueFields.length; i++)
  354. {
  355. var field = $(valueFields[i]);
  356. if (!field.is(':focus'))
  357. field.val(values[i]);
  358. }
  359. },
  360. updateSize: function()
  361. {
  362. var textField = this.$box.find('input.name');
  363. var height = textField.outerHeight(true);
  364. this.model.set('size', { width: 150, height: 95 + Math.max(0, (this.model.get('outPorts').length - 1) * height) });
  365. },
  366. });
  367. joint.shapes.dialogue.ConditionalBranch = joint.shapes.devs.Model.extend(
  368. {
  369. defaults: joint.util.deepSupplement
  370. (
  371. {
  372. type: 'dialogue.ConditionalBranch',
  373. size: { width: 200, height: 55, },
  374. inPorts: ['input'],
  375. outPorts: ['output0'],
  376. values: [],
  377. },
  378. joint.shapes.dialogue.Base.prototype.defaults
  379. ),
  380. });
  381. joint.shapes.dialogue.ConditionalBranchView = joint.shapes.dialogue.BaseView.extend(
  382. {
  383. template:
  384. [
  385. '<div class="node">',
  386. '<span class="label"></span>',
  387. '<button class="delete">x</button>',
  388. '<button class="add">+</button>',
  389. '<button class="remove">-</button>',
  390. '<input type="text" value="Default" readonly/>',
  391. '</div>',
  392. ].join(''),
  393. initialize: function()
  394. {
  395. joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
  396. this.$box.find('.add').on('click', _.bind(this.addPort, this));
  397. this.$box.find('.remove').on('click', _.bind(this.removePort, this));
  398. },
  399. removePort: function()
  400. {
  401. if (this.model.get('outPorts').length > 1)
  402. {
  403. var outPorts = this.model.get('outPorts').slice(0);
  404. outPorts.pop();
  405. this.model.set('outPorts', outPorts);
  406. var values = this.model.get('values').slice(0);
  407. values.pop();
  408. this.model.set('values', values);
  409. this.updateSize();
  410. }
  411. },
  412. addPort: function()
  413. {
  414. var outPorts = this.model.get('outPorts').slice(0);
  415. outPorts.push('output' + outPorts.length.toString());
  416. this.model.set('outPorts', outPorts);
  417. var values = this.model.get('values').slice(0);
  418. values.push(null);
  419. this.model.set('values', values);
  420. this.updateSize();
  421. },
  422. updateBox: function()
  423. {
  424. joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
  425. var values = this.model.get('values');
  426. var valueFields = this.$box.find('input.value');
  427. // Add value fields if necessary
  428. for (var i = valueFields.length; i < values.length; i++)
  429. {
  430. // Prevent paper from handling pointerdown.
  431. var $field = $('<input type="text" class="value" />');
  432. $field.attr('placeholder', 'Condition ' + (i + 1).toString());
  433. $field.attr('index', i);
  434. this.$box.append($field);
  435. $field.on('mousedown click', function(evt) { evt.stopPropagation(); });
  436. // This is an example of reacting on the input change and storing the input data in the cell model.
  437. $field.on('change', _.bind(function(evt)
  438. {
  439. var values = this.model.get('values').slice(0);
  440. values[$(evt.target).attr('index')] = $(evt.target).val();
  441. this.model.set('values', values);
  442. }, this));
  443. }
  444. // Remove value fields if necessary
  445. for (var i = values.length; i < valueFields.length; i++)
  446. $(valueFields[i]).remove();
  447. // Update value fields
  448. valueFields = this.$box.find('input.value');
  449. for (var i = 0; i < valueFields.length; i++)
  450. {
  451. var field = $(valueFields[i]);
  452. if (!field.is(':focus'))
  453. field.val(values[i]);
  454. }
  455. },
  456. updateSize: function()
  457. {
  458. var textField = this.$box.find('input');
  459. var height = textField.outerHeight(true);
  460. this.model.set('size', { width: 200, height: 95 + Math.max(0, (this.model.get('outPorts').length - 1) * height) });
  461. },
  462. });
  463. joint.shapes.dialogue.StartNode = joint.shapes.devs.Model.extend(
  464. {
  465. defaults: joint.util.deepSupplement
  466. (
  467. {
  468. type: 'dialogue.StartNode',
  469. inPorts: [],
  470. outPorts: ['output'],
  471. size: { width: 100, height: 32, },
  472. value: 'DialogueNode.START',
  473. attrs:
  474. {
  475. '.outPorts circle': { unlimitedConnections: ['dialogue.Choice'], }
  476. },
  477. },
  478. joint.shapes.dialogue.Base.prototype.defaults
  479. ),
  480. });
  481. joint.shapes.dialogue.StartNodeView = joint.shapes.dialogue.BaseView.extend(
  482. {
  483. template:
  484. [
  485. '<div class="node">',
  486. '<span class="label"></span>',
  487. '<button class="delete">x</button>',
  488. '</div>',
  489. ].join(''),
  490. initialize: function()
  491. {
  492. joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
  493. //this.$box.find('input.value').on('change', _.bind(function(evt)
  494. //{
  495. // this.model.set('value', $(evt.target).val());
  496. //}, this));
  497. },
  498. updateBox: function()
  499. {
  500. joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
  501. //var field = this.$box.find('input.value');
  502. //if (!field.is(':focus'))
  503. // field.val(this.model.get('value'));
  504. },
  505. });
  506. joint.shapes.dialogue.Choice = joint.shapes.devs.Model.extend(
  507. {
  508. defaults: joint.util.deepSupplement
  509. (
  510. {
  511. type: 'dialogue.Choice',
  512. inPorts: ['input'],
  513. outPorts: ['output'],
  514. size: { width: 175, height: 93, },
  515. value: '',
  516. },
  517. joint.shapes.dialogue.Base.prototype.defaults
  518. ),
  519. });
  520. joint.shapes.dialogue.ChoiceView = joint.shapes.dialogue.BaseView.extend(
  521. {
  522. template:
  523. [
  524. '<div class="node">',
  525. '<span class="label"></span>',
  526. '<button class="delete">x</button>',
  527. '<input type="text" class="name" placeholder="Text" />',
  528. '<input type="text" class="value" placeholder="Conditions" />',
  529. '</div>',
  530. ].join(''),
  531. initialize: function()
  532. {
  533. joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
  534. this.$box.find('input.value').on('change', _.bind(function(evt)
  535. {
  536. this.model.set('value', $(evt.target).val());
  537. }, this));
  538. },
  539. updateBox: function()
  540. {
  541. joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
  542. var field = this.$box.find('input.value');
  543. if (!field.is(':focus'))
  544. field.val(this.model.get('value'));
  545. },
  546. });
  547. joint.shapes.dialogue.Set = joint.shapes.devs.Model.extend(
  548. {
  549. defaults: joint.util.deepSupplement
  550. (
  551. {
  552. type: 'dialogue.Set',
  553. inPorts: ['input'],
  554. outPorts: ['output'],
  555. size: { width: 125, height: 90, },
  556. value: '',
  557. },
  558. joint.shapes.dialogue.Base.prototype.defaults
  559. ),
  560. });
  561. joint.shapes.dialogue.SetView = joint.shapes.dialogue.BaseView.extend(
  562. {
  563. template:
  564. [
  565. '<div class="node">',
  566. '<span class="label"></span>',
  567. '<button class="delete">x</button>',
  568. '<input type="text" class="name" placeholder="Variable" />',
  569. '<input type="text" class="value" placeholder="Value" />',
  570. '</div>',
  571. ].join(''),
  572. initialize: function()
  573. {
  574. joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
  575. this.$box.find('input.value').on('change', _.bind(function(evt)
  576. {
  577. this.model.set('value', $(evt.target).val());
  578. }, this));
  579. },
  580. updateBox: function()
  581. {
  582. joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
  583. var field = this.$box.find('input.value');
  584. if (!field.is(':focus'))
  585. field.val(this.model.get('value'));
  586. },
  587. });
  588. /**
  589. *
  590. * AI Hook
  591. *
  592. */
  593. joint.shapes.dialogue.AIHook = joint.shapes.devs.Model.extend(
  594. {
  595. defaults: joint.util.deepSupplement
  596. (
  597. {
  598. type: 'dialogue.AIHook',
  599. inPorts: [],
  600. outPorts: [],
  601. size: { width: 300, height: 126, },
  602. value: '',
  603. },
  604. joint.shapes.dialogue.Base.prototype.defaults
  605. ),
  606. });
  607. joint.shapes.dialogue.AIHookView = joint.shapes.dialogue.BaseView.extend(
  608. {
  609. template:
  610. [
  611. '<div class="node">',
  612. '<span class="label"></span>',
  613. '<button class="delete">x</button>',
  614. '<select class="hooktype">',
  615. '<option value="" selected disabled hidden>Choose Type</option>',
  616. '<option value="askabout">Investigate Option</option>',
  617. '<option value="circumstance">Common Dialogue Option</option>',
  618. '<option value="informative">Pre-Dialogue Dialogue</option>',
  619. '<option value="critical">Override Dialogue</option>',
  620. '</select>',
  621. '<input type="text" class="identifier" placeholder="Selection Text" />',
  622. '<input type="text" class="conditions" placeholder="Conditions (greeter, answerer)" />',
  623. '</div>',
  624. ].join(''),
  625. initialize: function()
  626. {
  627. joint.shapes.dialogue.BaseView.prototype.initialize.apply(this, arguments);
  628. this.$box.find('input.conditions').on('change', _.bind(function(evt)
  629. {
  630. this.model.set('conditions', $(evt.target).val());
  631. }, this));
  632. this.$box.find('select.hooktype').on('change', _.bind(function(evt)
  633. {
  634. this.model.set('hooktype', $(evt.target).val());
  635. }, this));
  636. this.$box.find('input.identifier').on('change', _.bind(function(evt)
  637. {
  638. this.model.set('identifier', $(evt.target).val());
  639. }, this));
  640. },
  641. updateBox: function()
  642. {
  643. joint.shapes.dialogue.BaseView.prototype.updateBox.apply(this, arguments);
  644. var field = this.$box.find('select.hooktype');
  645. if (!field.is(':focus'))
  646. field.val(this.model.get('hooktype'));
  647. var field = this.$box.find('input.conditions');
  648. if (!field.is(':focus'))
  649. field.val(this.model.get('conditions'));
  650. var field = this.$box.find('input.identifier');
  651. if (!field.is(':focus'))
  652. field.val(this.model.get('identifier'));
  653. },
  654. });
  655. // Functions
  656. var func = {};
  657. func.validate_connection = function(cellViewS, magnetS, cellViewT, magnetT, end, linkView)
  658. {
  659. // Prevent loop linking
  660. if (magnetS === magnetT)
  661. return false;
  662. if (cellViewS === cellViewT)
  663. return false;
  664. if ($(magnetT).parents('.outPorts').length > 0) // Can't connect to an output port
  665. return false;
  666. var sourceType = cellViewS.model.attributes.type;
  667. var targetType = cellViewT.model.attributes.type;
  668. var valid = false;
  669. for (var i = 0; i < con.allowable_connections.length; i++)
  670. {
  671. var rule = con.allowable_connections[i];
  672. if (sourceType === rule[0] && targetType === rule[1])
  673. {
  674. valid = true;
  675. break;
  676. }
  677. }
  678. if (!valid)
  679. return false;
  680. var links = state.graph.getConnectedLinks(cellViewS.model);
  681. for (var i = 0; i < links.length; i++)
  682. {
  683. var link = links[i];
  684. if (link.attributes.source.id === cellViewS.model.id && link.attributes.source.port === magnetS.attributes.port.nodeValue && link.attributes.target.id)
  685. {
  686. var targetCell = state.graph.getCell(link.attributes.target.id);
  687. if (targetCell.attributes.type !== targetType)
  688. return false; // We can only connect to multiple targets of the same type
  689. if (targetCell === cellViewT.model)
  690. return false; // Already connected
  691. }
  692. }
  693. return true;
  694. };
  695. func.validate_magnet = function(cellView, magnet)
  696. {
  697. if ($(magnet).parents('.outPorts').length === 0)
  698. return false; // we only want output ports
  699. // If unlimited connections attribute is null, we can only ever connect to one object
  700. // If it is not null, it is an array of type strings which are allowed to have unlimited connections
  701. var unlimitedConnections = magnet.getAttribute('unlimitedConnections');
  702. var links = state.graph.getConnectedLinks(cellView.model);
  703. for (var i = 0; i < links.length; i++)
  704. {
  705. var link = links[i];
  706. if (link.attributes.source.id === cellView.model.id && link.attributes.source.port === magnet.attributes.port.nodeValue)
  707. {
  708. // This port already has a connection
  709. if (unlimitedConnections && link.attributes.target.id)
  710. {
  711. var targetCell = state.graph.getCell(link.attributes.target.id);
  712. if (unlimitedConnections.indexOf(targetCell.attributes.type) !== -1)
  713. return true; // It's okay because this target type has unlimited connections
  714. }
  715. return false;
  716. }
  717. }
  718. return true;
  719. };
  720. func.optimized_data = function()
  721. {
  722. var cells = state.graph.toJSON().cells;
  723. var nodesByID = {};
  724. var cellsByID = {};
  725. var nodes = [];
  726. for (var i = 0; i < cells.length; i++)
  727. {
  728. var cell = cells[i];
  729. if (cell.type != 'link')
  730. {
  731. var node =
  732. {
  733. type: cell.type.slice('dialogue.'.length),
  734. id: cell.id,
  735. };
  736. if (node.type === 'Branch')
  737. {
  738. node.variable = cell.name;
  739. node.branches = {};
  740. for (var j = 0; j < cell.values.length; j++)
  741. {
  742. var branch = cell.values[j];
  743. node.branches[branch] = null;
  744. }
  745. }
  746. else if (node.type === 'ConditionalBranch')
  747. {
  748. node.branches = {};
  749. for (var j = 0; j < cell.values.length; j++)
  750. {
  751. var branch = cell.values[j];
  752. node.branches[branch] = null;
  753. }
  754. }
  755. else if (node.type === 'Set')
  756. {
  757. node.variable = cell.name;
  758. node.value = cell.value;
  759. node.next = null;
  760. }
  761. else if (node.type === 'AIHook')
  762. {
  763. node.hooktype = cell.hooktype;
  764. node.identifier = cell.identifier;
  765. node.conditions = cell.conditions;
  766. node.next = null;
  767. }
  768. else
  769. {
  770. node.name = cell.name;
  771. node.next = null;
  772. }
  773. if (node.type === "Choice") {
  774. node.value = cell.value;
  775. }
  776. nodes.push(node);
  777. nodesByID[cell.id] = node;
  778. cellsByID[cell.id] = cell;
  779. }
  780. }
  781. for (var i = 0; i < cells.length; i++)
  782. {
  783. var cell = cells[i];
  784. if (cell.type === 'link')
  785. {
  786. var source = nodesByID[cell.source.id];
  787. var target = cell.target ? nodesByID[cell.target.id] : null;
  788. if (source)
  789. {
  790. if (source.type === 'Branch')
  791. {
  792. var portNumber = parseInt(cell.source.port.slice('output'.length));
  793. var value;
  794. if (portNumber === 0)
  795. value = '_default';
  796. else
  797. {
  798. var sourceCell = cellsByID[source.id];
  799. value = sourceCell.values[portNumber - 1];
  800. }
  801. source.branches[value] = target ? target.id : null;
  802. }
  803. else if ((source.type === 'Text' || source.type === 'Node' || source.type === 'StartNode') && target && target.type === 'Choice')
  804. {
  805. if (!source.choices)
  806. {
  807. source.choices = [];
  808. delete source.next;
  809. }
  810. source.choices.push(target.id);
  811. }
  812. else
  813. source.next = target ? target.id : null;
  814. }
  815. }
  816. }
  817. return nodes;
  818. };
  819. // Menu actions
  820. func.flash = function(text)
  821. {
  822. var $flash = $('#flash');
  823. $flash.text(text);
  824. $flash.stop(true, true);
  825. $flash.show();
  826. $flash.css('opacity', 1.0);
  827. $flash.fadeOut({ duration: 1500 });
  828. };
  829. func.apply_fields = function()
  830. {
  831. $('input[type=text], select').blur();
  832. };
  833. func.save = function()
  834. {
  835. func.apply_fields();
  836. if (!state.filepath)
  837. func.show_save_dialog();
  838. else
  839. func.do_save();
  840. };
  841. func.optimized_filename = function(f)
  842. {
  843. return f.substring(0, f.length - 2) + 'dlz';
  844. };
  845. func.perfect_typescript = function(f)
  846. {
  847. return f.substring(0, f.length - 2) + 'dl.ts';
  848. };
  849. func.just_name = function (f) {
  850. return f.substring(0, f.length - 3);
  851. };
  852. func.do_save = function()
  853. {
  854. if (state.filepath)
  855. {
  856. fs.writeFileSync(state.filepath, JSON.stringify(state.graph), 'utf8');
  857. //fs.writeFileSync(func.optimized_filename(state.filepath), JSON.stringify(func.optimized_data()), 'utf8');
  858. let name = func.just_name(func.filename_from_filepath(state.filepath));
  859. let cleants = "// File created automatically by custom Dialogger on " + new Date().toLocaleString() + "\n";
  860. cleants += "// Do not tamper with this file.\n// It will be replaced automatically by Dialogger and all changes will be lost.\n// Instead change " + name + ".dl.";
  861. cleants += "\nmodule DialogueTrees {\n export let " + name + " = (function () {\n";
  862. cleants += func.ts_data(func.optimized_data(), name).replace(/^/gm, ' ') + "\n return tree;\n })();\n}";
  863. fs.writeFileSync(func.perfect_typescript(state.filepath), cleants, 'utf8');
  864. func.flash('Saved ' + state.filepath);
  865. }
  866. };
  867. func.filename_from_filepath = function(f)
  868. {
  869. return f.replace(/^.*[\\\/]/, '');
  870. };
  871. func.show_open_dialog = function()
  872. {
  873. $('#file_open').click();
  874. };
  875. func.show_save_dialog = function()
  876. {
  877. $('#file_save').click();
  878. };
  879. func.add_node = function(constructor)
  880. {
  881. return function()
  882. {
  883. var container = $('#container')[0];
  884. var element = new constructor(
  885. {
  886. position: { x: state.context_position.x + container.scrollLeft, y: state.context_position.y + container.scrollTop },
  887. });
  888. state.graph.addCells([element]);
  889. };
  890. };
  891. func.clear = function()
  892. {
  893. state.graph.clear();
  894. state.filepath = null;
  895. document.title = 'Dialogger';
  896. };
  897. func.handle_open_files = function(files)
  898. {
  899. state.filepath = files[0].path;
  900. var data = fs.readFileSync(state.filepath);
  901. document.title = func.filename_from_filepath(state.filepath);
  902. state.graph.clear();
  903. state.graph.fromJSON(JSON.parse(data));
  904. };
  905. func.handle_save_files = function(files)
  906. {
  907. state.filepath = files[0].path;
  908. func.do_save();
  909. };
  910. func.ts_data = function(nodes, name) {
  911. let declarations = [];
  912. let addDeclaration = (dec) => {
  913. if (declarations.indexOf(dec) == -1) {
  914. declarations.push(dec);
  915. }
  916. }
  917. addDeclaration("let tree : DialogueTree = new DialogueTree(" + JSON.stringify(name) + ");\n");
  918. let finalString = "";
  919. for (let i = 0; i < nodes.length; i++) {
  920. finalString += "\n\n";
  921. let node = nodes[i];
  922. if (node.type == "Node") {
  923. finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
  924. finalString += "node.setName(" + JSON.stringify(node.name) + ");\n";
  925. if (node.next != null && node.next != undefined) {
  926. finalString += "node.setNext(" + JSON.stringify(node.next) + ");\n";
  927. }
  928. if (node.choices != undefined && node.choices != null) {
  929. finalString += "node.setChoices(" + JSON.stringify(node.choices) + ");\n";
  930. }
  931. finalString += "tree.addNode(node);\n";
  932. addDeclaration("let node : DialogueNode;\n");
  933. } else if (node.type == "GoToLabel") {
  934. finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
  935. finalString += "node.setNext(" + JSON.stringify(node.name) + ");\n";
  936. finalString += "tree.addNode(node);\n";
  937. addDeclaration("let node : DialogueNode;\n");
  938. } else if (node.type == "Tree") {
  939. finalString += "nodetree = new DialogueNodeTree(" + JSON.stringify(node.id) + ");\n";
  940. finalString += "nodetree.setTree(() => { return " + node.name + "; });\n";
  941. if (node.next != null && node.next != undefined) {
  942. finalString += "nodetree.setNext(" + JSON.stringify(node.next) + ");\n";
  943. }
  944. if (node.choices != undefined && node.choices != null) {
  945. finalString += "nodetree.setChoices(" + JSON.stringify(node.choices) + ");\n";
  946. }
  947. finalString += "tree.addNode(nodetree);\n";
  948. addDeclaration("let nodetree : DialogueNodeTree;\n");
  949. } else if (node.type == "StartNode") {
  950. finalString += "node = new DialogueNode(" + JSON.stringify(node.id) + ");\n";
  951. if (node.next != null && node.next != undefined) {
  952. finalString += "node.setNext(" + JSON.stringify(node.next) + ");\n";
  953. }
  954. if (node.choices != undefined && node.choices != null) {
  955. finalString += "node.setChoices(" + JSON.stringify(node.choices) + ");\n";
  956. }
  957. finalString += "tree.addStartNode(node);\n";
  958. addDeclaration("let node : DialogueNode;\n");
  959. } else if (node.type == "Text") {
  960. finalString += "text = new DialogueText(" + JSON.stringify(node.id) + ");\n";
  961. //finalString += "node.setSay(" + node.name + ");\n"; // this way all code we placed will be added right away!
  962. // SetSay needs to be a function, otherwise data might not be updated when printing!
  963. finalString += "text.setSay(() => { return new Say(" + node.name + ");});\n";
  964. if (node.next != null && node.next != undefined) {
  965. finalString += "text.setNext(" + JSON.stringify(node.next) + ");\n";
  966. }
  967. if (node.choices != undefined && node.choices != null) {
  968. finalString += "text.setChoices(" + JSON.stringify(node.choices) + ");\n";
  969. }
  970. finalString += "tree.addNode(text);\n";
  971. addDeclaration("let text : DialogueText;\n");
  972. } else if (node.type == "Choice") {
  973. finalString += "choice = new DialogueChoice(" + JSON.stringify(node.id) + ");\n";
  974. //finalString += "node.setSay(" + node.name + ");\n"; // this way all code we placed will be added right away!
  975. // SetSay needs to be a function, otherwise data might not be updated when printing!
  976. finalString += "choice.setSay(() => { return new Say(" + node.name + ");});\n";
  977. if (node.value != undefined && node.value != null && node.value != "") {
  978. finalString += "choice.setConditions(() => { return " + node.value + ";});\n"
  979. }
  980. if (node.next != null && node.next != undefined) {
  981. finalString += "choice.setNext(" + JSON.stringify(node.next) + ");\n";
  982. }
  983. if (node.choices != undefined && node.choices != null) {
  984. finalString += "choice.setChoices(" + JSON.stringify(node.choices) + ");\n";
  985. }
  986. finalString += "tree.addNode(choice);\n";
  987. addDeclaration("let choice : DialogueChoice;\n");
  988. } else if (node.type == "Branch") {
  989. finalString += "branch = new DialogueBranch(" + JSON.stringify(node.id) + ");\n";
  990. finalString += "branch.setVariable(() => { return " + node.variable + ";});\n";
  991. for (let key in node.branches) {
  992. let branchCondition = key;
  993. let branchTarget = node.branches[key];
  994. if (branchCondition == "_default") {
  995. finalString += "branch.setNext(" + JSON.stringify(branchTarget) + ");\n";
  996. } else {
  997. finalString += "branch.addBranch(" + JSON.stringify(branchTarget) + ", () => { return " + branchCondition + ";});\n";
  998. }
  999. }
  1000. finalString += "tree.addNode(branch);\n";
  1001. addDeclaration("let branch : DialogueBranch;\n");
  1002. } else if (node.type == "ConditionalBranch") {
  1003. finalString += "branch = new DialogueBranch(" + JSON.stringify(node.id) + ");\n";
  1004. //finalString += "branch.setVariable(() => { return " + node.variable + ";});\n";
  1005. for (let key in node.branches) {
  1006. let branchCondition = key;
  1007. let branchTarget = node.branches[key];
  1008. if (branchCondition == "_default") {
  1009. finalString += "branch.setNext(" + JSON.stringify(branchTarget) + ");\n";
  1010. } else {
  1011. finalString += "branch.addBranch(" + JSON.stringify(branchTarget) + ", () => { return " + branchCondition + ";});\n";
  1012. }
  1013. }
  1014. finalString += "tree.addNode(branch);\n";
  1015. addDeclaration("let branch : DialogueBranch;\n");
  1016. } else if (node.type == "Set" || node.type == "Function") {
  1017. finalString += "set = new DialogueSet(" + JSON.stringify(node.id) + ");\n";
  1018. if (node.type == "Function") {
  1019. finalString += "set.setFunction(() => {\n" + node.name + "\n});\n";
  1020. } else {
  1021. finalString += "set.setFunction(() => {\n " + node.variable + " = " + node.value + ";\n});\n";
  1022. }
  1023. if (node.next != null && node.next != undefined) {
  1024. finalString += "set.setNext(" + JSON.stringify(node.next) + ");\n";
  1025. }
  1026. if (node.choices != undefined && node.choices != null) {
  1027. finalString += "set.setChoices(" + JSON.stringify(node.choices) + ");\n";
  1028. }
  1029. finalString += "tree.addNode(set);\n";
  1030. addDeclaration("let set : DialogueSet;\n");
  1031. } else if (node.type == "AIHook") {
  1032. // hooktype, identifier, conditions
  1033. // AskAbout/Circumstance/Informative/Critical
  1034. node.hooktype = node.hooktype != undefined ? node.hooktype : "";
  1035. node.identifier = node.identifier != undefined ? node.identifier : "";
  1036. node.conditions = node.conditions != undefined ? node.conditions : "";
  1037. let hookType = node.hooktype.toLowerCase().trim();
  1038. finalString += " AI." + (hookType == "askabout" ? "investigateRules" : "talktoRules") + ".createAndAddRule({\n" +
  1039. " name : " + JSON.stringify("AIHook for " + name) + ",\n";
  1040. finalString += " conditions : (runner : RulebookRunner<TalkingHeads>) => {\n" +
  1041. " // @ts-ignore\n" +
  1042. " let player = WorldState.player, greeter = runner.noun.greeter, answerer = runner.noun.answerer;\n" +
  1043. "\n" +
  1044. " return (" + node.conditions + ");\n" +
  1045. " },\n";
  1046. if (hookType == "critical" || hookType == "informative") {
  1047. finalString += " priority : Rule.PRIORITY_HIGHEST,\n" +
  1048. " firstPriority : Rule.PRIORITY_HIGHEST,\n";
  1049. } else {
  1050. finalString += " priority : Rule.PRIORITY_MEDIUM,\n" +
  1051. " firstPriority : Rule.PRIORITY_MEDIUM,\n"
  1052. }
  1053. if (hookType == "informative") {
  1054. finalString += " code : (runner : RulebookRunner<TalkingHeads>) => {\n" +
  1055. " runner.noun.runFirst.push(tree);\n" +
  1056. " },\n";
  1057. } else if (hookType == "critical") {
  1058. finalString += " code : (runner : RulebookRunner<TalkingHeads>) => {\n" +
  1059. " runner.noun.runAndStop.push(tree);\n" +
  1060. " },\n";
  1061. } else {
  1062. finalString += " code : (runner : RulebookRunner<TalkingHeads>) => {\n" +
  1063. " runner.noun.options.push({\n" +
  1064. " tree: tree,\n" +
  1065. " text : new Say(" + node.identifier + ")\n" +
  1066. " })\n" +
  1067. " },\n";
  1068. }
  1069. finalString += "});\n\n";
  1070. }
  1071. }
  1072. return declarations.join("") + finalString;
  1073. };
  1074. // Initialize
  1075. (function()
  1076. {
  1077. state.paper = new joint.dia.Paper(
  1078. {
  1079. el: $('#paper'),
  1080. width: 32000,
  1081. height: 16000,
  1082. model: state.graph,
  1083. gridSize: 1,
  1084. defaultLink: con.default_link,
  1085. validateConnection: func.validate_connection,
  1086. validateMagnet: func.validate_magnet,
  1087. snapLinks: { radius: 75 }, // enable link snapping within 75px lookup radius
  1088. markAvailable: true,
  1089. });
  1090. state.paper.on('blank:pointerdown', function(e, x, y)
  1091. {
  1092. if (e.button === 0 || e.button === 1)
  1093. {
  1094. state.panning = true;
  1095. state.mouse_position.x = e.pageX;
  1096. state.mouse_position.y = e.pageY;
  1097. $('body').css('cursor', 'move');
  1098. func.apply_fields();
  1099. }
  1100. });
  1101. state.paper.on('cell:pointerdown', function(e, x, y)
  1102. {
  1103. func.apply_fields();
  1104. });
  1105. $('#container').mousemove(function(e)
  1106. {
  1107. if (state.panning)
  1108. {
  1109. var $this = $(this);
  1110. $this.scrollLeft($this.scrollLeft() + state.mouse_position.x - e.pageX);
  1111. $this.scrollTop($this.scrollTop() + state.mouse_position.y - e.pageY);
  1112. state.mouse_position.x = e.pageX;
  1113. state.mouse_position.y = e.pageY;
  1114. }
  1115. });
  1116. $('#container').mouseup(function(e)
  1117. {
  1118. state.panning = false;
  1119. $('body').css('cursor', 'default');
  1120. });
  1121. $('#file_open').on('change', function()
  1122. {
  1123. if (this.files)
  1124. func.handle_open_files(this.files);
  1125. // clear files from this input
  1126. var $this = $(this);
  1127. $this.wrap('<form>').parent('form').trigger('reset');
  1128. $this.unwrap();
  1129. });
  1130. $('#file_save').on('change', function()
  1131. {
  1132. func.handle_save_files(this.files);
  1133. });
  1134. $('body').on('dragenter', function(e)
  1135. {
  1136. e.stopPropagation();
  1137. e.preventDefault();
  1138. });
  1139. $('body').on('dragexit', function(e)
  1140. {
  1141. e.stopPropagation();
  1142. e.preventDefault();
  1143. });
  1144. $('body').on('dragover', function(e)
  1145. {
  1146. e.stopPropagation();
  1147. e.preventDefault();
  1148. });
  1149. $('body').on('drop', function(e)
  1150. {
  1151. e.stopPropagation();
  1152. e.preventDefault();
  1153. func.handle_open_files(e.originalEvent.dataTransfer.files);
  1154. });
  1155. $(window).on('keydown', function(event)
  1156. {
  1157. // Catch Ctrl-S or key code 19 on Mac (Cmd-S)
  1158. if (((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 's') || event.which === 19)
  1159. {
  1160. event.stopPropagation();
  1161. event.preventDefault();
  1162. func.save();
  1163. return false;
  1164. }
  1165. else if ((event.ctrlKey || event.metaKey) && String.fromCharCode(event.which).toLowerCase() === 'o')
  1166. {
  1167. event.stopPropagation();
  1168. event.preventDefault();
  1169. func.show_open_dialog();
  1170. return false;
  1171. }
  1172. return true;
  1173. });
  1174. $(window).resize(function()
  1175. {
  1176. func.apply_fields();
  1177. var $window = $(window);
  1178. var $container = $('#container');
  1179. $container.height($window.innerHeight());
  1180. $container.width($window.innerWidth());
  1181. return this;
  1182. });
  1183. $(window).trigger('resize');
  1184. // Context menu
  1185. state.menu = new nw.Menu();
  1186. state.menu.append(new nw.MenuItem({ label: 'Text', click: func.add_node(joint.shapes.dialogue.Text) }));
  1187. state.menu.append(new nw.MenuItem({ label: 'Choice', click: func.add_node(joint.shapes.dialogue.Choice) }));
  1188. state.menu.append(new nw.MenuItem({ label: 'Switch', click: func.add_node(joint.shapes.dialogue.Branch) }));
  1189. state.menu.append(new nw.MenuItem({ label: 'Branch', click: func.add_node(joint.shapes.dialogue.ConditionalBranch) }));
  1190. state.menu.append(new nw.MenuItem({ label: 'Set', click: func.add_node(joint.shapes.dialogue.Set) }));
  1191. state.menu.append(new nw.MenuItem({ label: 'Function', click: func.add_node(joint.shapes.dialogue.Function) }));
  1192. state.menu.append(new nw.MenuItem({ label: 'GoToLabel', click: func.add_node(joint.shapes.dialogue.GoToLabel) }));
  1193. state.menu.append(new nw.MenuItem({ label: 'Tree', click: func.add_node(joint.shapes.dialogue.Tree) }));
  1194. state.menu.append(new nw.MenuItem({ label: 'Label', click: func.add_node(joint.shapes.dialogue.Node) }));
  1195. state.menu.append(new nw.MenuItem({ label: 'Start Point', click: func.add_node(joint.shapes.dialogue.StartNode) }));
  1196. state.menu.append(new nw.MenuItem({ label: 'AI Hook', click: func.add_node(joint.shapes.dialogue.AIHook) }));
  1197. state.menu.append(new nw.MenuItem({ type: 'separator' }));
  1198. state.menu.append(new nw.MenuItem({ label: 'Save', click: func.save }));
  1199. state.menu.append(new nw.MenuItem({ label: 'Open', click: func.show_open_dialog }));
  1200. state.menu.append(new nw.MenuItem({ label: 'New', click: func.clear }));
  1201. document.body.addEventListener('contextmenu', function(e)
  1202. {
  1203. e.preventDefault();
  1204. state.context_position.x = e.x;
  1205. state.context_position.y = e.y;
  1206. state.menu.popup(e.x, e.y);
  1207. return false;
  1208. }, false);
  1209. })();
  1210. function updateTextareas () {
  1211. window.requestAnimationFrame(updateTextareas);
  1212. let textareas = new Array(...document.getElementsByTagName("textarea"));
  1213. for (let i = 0; i < textareas.length; i++) {
  1214. let textarea = textareas[i];
  1215. textarea.style.height = "0 px";
  1216. textarea.style.height = textarea.scrollHeight + "px";
  1217. }
  1218. }
  1219. window.requestAnimationFrame(updateTextareas);