ElectronApp.js 23 KB

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