replay.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /**
  2. * Replay plugin. To record steps in the Editor, click on Extras, Record.
  3. * To stop recording click Extras, Record again. Enter the delay between
  4. * the steps and use the URL that opens in the new window.
  5. */
  6. Draw.loadPlugin(function(ui) {
  7. var graph = ui.editor.graph;
  8. var model = graph.model;
  9. function decodeChanges(delta, direct)
  10. {
  11. var codec2 = new mxCodec(delta.ownerDocument);
  12. codec2.lookup = function(id)
  13. {
  14. return model.getCell(id);
  15. };
  16. var changeNode = (direct) ? delta.firstChild : delta.firstChild.firstChild;
  17. var changes = [];
  18. while (changeNode != null)
  19. {
  20. var change = codec2.decode(changeNode);
  21. change.model = model;
  22. change.execute();
  23. changes.push(change);
  24. changeNode = changeNode.nextSibling;
  25. }
  26. return changes;
  27. };
  28. function createUndoableEdit(changes)
  29. {
  30. var edit = new mxUndoableEdit(model);
  31. edit.changes = changes;
  32. edit.notify = function()
  33. {
  34. // LATER: Remove changes property (deprecated)
  35. edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
  36. 'edit', edit, 'changes', edit.changes));
  37. edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
  38. 'edit', edit, 'changes', edit.changes));
  39. };
  40. return edit;
  41. };
  42. function processDelta(delta, direct)
  43. {
  44. var changes = decodeChanges(delta, direct);
  45. if (changes.length > 0)
  46. {
  47. var edit = createUndoableEdit(changes);
  48. if (ui.chromelessResize)
  49. {
  50. // No notify event here to avoid the edit from being encoded and transmitted
  51. // LATER: Remove changes property (deprecated)
  52. model.fireEvent(new mxEventObject(mxEvent.CHANGE,
  53. 'edit', edit, 'changes', changes));
  54. model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
  55. ui.chromelessResize();
  56. }
  57. else
  58. {
  59. edit.notify();
  60. }
  61. }
  62. return edit;
  63. };
  64. if (ui.editor.isChromelessView())
  65. {
  66. var replayData = urlParams['replay-data'];
  67. var delay = parseInt(urlParams['delay-delay'] || 1000);
  68. if (replayData != null)
  69. {
  70. var xmlDoc = mxUtils.parseXml(Graph.decompress(replayData));
  71. // LATER: Avoid duplicate parsing
  72. ui.fileLoaded(new LocalFile(ui, mxUtils.getXml(xmlDoc.documentElement.firstChild.firstChild)));
  73. // Process deltas
  74. var delta = xmlDoc.documentElement.firstChild.nextSibling;
  75. function nextStep()
  76. {
  77. if (delta != null)
  78. {
  79. window.setTimeout(function()
  80. {
  81. try
  82. {
  83. processDelta(delta);
  84. delta = delta.nextSibling;
  85. nextStep();
  86. }
  87. catch (e)
  88. {
  89. ui.handleError(e);
  90. }
  91. }, delay);
  92. }
  93. };
  94. nextStep();
  95. }
  96. }
  97. else
  98. {
  99. var tape = null;
  100. var codec = new mxCodec();
  101. codec.lookup = function(id)
  102. {
  103. return model.getCell(id);
  104. };
  105. model.addListener(mxEvent.CHANGE, function(sender, evt)
  106. {
  107. if (tape != null)
  108. {
  109. var changes = evt.getProperty('changes');
  110. var node = codec.encode(changes);
  111. var delta = codec.document.createElement('delta');
  112. delta.appendChild(node);
  113. tape.push(mxUtils.getXml(delta));
  114. }
  115. });
  116. mxResources.parse('record=Record');
  117. mxResources.parse('replay=Replay');
  118. // Adds actions
  119. var action = ui.actions.addAction('record...', function()
  120. {
  121. if (tape == null)
  122. {
  123. var node = codec.encode(model);
  124. var state = codec.document.createElement('state');
  125. state.appendChild(node);
  126. tape =[mxUtils.getXml(state)];
  127. ui.editor.setStatus('Recording started');
  128. }
  129. else if (tape != null)
  130. {
  131. ui.editor.setStatus('Recording stopped');
  132. var tmp = tape;
  133. tape = null;
  134. var dlg = new FilenameDialog(ui, 1000, mxResources.get('apply'), function(newValue)
  135. {
  136. if (newValue != null)
  137. {
  138. var dlg = new EmbedDialog(ui, 'https://www.draw.io/?p=replay&lightbox=1&replay-delay=' +
  139. parseFloat(newValue) + '&replay-data=' + Graph.compress('<recording>' +
  140. tmp.join('') + '</recording>'));
  141. ui.showDialog(dlg.container, 450, 240, true, true);
  142. dlg.init();
  143. }
  144. }, 'Delay');
  145. ui.showDialog(dlg.container, 300, 80, true, true);
  146. dlg.init();
  147. }
  148. action.label = (tape != null) ? 'Stop recording' : mxResources.get('record') + '...';
  149. });
  150. ui.actions.addAction('replay...', function()
  151. {
  152. var dlg = new TextareaDialog(ui, 'Changes [JSON export, compressed edits or <edit>..</edit>]:', '',
  153. function(newValue)
  154. {
  155. if (newValue.length > 0)
  156. {
  157. try
  158. {
  159. var current = null;
  160. if (newValue.charAt(0) == '{')
  161. {
  162. var temp = JSON.parse(newValue);
  163. current = temp.current;
  164. newValue = temp.edits;
  165. }
  166. if (newValue.charAt(0) != '<')
  167. {
  168. newValue = Graph.decompress(newValue);
  169. }
  170. if (newValue.charAt(0) == '[')
  171. {
  172. newValue = JSON.parse(newValue);
  173. console.log(JSON.stringify(newValue, null, 2));
  174. var pageId = null;
  175. var temp = [];
  176. for (var i = 0; i < newValue.length; i++)
  177. {
  178. if (pageId == null)
  179. {
  180. pageId = newValue[i].pageid;
  181. }
  182. if (pageId == newValue[i].pageid)
  183. {
  184. temp.push(newValue[i].data);
  185. }
  186. else
  187. {
  188. mxLog.debug('edit ignored for page ' + newValue[i].pageid);
  189. mxLog.show();
  190. }
  191. }
  192. newValue = temp.join('');
  193. }
  194. var edits = mxUtils.parseXml('<edits>' + newValue + '</edits>');
  195. var edit = edits.documentElement.firstChild;
  196. function step()
  197. {
  198. try
  199. {
  200. console.log(processDelta(edit, true));
  201. edit = edit.nextSibling;
  202. }
  203. catch (e)
  204. {
  205. ui.handleError(e);
  206. edit = null;
  207. }
  208. return edit != null;
  209. }
  210. if (ui.buttonContainer != null)
  211. {
  212. console.log(mxUtils.getPrettyXml(edit));
  213. var button = mxUtils.button('Step', function()
  214. {
  215. if (!step())
  216. {
  217. button.parentNode.removeChild(button);
  218. }
  219. else
  220. {
  221. console.log(mxUtils.getPrettyXml(edit));
  222. }
  223. });
  224. button.className = 'geBtn gePrimaryBtn';
  225. ui.buttonContainer.appendChild(button);
  226. }
  227. else
  228. {
  229. while (step())
  230. {
  231. // repeat
  232. }
  233. }
  234. }
  235. catch (e)
  236. {
  237. ui.handleError(e);
  238. console.error(e);
  239. }
  240. }
  241. });
  242. ui.showDialog(dlg.container, 620, 460, true, true);
  243. dlg.init();
  244. });
  245. var menu = ui.menus.get('extras');
  246. var oldFunct = menu.funct;
  247. menu.funct = function(menu, parent)
  248. {
  249. oldFunct.apply(this, arguments);
  250. ui.menus.addMenuItems(menu, ['-', 'record', 'replay'], parent);
  251. };
  252. }
  253. });