ElectronApp.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616
  1. window.TEMPLATE_PATH = 'templates';
  2. FeedbackDialog.feedbackUrl = 'https://log.draw.io/email';
  3. (function()
  4. {
  5. // Overrides default mode
  6. App.mode = App.MODE_DEVICE;
  7. // Redirects printing to iframe to avoid document.write
  8. PrintDialog.showPreview = function(preview, print)
  9. {
  10. var iframe = document.createElement('iframe');
  11. document.body.appendChild(iframe);
  12. // Workaround for lost gradients in print output
  13. var getBaseUrl = mxSvgCanvas2D.prototype.getBaseUrl;
  14. mxSvgCanvas2D.prototype.getBaseUrl = function()
  15. {
  16. return '';
  17. };
  18. // Renders print output into iframe and prints
  19. var result = preview.open(null, iframe.contentWindow);
  20. iframe.contentWindow.print();
  21. iframe.parentNode.removeChild(iframe);
  22. mxSvgCanvas2D.prototype.getBaseUrl = getBaseUrl;
  23. return result;
  24. };
  25. var menusInit = Menus.prototype.init;
  26. Menus.prototype.init = function()
  27. {
  28. menusInit.apply(this, arguments);
  29. var editorUi = this.editorUi;
  30. // Replaces file menu to replace openFrom menu with open and rename downloadAs to export
  31. this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
  32. {
  33. this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-'], parent);
  34. this.addSubmenu('exportAs', menu, parent);
  35. this.addSubmenu('embed', menu, parent);
  36. this.addMenuItems(menu, ['-', 'newLibrary', 'openLibrary', '-', 'documentProperties', 'print'], parent);
  37. })));
  38. this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
  39. {
  40. this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'mathematicalTypesetting', 'autosave', '-',
  41. 'createShape', 'editDiagram', '-', 'online'], parent);
  42. })));
  43. };
  44. // Initializes the user interface
  45. var editorUiInit = EditorUi.prototype.init;
  46. EditorUi.prototype.init = function()
  47. {
  48. editorUiInit.apply(this, arguments);
  49. var editorUi = this;
  50. var graph = this.editor.graph;
  51. // Adds support for libraries
  52. this.actions.addAction('newLibrary...', mxUtils.bind(this, function()
  53. {
  54. editorUi.showLibraryDialog(null, null, null, null, App.MODE_DEVICE);
  55. }));
  56. this.actions.addAction('openLibrary...', mxUtils.bind(this, function()
  57. {
  58. editorUi.pickLibrary(App.MODE_DEVICE);
  59. }));
  60. // // Replaces import action
  61. // this.actions.addAction('import...', mxUtils.bind(this, function()
  62. // {
  63. // if (this.getCurrentFile() != null)
  64. // {
  65. // chrome.fileSystem.chooseEntry({type: 'openFile', acceptsAllTypes: true}, mxUtils.bind(this, function(fileEntry)
  66. // {
  67. // if (!chrome.runtime.lastError)
  68. // {
  69. // fileEntry.file(mxUtils.bind(this, function(fileObject)
  70. // {
  71. // if (editorUi.spinner.spin(document.body, mxResources.get('loading')))
  72. // {
  73. // var reader = new FileReader();
  74. //
  75. // reader.onload = function(evt)
  76. // {
  77. // editorUi.spinner.stop();
  78. //
  79. // try
  80. // {
  81. // var data = reader.result;
  82. //
  83. // if (fileObject.type.substring(0, 9) == 'image/png')
  84. // {
  85. // data = editorUi.extractGraphModelFromPng(data);
  86. // }
  87. // else if (fileObject.type.substring(0, 6) == 'image/')
  88. // {
  89. // data = null;
  90. // }
  91. //
  92. // if (data != null)
  93. // {
  94. // graph.setSelectionCells(editorUi.importXml(data));
  95. // }
  96. // else
  97. // {
  98. // var img = new Image();
  99. // img.onload = function()
  100. // {
  101. // editorUi.resizeImage(img, reader.result, function(data2, w, h)
  102. // {
  103. // var pt = graph.getInsertPoint();
  104. // graph.setSelectionCell(graph.insertVertex(null, null, '', pt.x, pt.y, w, h,
  105. // 'shape=image;aspect=fixed;image=' + editorUi.convertDataUri(data2) + ';'));
  106. // }, true);
  107. // };
  108. // img.src = reader.result;
  109. // }
  110. // }
  111. // catch(e)
  112. // {
  113. // console.log(e);
  114. // editorUi.handleError(e);
  115. // }
  116. // };
  117. //
  118. // reader.onerror = function(ev)
  119. // {
  120. // editorUi.spinner.stop();
  121. // editorUi.handleError(ev);
  122. // };
  123. //
  124. // if (fileObject.type.substring(0, 6) == 'image/')
  125. // {
  126. // reader.readAsDataURL(fileObject);
  127. // }
  128. // else
  129. // {
  130. // reader.readAsText(fileObject);
  131. // }
  132. // }
  133. // }));
  134. // }
  135. // else if (chrome.runtime.lastError.message != 'User cancelled')
  136. // {
  137. // editorUi.handleError(chrome.runtime.lastError);
  138. // }
  139. // }));
  140. // }
  141. // }));
  142. // Replaces new action
  143. var oldNew = this.actions.get('new').funct;
  144. this.actions.addAction('new...', mxUtils.bind(this, function()
  145. {
  146. mxLog.debug(this.getCurrentFile());
  147. if (this.getCurrentFile() == null)
  148. {
  149. oldNew();
  150. }
  151. else
  152. {
  153. const electron = require('electron');
  154. const remote = electron.remote;
  155. const BrowserWindow = remote.BrowserWindow;
  156. mainWindow = new BrowserWindow({width: 1600, height: 1200, "web-security" : false});
  157. // and load the index.html of the app.
  158. mainWindow.loadURL(`file://${__dirname}/index.html?dev=1&test=1&db=0&gapi=0&od=0&analytics=0&picker=0&mode=device&browser=0&p=electron`);
  159. // Emitted when the window is closed.
  160. mainWindow.on('closed', function()
  161. {
  162. // Dereference the window object, usually you would store windows
  163. // in an array if your app supports multi windows, this is the time
  164. // when you should delete the corresponding element.
  165. mainWindow = null;
  166. });
  167. }
  168. }), null, null, 'Ctrl+N');
  169. this.actions.get('open').shortcut = 'Ctrl+O';
  170. // Adds shortcut keys for file operations
  171. editorUi.keyHandler.bindAction(78, true, 'new'); // Ctrl+N
  172. editorUi.keyHandler.bindAction(79, true, 'open'); // Ctrl+O
  173. editorUi.actions.addAction('quickStart...', function()
  174. {
  175. const {shell} = require('electron');
  176. shell.openExternal('https://www.youtube.com/watch?v=8OaMWa4R1SE&t=1');
  177. });
  178. this.actions.addAction('support...', function()
  179. {
  180. const {shell} = require('electron');
  181. shell.openExternal('https://support.draw.io/display/DFCC/draw.io+for+Confluence+Cloud');
  182. });
  183. editorUi.actions.addAction('userManual...', function()
  184. {
  185. const {shell} = require('electron');
  186. shell.openExternal('https://support.draw.io/display/DO/Draw.io+Online+User+Manual');
  187. });
  188. editorUi.actions.addAction('keyboardShortcuts...', function()
  189. {
  190. const electron = require('electron');
  191. const remote = electron.remote;
  192. const BrowserWindow = remote.BrowserWindow;
  193. keyboardWindow = new BrowserWindow({width: 1200, height: 1000});
  194. // and load the index.html of the app.
  195. keyboardWindow.loadURL(`file://${__dirname}/shortcuts.svg`);
  196. // Emitted when the window is closed.
  197. keyboardWindow.on('closed', function()
  198. {
  199. // Dereference the window object, usually you would store windows
  200. // in an array if your app supports multi windows, this is the time
  201. // when you should delete the corresponding element.
  202. keyboardWindow = null;
  203. });
  204. });
  205. }
  206. // Uses local picker
  207. App.prototype.pickFile = function()
  208. {
  209. this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data)
  210. {
  211. var file = new LocalFile(this, data, '');
  212. file.fileObject = fileEntry;
  213. this.fileLoaded(file);
  214. }));
  215. };
  216. /**
  217. * Selects a library to load from a picker
  218. *
  219. * @param mode the device mode, ignored in this case
  220. */
  221. App.prototype.pickLibrary = function(mode)
  222. {
  223. this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data)
  224. {
  225. var library = new LocalLibrary(this, data, fileEntry.name);
  226. library.fileObject = fileEntry;
  227. this.loadLibrary(library);
  228. }));
  229. };
  230. // Uses local picker
  231. App.prototype.chooseFileEntry = function(fn)
  232. {
  233. const electron = require('electron');
  234. var remote = electron.remote;
  235. var dialog = remote.dialog;
  236. var paths = dialog.showOpenDialog({properties: [ 'openFile' ] });
  237. if (paths !== undefined && paths[0] != null)
  238. {
  239. var fs = require('fs');
  240. var path = paths[0];
  241. var index = path.lastIndexOf('.png');
  242. var isPng = index > -1 && index == path.length - 4;
  243. var encoding = isPng ? 'base64' : 'utf-8'
  244. fs.readFile(path, encoding, mxUtils.bind(this, function (e, data)
  245. {
  246. if (e)
  247. {
  248. this.handleError(e);
  249. }
  250. else
  251. {
  252. if (isPng)
  253. {
  254. // Detecting png by extension. Would need https://github.com/mscdex/mmmagic
  255. // to do it by inspection
  256. data = this.extractGraphModelFromPng(data, true);
  257. }
  258. var fileEntry = new Object();
  259. fileEntry.path = path;
  260. fileEntry.name = path.replace(/^.*[\\\/]/, '');
  261. fileEntry.type = encoding;
  262. fn(fileEntry, data);
  263. }
  264. }));
  265. }
  266. };
  267. LocalFile.prototype.isAutosave = function()
  268. {
  269. return this.ui.editor.autosave;
  270. };
  271. LocalFile.prototype.isAutosaveOptional = function()
  272. {
  273. return true;
  274. };
  275. LocalLibrary.prototype.isAutosave = function()
  276. {
  277. return true;
  278. };
  279. LocalFile.prototype.getTitle = function()
  280. {
  281. return (this.fileObject != null) ? this.fileObject.name : null;
  282. };
  283. LocalFile.prototype.isRenamable = function()
  284. {
  285. return false;
  286. };
  287. // Restores default implementation of open with autosave
  288. LocalFile.prototype.open = DrawioFile.prototype.open;
  289. LocalFile.prototype.save = function(revision, success, error)
  290. {
  291. DrawioFile.prototype.save.apply(this, arguments);
  292. this.saveFile(revision, success, error);
  293. };
  294. LocalLibrary.prototype.save = function(revision, success, error)
  295. {
  296. this.saveFile(revision, success, error);
  297. };
  298. LocalFile.prototype.saveFile = function(revision, success, error)
  299. {
  300. var fn = mxUtils.bind(this, function()
  301. {
  302. var doSave = mxUtils.bind(this, function(data)
  303. {
  304. if (!this.savingFile)
  305. {
  306. this.savingFile = true;
  307. // Makes sure no changes get lost while the file is saved
  308. var prevModified = this.isModified;
  309. var modified = this.isModified();
  310. this.setModified(false);
  311. var fs = require('fs');
  312. fs.writeFile(this.fileObject.path, data, this.fileObject.encoding, mxUtils.bind(this, function (e)
  313. {
  314. if (e)
  315. {
  316. this.savingFile = false;
  317. this.isModified = prevModified;
  318. this.setModified(modified || this.isModified());
  319. if (error != null)
  320. {
  321. error();
  322. }
  323. }
  324. else
  325. {
  326. this.savingFile = false;
  327. this.isModified = prevModified;
  328. this.contentChanged();
  329. if (success != null)
  330. {
  331. success();
  332. }
  333. }
  334. }));
  335. }
  336. else
  337. {
  338. // TODO, already saving. Need a better error
  339. error();
  340. }
  341. });
  342. if (!/(\.png)$/i.test(this.fileObject.name))
  343. {
  344. doSave(this.getData());
  345. }
  346. else
  347. {
  348. this.ui.exportToCanvas(mxUtils.bind(this, function(canvas)
  349. {
  350. try
  351. {
  352. var data = canvas.toDataURL('image/png');
  353. data = this.ui.writeGraphModelToPng(data, 'zTXt', 'mxGraphModel',
  354. atob(this.ui.editor.graph.compress(mxUtils.getXml(this.ui.editor.getGraphXml()))));
  355. doSave(data, 'base64');
  356. }
  357. catch (e)
  358. {
  359. if (error != null)
  360. {
  361. error(e);
  362. }
  363. }
  364. }), null, null, null, mxUtils.bind(this, function(e)
  365. {
  366. if (error != null)
  367. {
  368. error(e);
  369. }
  370. }));
  371. }
  372. });
  373. if (this.fileObject == null)
  374. {
  375. const electron = require('electron');
  376. var remote = electron.remote;
  377. var dialog = remote.dialog;
  378. var path = dialog.showSaveDialog();
  379. if (path != null)
  380. {
  381. this.fileObject = new Object();
  382. this.fileObject.path = path;
  383. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  384. this.fileObject.type = 'utf-8';
  385. fn();
  386. }
  387. }
  388. else
  389. {
  390. fn();
  391. }
  392. };
  393. LocalFile.prototype.saveAs = function(title, success, error)
  394. {
  395. const electron = require('electron');
  396. var remote = electron.remote;
  397. var dialog = remote.dialog;
  398. var path = dialog.showSaveDialog();
  399. if (path != null)
  400. {
  401. this.fileObject = new Object();
  402. this.fileObject.path = path;
  403. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  404. this.fileObject.type = 'utf-8';
  405. this.save(false, success, error);
  406. }
  407. };
  408. App.prototype.saveFile = function(forceDialog)
  409. {
  410. var file = this.getCurrentFile();
  411. if (file != null)
  412. {
  413. if (!forceDialog && file.getTitle() != null)
  414. {
  415. file.save(true, mxUtils.bind(this, function(resp)
  416. {
  417. this.spinner.stop();
  418. this.editor.setStatus(mxResources.get('allChangesSaved'));
  419. }), mxUtils.bind(this, function(resp)
  420. {
  421. this.editor.setStatus('');
  422. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  423. }));
  424. }
  425. else
  426. {
  427. file.saveAs(null, mxUtils.bind(this, function(resp)
  428. {
  429. this.spinner.stop();
  430. this.editor.setStatus(mxResources.get('allChangesSaved'));
  431. }), mxUtils.bind(this, function(resp)
  432. {
  433. this.editor.setStatus('');
  434. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  435. }));
  436. }
  437. }
  438. };
  439. /**
  440. * Translates this point by the given vector.
  441. *
  442. * @param {number} dx X-coordinate of the translation.
  443. * @param {number} dy Y-coordinate of the translation.
  444. */
  445. App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn)
  446. {
  447. mode = (mode != null) ? mode : this.mode;
  448. noSpin = (noSpin != null) ? noSpin : false;
  449. noReload = (noReload != null) ? noReload : false;
  450. var xml = this.createLibraryDataFromImages(images);
  451. var error = mxUtils.bind(this, function(resp)
  452. {
  453. this.spinner.stop();
  454. if (fn != null)
  455. {
  456. fn();
  457. }
  458. // Null means cancel by user and is ignored
  459. if (resp != null)
  460. {
  461. this.handleError(resp, mxResources.get('errorSavingFile'));
  462. }
  463. });
  464. // Handles special case for local libraries
  465. if (file == null)
  466. {
  467. file = new LocalLibrary(this, xml, name);
  468. }
  469. if (noSpin || this.spinner.spin(document.body, mxResources.get('saving')))
  470. {
  471. file.setData(xml);
  472. var doSave = mxUtils.bind(this, function()
  473. {
  474. file.save(true, mxUtils.bind(this, function(resp)
  475. {
  476. this.spinner.stop();
  477. this.hideDialog(true);
  478. if (!noReload)
  479. {
  480. this.libraryLoaded(file, images)
  481. }
  482. if (fn != null)
  483. {
  484. fn();
  485. }
  486. }), error);
  487. });
  488. if (name != file.getTitle())
  489. {
  490. var oldHash = file.getHash();
  491. file.rename(name, mxUtils.bind(this, function(resp)
  492. {
  493. // Change hash in stored settings
  494. if (file.constructor != LocalLibrary && oldHash != file.getHash())
  495. {
  496. mxSettings.removeCustomLibrary(oldHash);
  497. mxSettings.addCustomLibrary(file.getHash());
  498. }
  499. // Workaround for library files changing hash so
  500. // the old library cannot be removed from the
  501. // sidebar using the updated file in libraryLoaded
  502. this.removeLibrarySidebar(oldHash);
  503. doSave();
  504. }), error)
  505. }
  506. else
  507. {
  508. doSave();
  509. }
  510. }
  511. };
  512. EditorUi.prototype.saveData = function(filename, format, data, mimeType, base64Encoded)
  513. {
  514. const electron = require('electron');
  515. var remote = electron.remote;
  516. var dialog = remote.dialog;
  517. var path = dialog.showSaveDialog();
  518. if (path != null)
  519. {
  520. this.fileObject = new Object();
  521. this.fileObject.path = path;
  522. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  523. var isImage = mimeType != null && mimeType.startsWith('image');
  524. this.fileObject.type = base64Encoded ? 'base64' : 'utf-8';
  525. var fs = require('fs');
  526. fs.writeFile(this.fileObject.path, data, this.fileObject.type, mxUtils.bind(this, function (e)
  527. {
  528. if (e)
  529. {
  530. // TODO
  531. }
  532. }));
  533. }
  534. };
  535. EditorUi.prototype.addBeforeUnloadListener = function() {};
  536. })();