ElectronApp.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. window.TEMPLATE_PATH = 'templates';
  2. (function()
  3. {
  4. // Overrides default mode
  5. App.mode = App.MODE_DEVICE;
  6. // Redirects printing to iframe to avoid document.write
  7. PrintDialog.showPreview = function(preview, print)
  8. {
  9. var iframe = document.createElement('iframe');
  10. document.body.appendChild(iframe);
  11. // Workaround for lost gradients in print output
  12. var getBaseUrl = mxSvgCanvas2D.prototype.getBaseUrl;
  13. mxSvgCanvas2D.prototype.getBaseUrl = function()
  14. {
  15. return '';
  16. };
  17. // Renders print output into iframe and prints
  18. var result = preview.open(null, iframe.contentWindow);
  19. iframe.contentWindow.print();
  20. iframe.parentNode.removeChild(iframe);
  21. mxSvgCanvas2D.prototype.getBaseUrl = getBaseUrl;
  22. return result;
  23. };
  24. var menusInit = Menus.prototype.init;
  25. Menus.prototype.init = function()
  26. {
  27. menusInit.apply(this, arguments);
  28. var editorUi = this.editorUi;
  29. editorUi.actions.addAction('online...', function()
  30. {
  31. window.open('https://www.draw.io/');
  32. });
  33. // Replaces file menu to replace openFrom menu with open and rename downloadAs to export
  34. this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
  35. {
  36. this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import'], parent);
  37. this.addSubmenu('exportAs', menu, parent);
  38. this.addSubmenu('embed', menu, parent);
  39. this.addMenuItems(menu, ['-', 'newLibrary', 'openLibrary', '-', 'documentProperties', 'print'], parent);
  40. })));
  41. this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
  42. {
  43. this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'gridColor', 'autosave', '-',
  44. 'createShape', 'editDiagram', '-', 'online'], parent);
  45. })));
  46. };
  47. // Uses local picker
  48. App.prototype.pickFile = function()
  49. {
  50. this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data)
  51. {
  52. var file = new LocalFile(this, data, '');
  53. file.fileObject = fileEntry;
  54. this.fileLoaded(file);
  55. }));
  56. };
  57. /**
  58. * Selects a library to load from a picker
  59. *
  60. * @param mode the device mode, ignored in this case
  61. */
  62. App.prototype.pickLibrary = function(mode)
  63. {
  64. this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data)
  65. {
  66. var library = new LocalLibrary(this, data, fileEntry.name);
  67. library.fileObject = fileEntry;
  68. this.loadLibrary(library);
  69. }));
  70. };
  71. // Uses local picker
  72. App.prototype.chooseFileEntry = function(fn)
  73. {
  74. const electron = require('electron');
  75. var remote = electron.remote;
  76. var dialog = remote.dialog;
  77. var paths = dialog.showOpenDialog({properties: [ 'openFile' ] });
  78. if (paths !== undefined && paths[0] != null)
  79. {
  80. var fs = require('fs');
  81. var path = paths[0];
  82. var index = path.lastIndexOf('.png');
  83. var isPng = index > -1 && index == path.length - 4;
  84. var encoding = isPng ? 'base64' : 'utf-8'
  85. fs.readFile(path, encoding, mxUtils.bind(this, function (e, data)
  86. {
  87. if (e)
  88. {
  89. this.handleError(e);
  90. }
  91. else
  92. {
  93. if (isPng)
  94. {
  95. // Detecting png by extension. Would need https://github.com/mscdex/mmmagic
  96. // to do it by inspection
  97. data = this.extractGraphModelFromPng(data, true);
  98. }
  99. var fileEntry = new Object();
  100. fileEntry.path = path;
  101. fileEntry.name = path.replace(/^.*[\\\/]/, '');
  102. fileEntry.type = encoding;
  103. fn(fileEntry, data);
  104. }
  105. }));
  106. }
  107. };
  108. LocalFile.prototype.isAutosave = function()
  109. {
  110. return this.ui.editor.autosave;
  111. };
  112. LocalFile.prototype.isAutosaveOptional = function()
  113. {
  114. return true;
  115. };
  116. LocalLibrary.prototype.isAutosave = function()
  117. {
  118. return true;
  119. };
  120. LocalFile.prototype.getTitle = function()
  121. {
  122. return (this.fileObject != null) ? this.fileObject.name : null;
  123. };
  124. LocalFile.prototype.isRenamable = function()
  125. {
  126. return false;
  127. };
  128. // Restores default implementation of open with autosave
  129. LocalFile.prototype.open = DrawioFile.prototype.open;
  130. LocalFile.prototype.save = function(revision, success, error)
  131. {
  132. DrawioFile.prototype.save.apply(this, arguments);
  133. this.saveFile(revision, success, error);
  134. };
  135. LocalLibrary.prototype.save = function(revision, success, error)
  136. {
  137. this.saveFile(revision, success, error);
  138. };
  139. LocalFile.prototype.saveFile = function(revision, success, error)
  140. {
  141. var fn = mxUtils.bind(this, function()
  142. {
  143. var doSave = mxUtils.bind(this, function(data)
  144. {
  145. if (!this.savingFile)
  146. {
  147. this.savingFile = true;
  148. // Makes sure no changes get lost while the file is saved
  149. var prevModified = this.isModified;
  150. var modified = this.isModified();
  151. this.setModified(false);
  152. var fs = require('fs');
  153. fs.writeFile(this.fileObject.path, data, this.fileObject.encoding, mxUtils.bind(this, function (e)
  154. {
  155. if (e)
  156. {
  157. this.savingFile = false;
  158. this.isModified = prevModified;
  159. this.setModified(modified || this.isModified());
  160. if (error != null)
  161. {
  162. error();
  163. }
  164. }
  165. else
  166. {
  167. this.savingFile = false;
  168. this.isModified = prevModified;
  169. this.contentChanged();
  170. if (success != null)
  171. {
  172. success();
  173. }
  174. }
  175. }));
  176. }
  177. else
  178. {
  179. // TODO, already saving. Need a better error
  180. error();
  181. }
  182. });
  183. if (!/(\.png)$/i.test(this.fileObject.name))
  184. {
  185. doSave(this.getData());
  186. }
  187. else
  188. {
  189. this.ui.exportToCanvas(mxUtils.bind(this, function(canvas)
  190. {
  191. try
  192. {
  193. var data = canvas.toDataURL('image/png');
  194. data = this.ui.writeGraphModelToPng(data, 'zTXt', 'mxGraphModel',
  195. atob(this.ui.editor.graph.compress(mxUtils.getXml(this.ui.editor.getGraphXml()))));
  196. doSave(data, 'base64');
  197. }
  198. catch (e)
  199. {
  200. if (error != null)
  201. {
  202. error(e);
  203. }
  204. }
  205. }), null, null, null, mxUtils.bind(this, function(e)
  206. {
  207. if (error != null)
  208. {
  209. error(e);
  210. }
  211. }));
  212. }
  213. });
  214. if (this.fileObject == null)
  215. {
  216. const electron = require('electron');
  217. var remote = electron.remote;
  218. var dialog = remote.dialog;
  219. var path = dialog.showSaveDialog();
  220. // chrome.fileSystem.chooseEntry({type: 'saveFile',
  221. // accepts: [(this.constructor == LocalFile) ? {description: 'Draw.io Diagram (.xml)',
  222. // extensions: ['xml']} : {description: 'Draw.io Library (.xml)',
  223. // extensions: ['xml']}]}, mxUtils.bind(this, function(xmlFile)
  224. if (path != null)
  225. {
  226. this.fileObject = new Object();
  227. this.fileObject.path = path;
  228. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  229. this.fileObject.type = 'utf-8';
  230. fn();
  231. }
  232. }
  233. else
  234. {
  235. fn();
  236. }
  237. };
  238. LocalFile.prototype.saveAs = function(title, success, error)
  239. {
  240. const electron = require('electron');
  241. var remote = electron.remote;
  242. var dialog = remote.dialog;
  243. var path = dialog.showSaveDialog();
  244. // chrome.fileSystem.chooseEntry({type: 'saveFile',
  245. // accepts: [(this.constructor == LocalFile) ? {description: 'Draw.io Diagram (.xml)',
  246. // extensions: ['xml']} : {description: 'Draw.io Library (.xml)',
  247. // extensions: ['xml']}]}, mxUtils.bind(this, function(f)
  248. if (path != null)
  249. {
  250. this.fileObject = new Object();
  251. this.fileObject.path = path;
  252. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  253. this.fileObject.type = 'utf-8';
  254. this.save(false, success, error);
  255. }
  256. };
  257. App.prototype.saveFile = function(forceDialog)
  258. {
  259. var file = this.getCurrentFile();
  260. if (file != null)
  261. {
  262. if (!forceDialog && file.getTitle() != null)
  263. {
  264. file.save(true, mxUtils.bind(this, function(resp)
  265. {
  266. this.spinner.stop();
  267. this.editor.setStatus(mxResources.get('allChangesSaved'));
  268. }), mxUtils.bind(this, function(resp)
  269. {
  270. this.editor.setStatus('');
  271. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  272. }));
  273. }
  274. else
  275. {
  276. file.saveAs(null, mxUtils.bind(this, function(resp)
  277. {
  278. this.spinner.stop();
  279. this.editor.setStatus(mxResources.get('allChangesSaved'));
  280. }), mxUtils.bind(this, function(resp)
  281. {
  282. this.editor.setStatus('');
  283. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  284. }));
  285. }
  286. }
  287. };
  288. /**
  289. * Translates this point by the given vector.
  290. *
  291. * @param {number} dx X-coordinate of the translation.
  292. * @param {number} dy Y-coordinate of the translation.
  293. */
  294. App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn)
  295. {
  296. mode = (mode != null) ? mode : this.mode;
  297. noSpin = (noSpin != null) ? noSpin : false;
  298. noReload = (noReload != null) ? noReload : false;
  299. var xml = this.createLibraryDataFromImages(images);
  300. var error = mxUtils.bind(this, function(resp)
  301. {
  302. this.spinner.stop();
  303. if (fn != null)
  304. {
  305. fn();
  306. }
  307. // Null means cancel by user and is ignored
  308. if (resp != null)
  309. {
  310. this.handleError(resp, mxResources.get('errorSavingFile'));
  311. }
  312. });
  313. // Handles special case for local libraries
  314. if (file == null)
  315. {
  316. file = new LocalLibrary(this, xml, name);
  317. }
  318. if (noSpin || this.spinner.spin(document.body, mxResources.get('saving')))
  319. {
  320. file.setData(xml);
  321. var doSave = mxUtils.bind(this, function()
  322. {
  323. file.save(true, mxUtils.bind(this, function(resp)
  324. {
  325. this.spinner.stop();
  326. this.hideDialog(true);
  327. if (!noReload)
  328. {
  329. this.libraryLoaded(file, images)
  330. }
  331. if (fn != null)
  332. {
  333. fn();
  334. }
  335. }), error);
  336. });
  337. if (name != file.getTitle())
  338. {
  339. var oldHash = file.getHash();
  340. file.rename(name, mxUtils.bind(this, function(resp)
  341. {
  342. // Change hash in stored settings
  343. if (file.constructor != LocalLibrary && oldHash != file.getHash())
  344. {
  345. mxSettings.removeCustomLibrary(oldHash);
  346. mxSettings.addCustomLibrary(file.getHash());
  347. }
  348. // Workaround for library files changing hash so
  349. // the old library cannot be removed from the
  350. // sidebar using the updated file in libraryLoaded
  351. this.removeLibrarySidebar(oldHash);
  352. doSave();
  353. }), error)
  354. }
  355. else
  356. {
  357. doSave();
  358. }
  359. }
  360. };
  361. App.prototype.doSaveLocalFile = function(data, filename, mimeType, base64Encoded)
  362. {
  363. chrome.fileSystem.chooseEntry({type: 'saveFile', suggestedName: filename, acceptsAllTypes: true}, mxUtils.bind(this, function(fileEntry)
  364. {
  365. if (!chrome.runtime.lastError)
  366. {
  367. fileEntry.createWriter(mxUtils.bind(this, function(writer)
  368. {
  369. writer.onwriteend = mxUtils.bind(this, function()
  370. {
  371. writer.onwriteend = null;
  372. writer.write((base64Encoded) ? this.base64ToBlob(data, mimeType) : new Blob([data], {type: mimeType}));
  373. });
  374. writer.onerror = mxUtils.bind(this, function(e)
  375. {
  376. this.handleError(e);
  377. });
  378. writer.truncate(0);
  379. }));
  380. }
  381. else if (chrome.runtime.lastError.message != 'User cancelled')
  382. {
  383. this.handleError(chrome.runtime.lastError);
  384. }
  385. }));
  386. };
  387. })();