ElectronApp.js 22 KB

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