ElectronApp.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815
  1. window.OPEN_URL = 'https://www.draw.io/open';
  2. window.TEMPLATE_PATH = 'templates';
  3. FeedbackDialog.feedbackUrl = 'https://log.draw.io/email';
  4. (function()
  5. {
  6. // Overrides default mode
  7. App.mode = App.MODE_DEVICE;
  8. // Disables new window option in edit diagram dialog
  9. EditDiagramDialog.showNewWindowOption = false;
  10. // Redirects printing to iframe to avoid document.write
  11. var printDialogCreatePrintPreview = PrintDialog.createPrintPreview;
  12. PrintDialog.createPrintPreview = function()
  13. {
  14. var iframe = document.createElement('iframe');
  15. document.body.appendChild(iframe);
  16. var result = printDialogCreatePrintPreview.apply(this, arguments);
  17. result.wnd = iframe.contentWindow;
  18. result.iframe = iframe;
  19. // Workaround for lost gradients in print output
  20. result.previousGetBaseUrl = mxSvgCanvas2D.prototype.getBaseUrl;
  21. mxSvgCanvas2D.prototype.getBaseUrl = function()
  22. {
  23. return '';
  24. };
  25. return result;
  26. };
  27. var oldWindowOpen = window.open;
  28. window.open = function(url)
  29. {
  30. if (url != null && url.startsWith('http'))
  31. {
  32. const {shell} = require('electron');
  33. shell.openExternal(url);
  34. }
  35. else
  36. {
  37. return oldWindowOpen(url);
  38. }
  39. }
  40. mxPrintPreview.prototype.addPageBreak = function(doc)
  41. {
  42. // Do nothing
  43. };
  44. mxPrintPreview.prototype.closeDocument = function()
  45. {
  46. var doc = this.wnd.document;
  47. // Removes all event handlers in the print output
  48. mxEvent.release(doc.body);
  49. };
  50. PrintDialog.printPreview = function(preview)
  51. {
  52. if (preview.iframe != null)
  53. {
  54. preview.iframe.contentWindow.print();
  55. preview.iframe.parentNode.removeChild(preview.iframe);
  56. mxSvgCanvas2D.prototype.getBaseUrl = preview.previousGetBaseUrl;
  57. preview.iframe = null;
  58. }
  59. };
  60. PrintDialog.previewEnabled = false;
  61. var menusInit = Menus.prototype.init;
  62. Menus.prototype.init = function()
  63. {
  64. menusInit.apply(this, arguments);
  65. var editorUi = this.editorUi;
  66. // Replaces file menu to replace openFrom menu with open and rename downloadAs to export
  67. this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
  68. {
  69. this.addMenuItems(menu, ['new', 'open', '-', 'save', 'saveAs', '-', 'import'], parent);
  70. this.addSubmenu('exportAs', menu, parent);
  71. this.addSubmenu('embed', menu, parent);
  72. this.addMenuItems(menu, ['-', 'newLibrary', 'openLibrary', '-', 'documentProperties', 'print'], parent);
  73. })));
  74. this.put('extras', new Menu(mxUtils.bind(this, function(menu, parent)
  75. {
  76. this.addMenuItems(menu, ['copyConnect', 'collapseExpand', '-', 'mathematicalTypesetting', 'autosave', '-',
  77. 'createShape', 'editDiagram', '-', 'tags', '-', 'online'], parent);
  78. })));
  79. };
  80. // Initializes the user interface
  81. var editorUiInit = EditorUi.prototype.init;
  82. EditorUi.prototype.init = function()
  83. {
  84. editorUiInit.apply(this, arguments);
  85. var editorUi = this;
  86. var graph = this.editor.graph;
  87. this.editor.autosave = false;
  88. global.__emt_isModified = e => {
  89. if (this.getCurrentFile())
  90. return this.getCurrentFile().isModified()
  91. return false
  92. }
  93. // global.__emt_getCurrentFile = e => {
  94. // return this.getCurrentFile()
  95. // }
  96. // Adds support for libraries
  97. this.actions.addAction('newLibrary...', mxUtils.bind(this, function()
  98. {
  99. editorUi.showLibraryDialog(null, null, null, null, App.MODE_DEVICE);
  100. }));
  101. this.actions.addAction('openLibrary...', mxUtils.bind(this, function()
  102. {
  103. editorUi.pickLibrary(App.MODE_DEVICE);
  104. }));
  105. // Replaces import action
  106. this.actions.addAction('import...', mxUtils.bind(this, function()
  107. {
  108. if (editorUi.getCurrentFile() != null)
  109. {
  110. const electron = require('electron');
  111. var remote = electron.remote;
  112. var dialog = remote.dialog;
  113. var paths = dialog.showOpenDialog({properties: ['openFile']});
  114. if (paths !== undefined && paths[0] != null)
  115. {
  116. var path = paths[0];
  117. var asImage = /\.png$/i.test(path) || /\.gif$/i.test(path) || /\.jpe?g$/i.test(path);
  118. var encoding = (asImage || /\.vsdx$/i.test(path) || /\.vssx$/i.test(path)) ?
  119. 'base64' : 'utf-8';
  120. if (editorUi.spinner.spin(document.body, mxResources.get('loading')))
  121. {
  122. var fs = require('fs');
  123. fs.readFile(path, encoding, mxUtils.bind(this, function (e, data)
  124. {
  125. if (e)
  126. {
  127. editorUi.spinner.stop();
  128. editorUi.handleError(e);
  129. }
  130. else
  131. {
  132. try
  133. {
  134. if (data.substring(0, 26) == '{"state":"{\\"Properties\\":')
  135. {
  136. editorUi.importLucidChart(data, 0, 0, null, function()
  137. {
  138. editorUi.spinner.stop();
  139. });
  140. }
  141. else
  142. {
  143. if (/\.png$/i.test(path))
  144. {
  145. var tmp = editorUi.extractGraphModelFromPng(data);
  146. if (tmp != null)
  147. {
  148. asImage = false;
  149. data = tmp;
  150. }
  151. }
  152. else if (/\.svg$/i.test(path))
  153. {
  154. // LATER: Use importXml without throwing exception if no data
  155. // Checks if SVG contains content attribute
  156. var root = mxUtils.parseXml(data);
  157. var svgs = root.getElementsByTagName('svg');
  158. if (svgs.length > 0)
  159. {
  160. var svgRoot = svgs[0];
  161. var cont = svgRoot.getAttribute('content');
  162. if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%')
  163. {
  164. cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
  165. }
  166. if (cont != null && cont.charAt(0) == '%')
  167. {
  168. cont = decodeURIComponent(cont);
  169. }
  170. if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
  171. cont.substring(0, 14) === '<mxGraphModel '))
  172. {
  173. asImage = false;
  174. data = cont;
  175. }
  176. else
  177. {
  178. asImage = true;
  179. data = btoa(data);
  180. }
  181. }
  182. }
  183. else if (!editorUi.isOffline() && new XMLHttpRequest().upload && editorUi.isRemoteFileFormat(data, path))
  184. {
  185. // Asynchronous parsing via server
  186. editorUi.parseFile(editorUi.base64ToBlob(data, 'application/octet-stream'), mxUtils.bind(this, function(xhr)
  187. {
  188. if (xhr.readyState == 4)
  189. {
  190. editorUi.spinner.stop();
  191. if (xhr.status >= 200 && xhr.status <= 299)
  192. {
  193. editorUi.editor.graph.setSelectionCells(editorUi.insertTextAt(xhr.responseText, 0, 0, true));
  194. }
  195. }
  196. }), path);
  197. }
  198. if (asImage)
  199. {
  200. var img = new Image();
  201. img.onload = function()
  202. {
  203. editorUi.resizeImage(img, img.src, function(data2, w, h)
  204. {
  205. editorUi.spinner.stop();
  206. var pt = graph.getInsertPoint();
  207. graph.setSelectionCell(graph.insertVertex(null, null, '', pt.x, pt.y, w, h,
  208. 'shape=image;aspect=fixed;image=' + editorUi.convertDataUri(data2) + ';'));
  209. }, true);
  210. };
  211. img.onerror = function(e)
  212. {
  213. editorUi.spinner.stop();
  214. editorUi.handleError();
  215. };
  216. var format = path.substring(path.lastIndexOf('.') + 1);
  217. if (format == 'svg')
  218. {
  219. format = 'svg+xml';
  220. }
  221. img.src = 'data:image/' + format + ';base64,' + data;
  222. }
  223. else
  224. {
  225. editorUi.spinner.stop();
  226. if (data != null)
  227. {
  228. graph.setSelectionCells(editorUi.importXml(data));
  229. }
  230. }
  231. }
  232. }
  233. catch(e)
  234. {
  235. editorUi.spinner.stop();
  236. editorUi.handleError(e);
  237. }
  238. }
  239. }));
  240. }
  241. }
  242. }
  243. }));
  244. // Replaces new action
  245. var oldNew = this.actions.get('new').funct;
  246. this.actions.addAction('new...', mxUtils.bind(this, function()
  247. {
  248. if (this.getCurrentFile() == null)
  249. {
  250. oldNew();
  251. }
  252. else {
  253. const ipc = require('electron').ipcRenderer
  254. ipc.sendSync('winman', {action: 'newfile', opt: {width: 1600}})
  255. }
  256. }), null, null, 'Ctrl+N');
  257. this.actions.get('open').shortcut = 'Ctrl+O';
  258. // Adds shortcut keys for file operations
  259. editorUi.keyHandler.bindAction(78, true, 'new'); // Ctrl+N
  260. editorUi.keyHandler.bindAction(79, true, 'open'); // Ctrl+O
  261. editorUi.actions.addAction('keyboardShortcuts...', function()
  262. {
  263. const electron = require('electron');
  264. const remote = electron.remote;
  265. const BrowserWindow = remote.BrowserWindow;
  266. keyboardWindow = new BrowserWindow({width: 1200, height: 1000});
  267. // and load the index.html of the app.
  268. keyboardWindow.loadURL(`file://${__dirname}/shortcuts.svg`);
  269. // Emitted when the window is closed.
  270. keyboardWindow.on('closed', function()
  271. {
  272. // Dereference the window object, usually you would store windows
  273. // in an array if your app supports multi windows, this is the time
  274. // when you should delete the corresponding element.
  275. keyboardWindow = null;
  276. });
  277. });
  278. }
  279. // Uses local picker
  280. App.prototype.pickFile = function()
  281. {
  282. var doPickFile = mxUtils.bind(this, function()
  283. {
  284. this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data)
  285. {
  286. var file = new LocalFile(this, data, '');
  287. file.fileObject = fileEntry;
  288. this.fileLoaded(file);
  289. }));
  290. });
  291. var file = this.getCurrentFile();
  292. if (file != null && file.isModified())
  293. {
  294. this.confirm(mxResources.get('allChangesLost'), null, doPickFile,
  295. mxResources.get('cancel'), mxResources.get('discardChanges'));
  296. }
  297. else
  298. {
  299. doPickFile();
  300. }
  301. };
  302. /**
  303. * Selects a library to load from a picker
  304. *
  305. * @param mode the device mode, ignored in this case
  306. */
  307. App.prototype.pickLibrary = function(mode)
  308. {
  309. this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data)
  310. {
  311. try
  312. {
  313. var library = new LocalLibrary(this, data, fileEntry.name);
  314. library.fileObject = fileEntry;
  315. this.loadLibrary(library);
  316. }
  317. catch (e)
  318. {
  319. this.handleError(e, mxResources.get('errorLoadingFile'));
  320. }
  321. }));
  322. };
  323. // Uses local picker
  324. App.prototype.chooseFileEntry = function(fn)
  325. {
  326. const electron = require('electron');
  327. var remote = electron.remote;
  328. var dialog = remote.dialog;
  329. var paths = dialog.showOpenDialog({properties: ['openFile']});
  330. if (paths !== undefined && paths[0] != null)
  331. {
  332. var fs = require('fs');
  333. var path = paths[0];
  334. var index = path.lastIndexOf('.png');
  335. var isPng = index > -1 && index == path.length - 4;
  336. var encoding = isPng ? 'base64' : 'utf-8'
  337. fs.readFile(path, encoding, mxUtils.bind(this, function (e, data)
  338. {
  339. if (e)
  340. {
  341. this.handleError(e);
  342. }
  343. else
  344. {
  345. if (isPng)
  346. {
  347. // Detecting png by extension. Would need https://github.com/mscdex/mmmagic
  348. // to do it by inspection
  349. data = this.extractGraphModelFromPng(data, true);
  350. }
  351. var fileEntry = new Object();
  352. fileEntry.path = path;
  353. fileEntry.name = path.replace(/^.*[\\\/]/, '');
  354. fileEntry.type = encoding;
  355. fn(fileEntry, data);
  356. }
  357. }));
  358. }
  359. };
  360. // Disables temp files in Electron
  361. var LocalFileCtor = LocalFile;
  362. LocalFile = function(ui, data, title, temp)
  363. {
  364. LocalFileCtor.call(this, ui, data, title, false);
  365. };
  366. mxUtils.extend(LocalFile, LocalFileCtor);
  367. LocalFile.prototype.isAutosave = function()
  368. {
  369. return this.ui.editor.autosave && this.fileObject != null;
  370. };
  371. LocalFile.prototype.isAutosaveOptional = function()
  372. {
  373. return true;
  374. };
  375. LocalLibrary.prototype.isAutosave = function()
  376. {
  377. return this.fileObject != null;
  378. };
  379. LocalFile.prototype.getTitle = function()
  380. {
  381. return (this.fileObject != null) ? this.fileObject.name : this.title;
  382. };
  383. LocalFile.prototype.isRenamable = function()
  384. {
  385. return false;
  386. };
  387. // Restores default implementation of open with autosave
  388. LocalFile.prototype.open = DrawioFile.prototype.open;
  389. LocalFile.prototype.save = function(revision, success, error)
  390. {
  391. DrawioFile.prototype.save.apply(this, arguments);
  392. this.saveFile(revision, success, error);
  393. };
  394. LocalFile.prototype.saveFile = function(revision, success, error)
  395. {
  396. var fn = mxUtils.bind(this, function()
  397. {
  398. var doSave = mxUtils.bind(this, function(data, enc)
  399. {
  400. if (!this.savingFile)
  401. {
  402. this.savingFile = true;
  403. // Makes sure no changes get lost while the file is saved
  404. var prevModified = this.isModified;
  405. var modified = this.isModified();
  406. this.setModified(false);
  407. var fs = require('fs');
  408. fs.writeFile(this.fileObject.path, data, enc || this.fileObject.encoding, mxUtils.bind(this, function (e)
  409. {
  410. if (e)
  411. {
  412. this.savingFile = false;
  413. this.isModified = prevModified;
  414. this.setModified(modified || this.isModified());
  415. if (error != null)
  416. {
  417. error();
  418. }
  419. }
  420. else
  421. {
  422. this.savingFile = false;
  423. this.isModified = prevModified;
  424. this.contentChanged();
  425. this.lastData = data;
  426. if (success != null)
  427. {
  428. success();
  429. }
  430. }
  431. }));
  432. }
  433. else
  434. {
  435. // TODO, already saving. Need a better error
  436. if (error != null)
  437. {
  438. error();
  439. }
  440. }
  441. });
  442. if (!/(\.png)$/i.test(this.fileObject.name))
  443. {
  444. doSave(this.getData());
  445. }
  446. else
  447. {
  448. var graph = this.ui.editor.graph;
  449. // Exports PNG for first page while other page is visible by creating a graph
  450. // LATER: Add caching for the graph or SVG while not on first page
  451. if (this.ui.pages != null && this.ui.currentPage != this.ui.pages[0])
  452. {
  453. graph = this.ui.createTemporaryGraph(graph.getStylesheet());
  454. var graphGetGlobalVariable = graph.getGlobalVariable;
  455. var page = this.ui.pages[0];
  456. graph.getGlobalVariable = function(name)
  457. {
  458. if (name == 'page')
  459. {
  460. return page.getName();
  461. }
  462. else if (name == 'pagenumber')
  463. {
  464. return 1;
  465. }
  466. return graphGetGlobalVariable.apply(this, arguments);
  467. };
  468. document.body.appendChild(graph.container);
  469. graph.model.setRoot(page.root);
  470. }
  471. this.ui.exportToCanvas(mxUtils.bind(this, function(canvas)
  472. {
  473. try
  474. {
  475. var data = canvas.toDataURL('image/png');
  476. data = this.ui.writeGraphModelToPng(data, 'zTXt', 'mxGraphModel',
  477. atob(this.ui.editor.graph.compress(this.ui.getFileData(true))));
  478. doSave(atob(data.substring(data.lastIndexOf(',') + 1)), 'binary');
  479. // Removes temporary graph from DOM
  480. if (graph != this.ui.editor.graph)
  481. {
  482. graph.container.parentNode.removeChild(graph.container);
  483. }
  484. }
  485. catch (e)
  486. {
  487. if (error != null)
  488. {
  489. error(e);
  490. }
  491. }
  492. }), null, null, null, mxUtils.bind(this, function(e)
  493. {
  494. if (error != null)
  495. {
  496. error(e);
  497. }
  498. }), null, null, null, null, null, null, graph);
  499. }
  500. });
  501. if (this.fileObject == null)
  502. {
  503. const electron = require('electron');
  504. var remote = electron.remote;
  505. var dialog = remote.dialog;
  506. var path = dialog.showSaveDialog({defaultPath: this.title});
  507. if (path != null)
  508. {
  509. this.fileObject = new Object();
  510. this.fileObject.path = path;
  511. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  512. this.fileObject.type = 'utf-8';
  513. fn();
  514. }
  515. else if (error != null)
  516. {
  517. error();
  518. }
  519. }
  520. else
  521. {
  522. fn();
  523. }
  524. };
  525. LocalLibrary.prototype.save = function(revision, success, error)
  526. {
  527. LocalFile.prototype.saveFile.apply(this, arguments);
  528. };
  529. LocalFile.prototype.saveAs = function(title, success, error)
  530. {
  531. const electron = require('electron');
  532. var remote = electron.remote;
  533. var dialog = remote.dialog;
  534. var filename = this.title;
  535. // Adds default extension
  536. if (filename.length > 0 && (!/(\.xml)$/i.test(filename) && !/(\.html)$/i.test(filename) &&
  537. !/(\.svg)$/i.test(filename) && !/(\.png)$/i.test(filename)))
  538. {
  539. filename += '.xml';
  540. }
  541. var path = dialog.showSaveDialog({defaultPath: filename});
  542. if (path != null)
  543. {
  544. this.fileObject = new Object();
  545. this.fileObject.path = path;
  546. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  547. this.fileObject.type = 'utf-8';
  548. this.save(false, success, error);
  549. }
  550. else if (error != null)
  551. {
  552. error();
  553. }
  554. };
  555. App.prototype.saveFile = function(forceDialog)
  556. {
  557. var file = this.getCurrentFile();
  558. if (file != null)
  559. {
  560. if (!forceDialog && file.getTitle() != null)
  561. {
  562. file.save(true, mxUtils.bind(this, function(resp)
  563. {
  564. this.spinner.stop();
  565. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
  566. }), mxUtils.bind(this, function(resp)
  567. {
  568. this.editor.setStatus('');
  569. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  570. }));
  571. }
  572. else
  573. {
  574. file.saveAs(null, mxUtils.bind(this, function(resp)
  575. {
  576. this.spinner.stop();
  577. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
  578. }), mxUtils.bind(this, function(resp)
  579. {
  580. this.editor.setStatus('');
  581. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  582. }));
  583. }
  584. }
  585. };
  586. /**
  587. * Translates this point by the given vector.
  588. *
  589. * @param {number} dx X-coordinate of the translation.
  590. * @param {number} dy Y-coordinate of the translation.
  591. */
  592. App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn)
  593. {
  594. mode = (mode != null) ? mode : this.mode;
  595. noSpin = (noSpin != null) ? noSpin : false;
  596. noReload = (noReload != null) ? noReload : false;
  597. var xml = this.createLibraryDataFromImages(images);
  598. var error = mxUtils.bind(this, function(resp)
  599. {
  600. this.spinner.stop();
  601. if (fn != null)
  602. {
  603. fn();
  604. }
  605. // Null means cancel by user and is ignored
  606. if (resp != null)
  607. {
  608. this.handleError(resp, mxResources.get('errorSavingFile'));
  609. }
  610. });
  611. // Handles special case for local libraries
  612. if (file == null)
  613. {
  614. file = new LocalLibrary(this, xml, name);
  615. }
  616. if (noSpin || this.spinner.spin(document.body, mxResources.get('saving')))
  617. {
  618. file.setData(xml);
  619. var doSave = mxUtils.bind(this, function()
  620. {
  621. file.save(true, mxUtils.bind(this, function(resp)
  622. {
  623. this.spinner.stop();
  624. this.hideDialog(true);
  625. if (!noReload)
  626. {
  627. this.libraryLoaded(file, images)
  628. }
  629. if (fn != null)
  630. {
  631. fn();
  632. }
  633. }), error);
  634. });
  635. if (name != file.getTitle())
  636. {
  637. var oldHash = file.getHash();
  638. file.rename(name, mxUtils.bind(this, function(resp)
  639. {
  640. // Change hash in stored settings
  641. if (file.constructor != LocalLibrary && oldHash != file.getHash())
  642. {
  643. mxSettings.removeCustomLibrary(oldHash);
  644. mxSettings.addCustomLibrary(file.getHash());
  645. }
  646. // Workaround for library files changing hash so
  647. // the old library cannot be removed from the
  648. // sidebar using the updated file in libraryLoaded
  649. this.removeLibrarySidebar(oldHash);
  650. doSave();
  651. }), error)
  652. }
  653. else
  654. {
  655. doSave();
  656. }
  657. }
  658. };
  659. EditorUi.prototype.saveData = function(filename, format, data, mimeType, base64Encoded)
  660. {
  661. const electron = require('electron');
  662. var remote = electron.remote;
  663. var dialog = remote.dialog;
  664. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  665. // Spinner.stop is asynchronous so we must invoke save dialog asynchronously
  666. // to give the spinner some time to stop spinning
  667. window.setTimeout(mxUtils.bind(this, function()
  668. {
  669. var path = dialog.showSaveDialog({defaultPath: filename});
  670. if (path != null)
  671. {
  672. var fs = require('fs');
  673. resume();
  674. var fileObject = new Object();
  675. fileObject.path = path;
  676. fileObject.name = path.replace(/^.*[\\\/]/, '');
  677. fileObject.type = (base64Encoded) ? 'base64' : 'utf-8';
  678. fs.writeFile(fileObject.path, data, fileObject.type, mxUtils.bind(this, function (e)
  679. {
  680. this.spinner.stop();
  681. if (e)
  682. {
  683. this.handleError({message: mxResources.get('errorSavingFile')});
  684. }
  685. }));
  686. }
  687. }), 0);
  688. };
  689. EditorUi.prototype.createImageUrlConverter = function()
  690. {
  691. var converter = new mxUrlConverter();
  692. converter.updateBaseUrl();
  693. return converter;
  694. };
  695. EditorUi.prototype.isCorsEnabledForUrl = function()
  696. {
  697. return true;
  698. }
  699. EditorUi.prototype.addBeforeUnloadListener = function() {};
  700. })();