ElectronApp.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822
  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. this.ui.getEmbeddedPng(function(data)
  464. {
  465. doSave(atob(data), 'binary');
  466. }, error);
  467. }
  468. });
  469. if (this.fileObject == null)
  470. {
  471. const electron = require('electron');
  472. var remote = electron.remote;
  473. var dialog = remote.dialog;
  474. var path = dialog.showSaveDialog({defaultPath: this.title});
  475. if (path != null)
  476. {
  477. this.fileObject = new Object();
  478. this.fileObject.path = path;
  479. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  480. this.fileObject.type = 'utf-8';
  481. fn();
  482. }
  483. else if (error != null)
  484. {
  485. error();
  486. }
  487. }
  488. else
  489. {
  490. fn();
  491. }
  492. };
  493. LocalLibrary.prototype.save = function(revision, success, error)
  494. {
  495. LocalFile.prototype.saveFile.apply(this, arguments);
  496. };
  497. LocalFile.prototype.saveAs = function(title, success, error)
  498. {
  499. const electron = require('electron');
  500. var remote = electron.remote;
  501. var dialog = remote.dialog;
  502. var filename = this.title;
  503. // Adds default extension
  504. if (filename.length > 0 && (!/(\.xml)$/i.test(filename) && !/(\.html)$/i.test(filename) &&
  505. !/(\.svg)$/i.test(filename) && !/(\.png)$/i.test(filename)))
  506. {
  507. filename += '.xml';
  508. }
  509. var path = dialog.showSaveDialog({defaultPath: filename});
  510. if (path != null)
  511. {
  512. this.fileObject = new Object();
  513. this.fileObject.path = path;
  514. this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  515. this.fileObject.type = 'utf-8';
  516. this.save(false, success, error);
  517. }
  518. else if (error != null)
  519. {
  520. error();
  521. }
  522. };
  523. App.prototype.saveFile = function(forceDialog)
  524. {
  525. var file = this.getCurrentFile();
  526. if (file != null)
  527. {
  528. if (!forceDialog && file.getTitle() != null)
  529. {
  530. file.save(true, mxUtils.bind(this, function(resp)
  531. {
  532. this.spinner.stop();
  533. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
  534. }), mxUtils.bind(this, function(resp)
  535. {
  536. this.editor.setStatus('');
  537. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  538. }));
  539. }
  540. else
  541. {
  542. file.saveAs(null, mxUtils.bind(this, function(resp)
  543. {
  544. this.spinner.stop();
  545. this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
  546. }), mxUtils.bind(this, function(resp)
  547. {
  548. this.editor.setStatus('');
  549. this.handleError(resp, (resp != null) ? mxResources.get('errorSavingFile') : null);
  550. }));
  551. }
  552. }
  553. };
  554. /**
  555. * Translates this point by the given vector.
  556. *
  557. * @param {number} dx X-coordinate of the translation.
  558. * @param {number} dy Y-coordinate of the translation.
  559. */
  560. App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn)
  561. {
  562. mode = (mode != null) ? mode : this.mode;
  563. noSpin = (noSpin != null) ? noSpin : false;
  564. noReload = (noReload != null) ? noReload : false;
  565. var xml = this.createLibraryDataFromImages(images);
  566. var error = mxUtils.bind(this, function(resp)
  567. {
  568. this.spinner.stop();
  569. if (fn != null)
  570. {
  571. fn();
  572. }
  573. // Null means cancel by user and is ignored
  574. if (resp != null)
  575. {
  576. this.handleError(resp, mxResources.get('errorSavingFile'));
  577. }
  578. });
  579. // Handles special case for local libraries
  580. if (file == null)
  581. {
  582. file = new LocalLibrary(this, xml, name);
  583. }
  584. if (noSpin || this.spinner.spin(document.body, mxResources.get('saving')))
  585. {
  586. file.setData(xml);
  587. var doSave = mxUtils.bind(this, function()
  588. {
  589. file.save(true, mxUtils.bind(this, function(resp)
  590. {
  591. this.spinner.stop();
  592. this.hideDialog(true);
  593. if (!noReload)
  594. {
  595. this.libraryLoaded(file, images)
  596. }
  597. if (fn != null)
  598. {
  599. fn();
  600. }
  601. }), error);
  602. });
  603. if (name != file.getTitle())
  604. {
  605. var oldHash = file.getHash();
  606. file.rename(name, mxUtils.bind(this, function(resp)
  607. {
  608. // Change hash in stored settings
  609. if (file.constructor != LocalLibrary && oldHash != file.getHash())
  610. {
  611. mxSettings.removeCustomLibrary(oldHash);
  612. mxSettings.addCustomLibrary(file.getHash());
  613. }
  614. // Workaround for library files changing hash so
  615. // the old library cannot be removed from the
  616. // sidebar using the updated file in libraryLoaded
  617. this.removeLibrarySidebar(oldHash);
  618. doSave();
  619. }), error)
  620. }
  621. else
  622. {
  623. doSave();
  624. }
  625. }
  626. };
  627. EditorUi.prototype.saveLocalFile = function(data, filename, mimeType, base64Encoded, format, allowBrowser)
  628. {
  629. this.saveData(filename, format, data, mimeType, base64Encoded);
  630. };
  631. EditorUi.prototype.saveRequest = function(filename, format, fn, data, base64Encoded, mimeType)
  632. {
  633. var xhr = fn(null, '1');
  634. if (xhr != null && this.spinner.spin(document.body, mxResources.get('saving')))
  635. {
  636. xhr.send(mxUtils.bind(this, function()
  637. {
  638. this.spinner.stop();
  639. if (xhr.getStatus() >= 200 && xhr.getStatus() <= 299)
  640. {
  641. this.saveData(filename, format, xhr.getText(), mimeType, true);
  642. }
  643. else
  644. {
  645. this.handleError({message: mxResources.get('errorSavingFile')});
  646. }
  647. }), function(resp)
  648. {
  649. this.spinner.stop();
  650. this.handleError(resp);
  651. });
  652. }
  653. };
  654. EditorUi.prototype.saveData = function(filename, format, data, mimeType, base64Encoded)
  655. {
  656. const electron = require('electron');
  657. var remote = electron.remote;
  658. var dialog = remote.dialog;
  659. var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  660. // Spinner.stop is asynchronous so we must invoke save dialog asynchronously
  661. // to give the spinner some time to stop spinning
  662. window.setTimeout(mxUtils.bind(this, function()
  663. {
  664. var path = dialog.showSaveDialog({defaultPath: filename});
  665. if (path != null)
  666. {
  667. var fs = require('fs');
  668. resume();
  669. var fileObject = new Object();
  670. fileObject.path = path;
  671. fileObject.name = path.replace(/^.*[\\\/]/, '');
  672. fileObject.type = (base64Encoded) ? 'base64' : 'utf-8';
  673. fs.writeFile(fileObject.path, data, fileObject.type, mxUtils.bind(this, function (e)
  674. {
  675. this.spinner.stop();
  676. if (e)
  677. {
  678. this.handleError({message: mxResources.get('errorSavingFile')});
  679. }
  680. }));
  681. }
  682. }), 0);
  683. };
  684. EditorUi.prototype.createImageUrlConverter = function()
  685. {
  686. var converter = new mxUrlConverter();
  687. converter.updateBaseUrl();
  688. return converter;
  689. };
  690. // Adds file: protocol as absolute URL for images
  691. var mxUrlConverterIsRelativeUrl = mxUrlConverter.prototype.isRelativeUrl;
  692. mxUrlConverter.prototype.isRelativeUrl = function(url)
  693. {
  694. return url.substring(0, 7) !== 'file://' && mxUrlConverterIsRelativeUrl.apply(this, arguments);
  695. };
  696. // Disables proxy for all images
  697. EditorUi.prototype.isCorsEnabledForUrl = function()
  698. {
  699. return true;
  700. }
  701. EditorUi.prototype.addBeforeUnloadListener = function() {};
  702. })();