electron.js 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309
  1. const fs = require('fs')
  2. const fsProm = require('fs/promises');
  3. const os = require('os');
  4. const path = require('path')
  5. const url = require('url')
  6. const {Menu: menu, shell, dialog,
  7. clipboard, nativeImage, ipcMain, app, BrowserWindow} = require('electron')
  8. const crc = require('crc');
  9. const zlib = require('zlib');
  10. const log = require('electron-log')
  11. const program = require('commander')
  12. const {autoUpdater} = require("electron-updater")
  13. const PDFDocument = require('pdf-lib').PDFDocument;
  14. const Store = require('electron-store');
  15. const store = new Store();
  16. const ProgressBar = require('electron-progressbar');
  17. const spawn = require('child_process').spawn;
  18. const disableUpdate = require('./disableUpdate').disableUpdate() ||
  19. process.env.DRAWIO_DISABLE_UPDATE === 'true' ||
  20. fs.existsSync('/.flatpak-info'); //This file indicates running in flatpak sandbox
  21. autoUpdater.logger = log
  22. autoUpdater.logger.transports.file.level = 'info'
  23. autoUpdater.autoDownload = false
  24. //Command option to disable hardware acceleration
  25. if (process.argv.indexOf('--disable-acceleration') !== -1)
  26. {
  27. app.disableHardwareAcceleration();
  28. }
  29. const __DEV__ = process.env.DRAWIO_ENV === 'dev'
  30. let windowsRegistry = []
  31. let cmdQPressed = false
  32. let firstWinLoaded = false
  33. let firstWinFilePath = null
  34. const isMac = process.platform === 'darwin'
  35. const isWin = process.platform === 'win32'
  36. let enableSpellCheck = store.get('enableSpellCheck');
  37. enableSpellCheck = enableSpellCheck != null? enableSpellCheck : isMac;
  38. let enableStoreBkp = store.get('enableStoreBkp') != null? store.get('enableStoreBkp') : true;
  39. let dialogOpen = false;
  40. //Read config file
  41. var queryObj = {
  42. 'dev': __DEV__ ? 1 : 0,
  43. 'test': __DEV__ ? 1 : 0,
  44. 'gapi': 0,
  45. 'db': 0,
  46. 'od': 0,
  47. 'gh': 0,
  48. 'gl': 0,
  49. 'tr': 0,
  50. 'browser': 0,
  51. 'picker': 0,
  52. 'mode': 'device',
  53. 'export': 'https://convert.diagrams.net/node/export',
  54. 'disableUpdate': disableUpdate? 1 : 0,
  55. 'winCtrls': isMac? 0 : 1,
  56. 'enableSpellCheck': enableSpellCheck? 1 : 0,
  57. 'enableStoreBkp': enableStoreBkp? 1 : 0
  58. };
  59. try
  60. {
  61. if (fs.existsSync(process.cwd() + '/urlParams.json'))
  62. {
  63. let urlParams = JSON.parse(fs.readFileSync(process.cwd() + '/urlParams.json'));
  64. for (var param in urlParams)
  65. {
  66. queryObj[param] = urlParams[param];
  67. }
  68. }
  69. }
  70. catch(e)
  71. {
  72. console.log('Error in urlParams.json file: ' + e.message);
  73. }
  74. // Trying sandboxing the renderer for more protection
  75. //app.enableSandbox(); // This maybe the reason snap stopped working
  76. function createWindow (opt = {})
  77. {
  78. let lastWinSizeStr = store.get('lastWinSize');
  79. let lastWinSize = lastWinSizeStr ? lastWinSizeStr.split(',') : [1600, 1200];
  80. // TODO On some Mac OS, double click the titlebar set incorrect window size
  81. if (lastWinSize[0] < 500)
  82. {
  83. lastWinSize[0] = 500;
  84. }
  85. if (lastWinSize[1] < 500)
  86. {
  87. lastWinSize[1] = 500;
  88. }
  89. let options = Object.assign(
  90. {
  91. frame: isMac,
  92. backgroundColor: '#FFF',
  93. width: parseInt(lastWinSize[0]),
  94. height: parseInt(lastWinSize[1]),
  95. icon: `${__dirname}/images/drawlogo256.png`,
  96. webViewTag: false,
  97. 'web-security': true,
  98. webPreferences: {
  99. preload: `${__dirname}/electron-preload.js`,
  100. spellcheck: enableSpellCheck,
  101. contextIsolation: true,
  102. disableBlinkFeatures: 'Auxclick' // Is this needed?
  103. }
  104. }, opt)
  105. let mainWindow = new BrowserWindow(options)
  106. windowsRegistry.push(mainWindow)
  107. if (__DEV__)
  108. {
  109. console.log('createWindow', opt)
  110. }
  111. //Cannot be read before app is ready
  112. queryObj['appLang'] = app.getLocale();
  113. let ourl = url.format(
  114. {
  115. pathname: `${__dirname}/index.html`,
  116. protocol: 'file:',
  117. query: queryObj,
  118. slashes: true
  119. })
  120. mainWindow.loadURL(ourl)
  121. // Open the DevTools.
  122. if (__DEV__)
  123. {
  124. mainWindow.webContents.openDevTools()
  125. }
  126. ipcMain.on('openDevTools', function()
  127. {
  128. mainWindow.webContents.openDevTools();
  129. });
  130. mainWindow.on('maximize', function()
  131. {
  132. mainWindow.webContents.send('maximize')
  133. });
  134. mainWindow.on('unmaximize', function()
  135. {
  136. mainWindow.webContents.send('unmaximize')
  137. });
  138. mainWindow.on('resize', function()
  139. {
  140. const size = mainWindow.getSize();
  141. store.set('lastWinSize', size[0] + ',' + size[1]);
  142. mainWindow.webContents.send('resize')
  143. });
  144. mainWindow.on('close', (event) =>
  145. {
  146. const win = event.sender
  147. const index = windowsRegistry.indexOf(win)
  148. if (__DEV__)
  149. {
  150. console.log('Window on close', index)
  151. }
  152. const contents = win.webContents
  153. if (contents != null)
  154. {
  155. ipcMain.once('isModified-result', (evt, data) =>
  156. {
  157. if (data.isModified)
  158. {
  159. dialog.showMessageBox(
  160. win,
  161. {
  162. type: 'question',
  163. buttons: ['Cancel', 'Discard Changes'],
  164. title: 'Confirm',
  165. message: 'The document has unsaved changes. Do you really want to quit without saving?' //mxResources.get('allChangesLost')
  166. }).then( async result =>
  167. {
  168. if (result.response === 1)
  169. {
  170. //If user chose not to save, remove the draft
  171. if (data.draftPath != null)
  172. {
  173. await deleteFile(data.draftPath);
  174. }
  175. win.destroy();
  176. }
  177. else
  178. {
  179. cmdQPressed = false;
  180. }
  181. });
  182. }
  183. else
  184. {
  185. win.destroy();
  186. }
  187. });
  188. contents.send('isModified');
  189. event.preventDefault();
  190. }
  191. })
  192. // Emitted when the window is closed.
  193. mainWindow.on('closed', (event/*:WindowEvent*/) =>
  194. {
  195. const index = windowsRegistry.indexOf(event.sender)
  196. if (__DEV__)
  197. {
  198. console.log('Window closed idx:%d', index)
  199. }
  200. windowsRegistry.splice(index, 1)
  201. })
  202. return mainWindow
  203. }
  204. // This method will be called when Electron has finished
  205. // initialization and is ready to create browser windows.
  206. // Some APIs can only be used after this event occurs.
  207. app.on('ready', e =>
  208. {
  209. ipcMain.on('newfile', (event, arg) =>
  210. {
  211. createWindow(arg)
  212. })
  213. let argv = process.argv
  214. // https://github.com/electron/electron/issues/4690#issuecomment-217435222
  215. if (process.defaultApp != true)
  216. {
  217. argv.unshift(null)
  218. }
  219. var validFormatRegExp = /^(pdf|svg|png|jpeg|jpg|vsdx|xml)$/;
  220. function argsRange(val)
  221. {
  222. return val.split('..').map(Number);
  223. }
  224. try
  225. {
  226. program
  227. .version(app.getVersion())
  228. .usage('[options] [input file/folder]')
  229. .allowUnknownOption() //-h and --help are considered unknown!!
  230. .option('-c, --create', 'creates a new empty file if no file is passed')
  231. .option('-k, --check', 'does not overwrite existing files')
  232. .option('-x, --export', 'export the input file/folder based on the given options')
  233. .option('-r, --recursive', 'for a folder input, recursively convert all files in sub-folders also')
  234. .option('-o, --output <output file/folder>', 'specify the output file/folder. If omitted, the input file name is used for output with the specified format as extension')
  235. .option('-f, --format <format>',
  236. 'if output file name extension is specified, this option is ignored (file type is determined from output extension, possible export formats are pdf, png, jpg, svg, vsdx, and xml)',
  237. validFormatRegExp, 'pdf')
  238. .option('-q, --quality <quality>',
  239. 'output image quality for JPEG (default: 90)', parseInt)
  240. .option('-t, --transparent',
  241. 'set transparent background for PNG')
  242. .option('-e, --embed-diagram',
  243. 'includes a copy of the diagram (for PNG, SVG and PDF formats only)')
  244. .option('--embed-svg-images',
  245. 'Embed Images in SVG file (for SVG format only)')
  246. .option('-b, --border <border>',
  247. 'sets the border width around the diagram (default: 0)', parseInt)
  248. .option('-s, --scale <scale>',
  249. 'scales the diagram size', parseFloat)
  250. .option('--width <width>',
  251. 'fits the generated image/pdf into the specified width, preserves aspect ratio.', parseInt)
  252. .option('--height <height>',
  253. 'fits the generated image/pdf into the specified height, preserves aspect ratio.', parseInt)
  254. .option('--crop',
  255. 'crops PDF to diagram size')
  256. .option('-a, --all-pages',
  257. 'export all pages (for PDF format only)')
  258. .option('-p, --page-index <pageIndex>',
  259. 'selects a specific page, if not specified and the format is an image, the first page is selected', parseInt)
  260. .option('-g, --page-range <from>..<to>',
  261. 'selects a page range (for PDF format only)', argsRange)
  262. .option('-u, --uncompressed',
  263. 'Uncompressed XML output (for XML format only)')
  264. .parse(argv)
  265. }
  266. catch(e)
  267. {
  268. //On parse error, return [exit and commander will show the error message]
  269. return;
  270. }
  271. var options = program.opts();
  272. //Start export mode?
  273. if (options.export)
  274. {
  275. var dummyWin = new BrowserWindow({
  276. show : false,
  277. webPreferences: {
  278. preload: `${__dirname}/electron-preload.js`,
  279. contextIsolation: true,
  280. disableBlinkFeatures: 'Auxclick' // Is this needed?
  281. }
  282. });
  283. windowsRegistry.push(dummyWin);
  284. try
  285. {
  286. //Prepare arguments and confirm it's valid
  287. var format = null;
  288. var outType = null;
  289. //Format & Output
  290. if (options.output)
  291. {
  292. try
  293. {
  294. var outStat = fs.statSync(options.output);
  295. if (outStat.isDirectory())
  296. {
  297. outType = {isDir: true};
  298. }
  299. else //If we can get file stat, then it exists
  300. {
  301. throw 'Error: Output file already exists';
  302. }
  303. }
  304. catch(e) //on error, file doesn't exist and it is not a dir
  305. {
  306. outType = {isFile: true};
  307. format = path.extname(options.output).substr(1);
  308. if (!validFormatRegExp.test(format))
  309. {
  310. format = null;
  311. }
  312. }
  313. }
  314. if (format == null)
  315. {
  316. format = options.format;
  317. }
  318. var from = null, to = null;
  319. if (options.pageIndex != null && options.pageIndex >= 0)
  320. {
  321. from = options.pageIndex;
  322. }
  323. else if (options.pageRange && options.pageRange.length == 2)
  324. {
  325. from = options.pageRange[0] >= 0 ? options.pageRange[0] : null;
  326. to = options.pageRange[1] >= 0 ? options.pageRange[1] : null;
  327. }
  328. var expArgs = {
  329. format: format,
  330. w: options.width > 0 ? options.width : null,
  331. h: options.height > 0 ? options.height : null,
  332. border: options.border > 0 ? options.border : 0,
  333. bg: options.transparent ? 'none' : '#ffffff',
  334. from: from,
  335. to: to,
  336. allPages: format == 'pdf' && options.allPages,
  337. scale: (options.crop && (options.scale == null || options.scale == 1)) ? 1.00001: (options.scale || 1), //any value other than 1 crops the pdf
  338. embedXml: options.embedDiagram? '1' : '0',
  339. embedImages: options.embedSvgImages? '1' : '0',
  340. jpegQuality: options.quality,
  341. uncompressed: options.uncompressed
  342. };
  343. var paths = program.args;
  344. // If a file is passed
  345. if (paths !== undefined && paths[0] != null)
  346. {
  347. var inStat = null;
  348. try
  349. {
  350. inStat = fs.statSync(paths[0]);
  351. }
  352. catch(e)
  353. {
  354. throw 'Error: input file/directory not found';
  355. }
  356. var files = [];
  357. function addDirectoryFiles(dir, isRecursive)
  358. {
  359. fs.readdirSync(dir).forEach(function(file)
  360. {
  361. var filePath = path.join(dir, file);
  362. stat = fs.statSync(filePath);
  363. if (stat.isFile() && path.basename(filePath).charAt(0) != '.')
  364. {
  365. files.push(filePath);
  366. }
  367. if (stat.isDirectory() && isRecursive)
  368. {
  369. addDirectoryFiles(filePath, isRecursive)
  370. }
  371. });
  372. }
  373. if (inStat.isFile())
  374. {
  375. files.push(paths[0]);
  376. }
  377. else if (inStat.isDirectory())
  378. {
  379. addDirectoryFiles(paths[0], options.recursive);
  380. }
  381. if (files.length > 0)
  382. {
  383. var fileIndex = 0;
  384. function processOneFile()
  385. {
  386. var curFile = files[fileIndex];
  387. try
  388. {
  389. var ext = path.extname(curFile);
  390. expArgs.xml = fs.readFileSync(curFile, ext === '.png' || ext === '.vsdx' ? null : 'utf-8');
  391. if (ext === '.png')
  392. {
  393. expArgs.xml = Buffer.from(expArgs.xml).toString('base64');
  394. startExport();
  395. }
  396. else if (ext === '.vsdx')
  397. {
  398. dummyWin.loadURL(`file://${__dirname}/vsdxImporter.html`);
  399. const contents = dummyWin.webContents;
  400. contents.on('did-finish-load', function()
  401. {
  402. contents.send('import', expArgs.xml);
  403. ipcMain.once('import-success', function(evt, xml)
  404. {
  405. expArgs.xml = xml;
  406. startExport();
  407. });
  408. ipcMain.once('import-error', function()
  409. {
  410. console.error('Error: cannot import VSDX file: ' + curFile);
  411. next();
  412. });
  413. });
  414. }
  415. else
  416. {
  417. startExport();
  418. }
  419. function next()
  420. {
  421. fileIndex++;
  422. if (fileIndex < files.length)
  423. {
  424. processOneFile();
  425. }
  426. else
  427. {
  428. cmdQPressed = true;
  429. dummyWin.destroy();
  430. }
  431. };
  432. function startExport()
  433. {
  434. var mockEvent = {
  435. reply: function(msg, data)
  436. {
  437. try
  438. {
  439. if (data == null || data.length == 0)
  440. {
  441. console.error('Error: Export failed: ' + curFile);
  442. }
  443. else if (msg == 'export-success')
  444. {
  445. var outFileName = null;
  446. if (outType != null)
  447. {
  448. if (outType.isDir)
  449. {
  450. outFileName = path.join(options.output, path.basename(curFile,
  451. path.extname(curFile))) + '.' + format;
  452. }
  453. else
  454. {
  455. outFileName = options.output;
  456. }
  457. }
  458. else if (inStat.isFile())
  459. {
  460. outFileName = path.join(path.dirname(paths[0]), path.basename(paths[0],
  461. path.extname(paths[0]))) + '.' + format;
  462. }
  463. else //dir
  464. {
  465. outFileName = path.join(path.dirname(curFile), path.basename(curFile,
  466. path.extname(curFile))) + '.' + format;
  467. }
  468. try
  469. {
  470. var counter = 0;
  471. var realFileName = outFileName;
  472. if (program.rawArgs.indexOf('-k') > -1 || program.rawArgs.indexOf('--check') > -1)
  473. {
  474. while (fs.existsSync(realFileName))
  475. {
  476. counter++;
  477. realFileName = path.join(path.dirname(outFileName), path.basename(outFileName,
  478. path.extname(outFileName))) + '-' + counter + path.extname(outFileName);
  479. }
  480. }
  481. fs.writeFileSync(realFileName, data, format == 'vsdx'? 'base64' : null, { flag: 'wx' });
  482. console.log(curFile + ' -> ' + realFileName);
  483. }
  484. catch(e)
  485. {
  486. console.error('Error writing to file: ' + outFileName);
  487. }
  488. }
  489. else
  490. {
  491. console.error('Error: ' + data + ': ' + curFile);
  492. }
  493. next();
  494. }
  495. finally
  496. {
  497. mockEvent.finalize();
  498. }
  499. }
  500. };
  501. exportDiagram(mockEvent, expArgs, true);
  502. };
  503. }
  504. catch(e)
  505. {
  506. console.error('Error reading file: ' + curFile);
  507. next();
  508. }
  509. }
  510. processOneFile();
  511. }
  512. else
  513. {
  514. throw 'Error: input file/directory not found or directory is empty';
  515. }
  516. }
  517. else
  518. {
  519. throw 'Error: An input file must be specified';
  520. }
  521. }
  522. catch(e)
  523. {
  524. console.error(e);
  525. cmdQPressed = true;
  526. dummyWin.destroy();
  527. }
  528. return;
  529. }
  530. else if (program.rawArgs.indexOf('-h') > -1 || program.rawArgs.indexOf('--help') > -1 || program.rawArgs.indexOf('-V') > -1 || program.rawArgs.indexOf('--version') > -1) //To prevent execution when help/version arg is used
  531. {
  532. app.quit();
  533. return;
  534. }
  535. //Prevent multiple instances of the application (casuses issues with configuration)
  536. const gotTheLock = app.requestSingleInstanceLock()
  537. if (!gotTheLock)
  538. {
  539. app.quit()
  540. }
  541. else
  542. {
  543. app.on('second-instance', (event, commandLine, workingDirectory) => {
  544. // Creating a new window while a save/open dialog is open crashes the app
  545. if (dialogOpen) return;
  546. //Create another window
  547. let win = createWindow()
  548. let loadEvtCount = 0;
  549. function loadFinished()
  550. {
  551. loadEvtCount++;
  552. if (loadEvtCount == 2)
  553. {
  554. //Open the file if new app request is from opening a file
  555. var potFile = commandLine.pop();
  556. if (fs.existsSync(potFile))
  557. {
  558. win.webContents.send('args-obj', {args: [potFile]});
  559. }
  560. }
  561. }
  562. //Order of these two events is not guaranteed, so wait for them async.
  563. //TOOD There is still a chance we catch another window 'app-load-finished' if user created multiple windows quickly
  564. ipcMain.once('app-load-finished', loadFinished);
  565. win.webContents.on('did-finish-load', function()
  566. {
  567. win.webContents.zoomFactor = 1;
  568. win.webContents.setVisualZoomLevelLimits(1, 1);
  569. loadFinished();
  570. });
  571. })
  572. }
  573. let win = createWindow()
  574. let loadEvtCount = 0;
  575. function loadFinished()
  576. {
  577. loadEvtCount++;
  578. if (loadEvtCount == 2)
  579. {
  580. //Sending entire program is not allowed in Electron 9 as it is not native JS object
  581. win.webContents.send('args-obj', {args: program.args, create: options.create});
  582. }
  583. }
  584. //Order of these two events is not guaranteed, so wait for them async.
  585. //TOOD There is still a chance we catch another window 'app-load-finished' if user created multiple windows quickly
  586. ipcMain.once('app-load-finished', loadFinished);
  587. win.webContents.on('did-finish-load', function()
  588. {
  589. if (firstWinFilePath != null)
  590. {
  591. if (program.args != null)
  592. {
  593. program.args.push(firstWinFilePath);
  594. }
  595. else
  596. {
  597. program.args = [firstWinFilePath];
  598. }
  599. }
  600. firstWinLoaded = true;
  601. win.webContents.zoomFactor = 1;
  602. win.webContents.setVisualZoomLevelLimits(1, 1);
  603. loadFinished();
  604. });
  605. function toggleSpellCheck()
  606. {
  607. enableSpellCheck = !enableSpellCheck;
  608. store.set('enableSpellCheck', enableSpellCheck);
  609. };
  610. ipcMain.on('toggleSpellCheck', toggleSpellCheck);
  611. function toggleStoreBkp()
  612. {
  613. enableStoreBkp = !enableStoreBkp;
  614. store.set('enableStoreBkp', enableStoreBkp);
  615. };
  616. ipcMain.on('toggleStoreBkp', toggleStoreBkp);
  617. let updateNoAvailAdded = false;
  618. function checkForUpdatesFn()
  619. {
  620. autoUpdater.checkForUpdates();
  621. store.set('dontCheckUpdates', false);
  622. if (!updateNoAvailAdded)
  623. {
  624. updateNoAvailAdded = true;
  625. autoUpdater.on('update-not-available', (info) => {
  626. dialog.showMessageBox(
  627. {
  628. type: 'info',
  629. title: 'No updates found',
  630. message: 'You application is up-to-date',
  631. })
  632. })
  633. }
  634. };
  635. let checkForUpdates = {
  636. label: 'Check for updates',
  637. click: checkForUpdatesFn
  638. }
  639. ipcMain.on('checkForUpdates', checkForUpdatesFn);
  640. if (isMac)
  641. {
  642. let template = [{
  643. label: app.name,
  644. submenu: [
  645. {
  646. label: 'About ' + app.name,
  647. click() { shell.openExternal('https://www.diagrams.net'); }
  648. },
  649. {
  650. label: 'Support',
  651. click() { shell.openExternal('https://github.com/jgraph/drawio-desktop/issues'); }
  652. },
  653. checkForUpdates,
  654. { type: 'separator' },
  655. { role: 'hide' },
  656. { role: 'hideothers' },
  657. { role: 'unhide' },
  658. { type: 'separator' },
  659. { role: 'quit' }
  660. ]
  661. }, {
  662. label: 'Edit',
  663. submenu: [
  664. { role: 'undo' },
  665. { role: 'redo' },
  666. { type: 'separator' },
  667. { role: 'cut' },
  668. { role: 'copy' },
  669. { role: 'paste' },
  670. { role: 'pasteAndMatchStyle' },
  671. { role: 'selectAll' }
  672. ]
  673. }]
  674. if (disableUpdate)
  675. {
  676. template[0].submenu.splice(2, 1);
  677. }
  678. const menuBar = menu.buildFromTemplate(template)
  679. menu.setApplicationMenu(menuBar)
  680. }
  681. else //hide menubar in win/linux
  682. {
  683. menu.setApplicationMenu(null)
  684. }
  685. autoUpdater.setFeedURL({
  686. provider: 'github',
  687. repo: 'drawio-desktop',
  688. owner: 'jgraph'
  689. })
  690. if (!disableUpdate && !store.get('dontCheckUpdates'))
  691. {
  692. autoUpdater.checkForUpdates()
  693. }
  694. })
  695. //Quit from the dock context menu should quit the application directly
  696. if (isMac)
  697. {
  698. app.on('before-quit', function() {
  699. cmdQPressed = true;
  700. });
  701. }
  702. // Quit when all windows are closed.
  703. app.on('window-all-closed', function ()
  704. {
  705. if (__DEV__)
  706. {
  707. console.log('window-all-closed', windowsRegistry.length)
  708. }
  709. // On OS X it is common for applications and their menu bar
  710. // to stay active until the user quits explicitly with Cmd + Q
  711. if (cmdQPressed || !isMac)
  712. {
  713. app.quit()
  714. }
  715. })
  716. app.on('activate', function ()
  717. {
  718. if (__DEV__)
  719. {
  720. console.log('app on activate', windowsRegistry.length)
  721. }
  722. // On OS X it's common to re-create a window in the app when the
  723. // dock icon is clicked and there are no other windows open.
  724. if (windowsRegistry.length === 0)
  725. {
  726. createWindow()
  727. }
  728. })
  729. app.on('will-finish-launching', function()
  730. {
  731. app.on("open-file", function(event, path)
  732. {
  733. event.preventDefault();
  734. // Creating a new window while a save/open dialog is open crashes the app
  735. if (dialogOpen) return;
  736. if (firstWinLoaded)
  737. {
  738. let win = createWindow();
  739. let loadEvtCount = 0;
  740. function loadFinished()
  741. {
  742. loadEvtCount++;
  743. if (loadEvtCount == 2)
  744. {
  745. win.webContents.send('args-obj', {args: [path]});
  746. }
  747. }
  748. //Order of these two events is not guaranteed, so wait for them async.
  749. //TOOD There is still a chance we catch another window 'app-load-finished' if user created multiple windows quickly
  750. ipcMain.once('app-load-finished', loadFinished);
  751. win.webContents.on('did-finish-load', function()
  752. {
  753. win.webContents.zoomFactor = 1;
  754. win.webContents.setVisualZoomLevelLimits(1, 1);
  755. loadFinished();
  756. });
  757. }
  758. else
  759. {
  760. firstWinFilePath = path
  761. }
  762. });
  763. });
  764. app.on('web-contents-created', (event, contents) => {
  765. // Disable navigation
  766. contents.on('will-navigate', (event, navigationUrl) => {
  767. event.preventDefault()
  768. })
  769. // Limit creation of new windows (we also override window.open)
  770. contents.setWindowOpenHandler(({ url }) => {
  771. // We allow external absolute URLs to be open externally (check openExternal for details) and also empty windows (url -> about:blank)
  772. if (url.startsWith('about:blank'))
  773. {
  774. return {
  775. action: 'allow',
  776. overrideBrowserWindowOptions: {
  777. fullscreenable: false,
  778. webPreferences: {
  779. contextIsolation: true
  780. }
  781. }
  782. }
  783. }
  784. else if (!openExternal(url))
  785. {
  786. return {action: 'deny'}
  787. }
  788. })
  789. // Disable all webviews
  790. contents.on('will-attach-webview', (event, webPreferences, params) => {
  791. event.preventDefault()
  792. })
  793. })
  794. autoUpdater.on('error', e => log.error('@error@\n', e))
  795. autoUpdater.on('update-available', (a, b) =>
  796. {
  797. log.info('@update-available@\n', a, b)
  798. dialog.showMessageBox(
  799. {
  800. type: 'question',
  801. buttons: ['Ok', 'Cancel', 'Don\'t Ask Again'],
  802. title: 'Confirm Update',
  803. message: 'Update available.\n\nWould you like to download and install new version?',
  804. detail: 'Application will automatically restart to apply update after download',
  805. }).then( result =>
  806. {
  807. if (result.response === 0)
  808. {
  809. autoUpdater.downloadUpdate()
  810. var progressBar = new ProgressBar({
  811. title: 'draw.io Update',
  812. text: 'Downloading draw.io update...'
  813. });
  814. function reportUpdateError(e)
  815. {
  816. progressBar.detail = 'Error occurred while fetching updates. ' + (e && e.message? e.message : e)
  817. progressBar._window.setClosable(true);
  818. }
  819. autoUpdater.on('error', e => {
  820. if (progressBar._window != null)
  821. {
  822. reportUpdateError(e);
  823. }
  824. else
  825. {
  826. progressBar.on('ready', function() {
  827. reportUpdateError(e);
  828. });
  829. }
  830. })
  831. var firstTimeProg = true;
  832. autoUpdater.on('download-progress', (d) => {
  833. //On mac, download-progress event is not called, so the indeterminate progress will continue until download is finished
  834. log.info('@update-progress@\n', d);
  835. var percent = d.percent;
  836. if (percent)
  837. {
  838. percent = Math.round(percent * 100)/100;
  839. }
  840. if (firstTimeProg)
  841. {
  842. firstTimeProg = false;
  843. progressBar.close();
  844. progressBar = new ProgressBar({
  845. indeterminate: false,
  846. title: 'draw.io Update',
  847. text: 'Downloading draw.io update...',
  848. detail: `${percent}% ...`,
  849. initialValue: percent
  850. });
  851. progressBar
  852. .on('completed', function() {
  853. progressBar.detail = 'Download completed.';
  854. })
  855. .on('aborted', function(value) {
  856. log.info(`progress aborted... ${value}`);
  857. })
  858. .on('progress', function(value) {
  859. progressBar.detail = `${value}% ...`;
  860. })
  861. .on('ready', function() {
  862. //InitialValue doesn't set the UI! so this is needed to render it correctly
  863. progressBar.value = percent;
  864. });
  865. }
  866. else
  867. {
  868. progressBar.value = percent;
  869. }
  870. });
  871. autoUpdater.on('update-downloaded', (info) => {
  872. if (!progressBar.isCompleted())
  873. {
  874. progressBar.close()
  875. }
  876. log.info('@update-downloaded@\n', info)
  877. // Ask user to update the app
  878. dialog.showMessageBox(
  879. {
  880. type: 'question',
  881. buttons: ['Install', 'Later'],
  882. defaultId: 0,
  883. message: 'A new version of ' + app.name + ' has been downloaded',
  884. detail: 'It will be installed the next time you restart the application',
  885. }).then(result =>
  886. {
  887. if (result.response === 0)
  888. {
  889. setTimeout(() => autoUpdater.quitAndInstall(), 1)
  890. }
  891. })
  892. });
  893. }
  894. else if (result.response === 2)
  895. {
  896. //save in settings don't check for updates
  897. log.info('@dont check for updates!@')
  898. store.set('dontCheckUpdates', true)
  899. }
  900. })
  901. })
  902. //Pdf export
  903. const MICRON_TO_PIXEL = 264.58 //264.58 micron = 1 pixel
  904. const PNG_CHUNK_IDAT = 1229209940;
  905. const LARGE_IMAGE_AREA = 30000000;
  906. //NOTE: Key length must not be longer than 79 bytes (not checked)
  907. function writePngWithText(origBuff, key, text, compressed, base64encoded)
  908. {
  909. var isDpi = key == 'dpi';
  910. var inOffset = 0;
  911. var outOffset = 0;
  912. var data = text;
  913. var dataLen = isDpi? 9 : key.length + data.length + 1; //we add 1 zeros with non-compressed data, for pHYs it's 2 of 4-byte-int + 1 byte
  914. //prepare compressed data to get its size
  915. if (compressed)
  916. {
  917. data = zlib.deflateRawSync(encodeURIComponent(text));
  918. dataLen = key.length + data.length + 2; //we add 2 zeros with compressed data
  919. }
  920. var outBuff = Buffer.allocUnsafe(origBuff.length + dataLen + 4); //4 is the header size "zTXt", "tEXt" or "pHYs"
  921. try
  922. {
  923. var magic1 = origBuff.readUInt32BE(inOffset);
  924. inOffset += 4;
  925. var magic2 = origBuff.readUInt32BE(inOffset);
  926. inOffset += 4;
  927. if (magic1 != 0x89504e47 && magic2 != 0x0d0a1a0a)
  928. {
  929. throw new Error("PNGImageDecoder0");
  930. }
  931. outBuff.writeUInt32BE(magic1, outOffset);
  932. outOffset += 4;
  933. outBuff.writeUInt32BE(magic2, outOffset);
  934. outOffset += 4;
  935. }
  936. catch (e)
  937. {
  938. log.error(e.message, {stack: e.stack});
  939. throw new Error("PNGImageDecoder1");
  940. }
  941. try
  942. {
  943. while (inOffset < origBuff.length)
  944. {
  945. var length = origBuff.readInt32BE(inOffset);
  946. inOffset += 4;
  947. var type = origBuff.readInt32BE(inOffset)
  948. inOffset += 4;
  949. if (type == PNG_CHUNK_IDAT)
  950. {
  951. // Insert zTXt chunk before IDAT chunk
  952. outBuff.writeInt32BE(dataLen, outOffset);
  953. outOffset += 4;
  954. var typeSignature = isDpi? 'pHYs' : (compressed ? "zTXt" : "tEXt");
  955. outBuff.write(typeSignature, outOffset);
  956. outOffset += 4;
  957. if (isDpi)
  958. {
  959. var dpm = Math.round(parseInt(text) / 0.0254) || 3937; //One inch is equal to exactly 0.0254 meters. 3937 is 100dpi
  960. outBuff.writeInt32BE(dpm, outOffset);
  961. outBuff.writeInt32BE(dpm, outOffset + 4);
  962. outBuff.writeInt8(1, outOffset + 8);
  963. outOffset += 9;
  964. data = Buffer.allocUnsafe(9);
  965. data.writeInt32BE(dpm, 0);
  966. data.writeInt32BE(dpm, 4);
  967. data.writeInt8(1, 8);
  968. }
  969. else
  970. {
  971. outBuff.write(key, outOffset);
  972. outOffset += key.length;
  973. outBuff.writeInt8(0, outOffset);
  974. outOffset ++;
  975. if (compressed)
  976. {
  977. outBuff.writeInt8(0, outOffset);
  978. outOffset ++;
  979. data.copy(outBuff, outOffset);
  980. }
  981. else
  982. {
  983. outBuff.write(data, outOffset);
  984. }
  985. outOffset += data.length;
  986. }
  987. var crcVal = 0xffffffff;
  988. crcVal = crc.crcjam(typeSignature, crcVal);
  989. crcVal = crc.crcjam(data, crcVal);
  990. // CRC
  991. outBuff.writeInt32BE(crcVal ^ 0xffffffff, outOffset);
  992. outOffset += 4;
  993. // Writes the IDAT chunk after the zTXt
  994. outBuff.writeInt32BE(length, outOffset);
  995. outOffset += 4;
  996. outBuff.writeInt32BE(type, outOffset);
  997. outOffset += 4;
  998. origBuff.copy(outBuff, outOffset, inOffset);
  999. // Encodes the buffer using base64 if requested
  1000. return base64encoded? outBuff.toString('base64') : outBuff;
  1001. }
  1002. outBuff.writeInt32BE(length, outOffset);
  1003. outOffset += 4;
  1004. outBuff.writeInt32BE(type, outOffset);
  1005. outOffset += 4;
  1006. origBuff.copy(outBuff, outOffset, inOffset, inOffset + length + 4);// +4 to move past the crc
  1007. inOffset += length + 4;
  1008. outOffset += length + 4;
  1009. }
  1010. }
  1011. catch (e)
  1012. {
  1013. log.error(e.message, {stack: e.stack});
  1014. throw e;
  1015. }
  1016. }
  1017. //TODO Create a lightweight html file similar to export3.html for exporting to vsdx
  1018. function exportVsdx(event, args, directFinalize)
  1019. {
  1020. let win = createWindow({
  1021. show : false
  1022. });
  1023. let loadEvtCount = 0;
  1024. function loadFinished()
  1025. {
  1026. loadEvtCount++;
  1027. if (loadEvtCount == 2)
  1028. {
  1029. win.webContents.send('export-vsdx', args);
  1030. ipcMain.once('export-vsdx-finished', (evt, data) =>
  1031. {
  1032. var hasError = false;
  1033. if (data == null)
  1034. {
  1035. hasError = true;
  1036. }
  1037. //Set finalize here since it is call in the reply below
  1038. function finalize()
  1039. {
  1040. win.destroy();
  1041. };
  1042. if (directFinalize === true)
  1043. {
  1044. event.finalize = finalize;
  1045. }
  1046. else
  1047. {
  1048. //Destroy the window after response being received by caller
  1049. ipcMain.once('export-finalize', finalize);
  1050. }
  1051. if (hasError)
  1052. {
  1053. event.reply('export-error');
  1054. }
  1055. else
  1056. {
  1057. event.reply('export-success', data);
  1058. }
  1059. });
  1060. }
  1061. }
  1062. //Order of these two events is not guaranteed, so wait for them async.
  1063. //TOOD There is still a chance we catch another window 'app-load-finished' if user created multiple windows quickly
  1064. ipcMain.once('app-load-finished', loadFinished);
  1065. win.webContents.on('did-finish-load', loadFinished);
  1066. };
  1067. async function mergePdfs(pdfFiles, xml)
  1068. {
  1069. //Pass throgh single files
  1070. if (pdfFiles.length == 1 && xml == null)
  1071. {
  1072. return pdfFiles[0];
  1073. }
  1074. try
  1075. {
  1076. const pdfDoc = await PDFDocument.create();
  1077. pdfDoc.setCreator('diagrams.net');
  1078. if (xml != null)
  1079. {
  1080. //Embed diagram XML as file attachment
  1081. await pdfDoc.attach(Buffer.from(xml).toString('base64'), 'diagram.xml', {
  1082. mimeType: 'application/vnd.jgraph.mxfile',
  1083. description: 'Diagram Content'
  1084. });
  1085. }
  1086. for (var i = 0; i < pdfFiles.length; i++)
  1087. {
  1088. const pdfFile = await PDFDocument.load(pdfFiles[i].buffer);
  1089. const pages = await pdfDoc.copyPages(pdfFile, pdfFile.getPageIndices());
  1090. pages.forEach(p => pdfDoc.addPage(p));
  1091. }
  1092. const pdfBytes = await pdfDoc.save();
  1093. return Buffer.from(pdfBytes);
  1094. }
  1095. catch(e)
  1096. {
  1097. throw new Error('Error during PDF combination: ' + e.message);
  1098. }
  1099. }
  1100. //TODO Use canvas to export images if math is not used to speedup export (no capturePage). Requires change to export3.html also
  1101. function exportDiagram(event, args, directFinalize)
  1102. {
  1103. if (args.format == 'vsdx')
  1104. {
  1105. exportVsdx(event, args, directFinalize);
  1106. return;
  1107. }
  1108. var browser = null;
  1109. try
  1110. {
  1111. browser = new BrowserWindow({
  1112. webPreferences: {
  1113. preload: `${__dirname}/electron-preload.js`,
  1114. backgroundThrottling: false,
  1115. contextIsolation: true,
  1116. disableBlinkFeatures: 'Auxclick' // Is this needed?
  1117. },
  1118. show : false,
  1119. frame: false,
  1120. enableLargerThanScreen: true,
  1121. transparent: args.format == 'png' && (args.bg == null || args.bg == 'none'),
  1122. parent: windowsRegistry[0] //set parent to first opened window. Not very accurate, but useful when all visible windows are closed
  1123. });
  1124. browser.loadURL(`file://${__dirname}/export3.html`);
  1125. const contents = browser.webContents;
  1126. var pageByPage = (args.format == 'pdf' && !args.print), from, pdfs;
  1127. if (pageByPage)
  1128. {
  1129. from = args.allPages? 0 : parseInt(args.from || 0);
  1130. to = args.allPages? 1000 : parseInt(args.to || 1000) + 1; //The 'to' will be corrected later
  1131. pdfs = [];
  1132. args.from = from;
  1133. args.to = from;
  1134. args.allPages = false;
  1135. }
  1136. contents.on('did-finish-load', function()
  1137. {
  1138. //Set finalize here since it is call in the reply below
  1139. function finalize()
  1140. {
  1141. browser.destroy();
  1142. };
  1143. if (directFinalize === true)
  1144. {
  1145. event.finalize = finalize;
  1146. }
  1147. else
  1148. {
  1149. //Destroy the window after response being received by caller
  1150. ipcMain.once('export-finalize', finalize);
  1151. }
  1152. function renderingFinishHandler(evt, renderInfo)
  1153. {
  1154. if (renderInfo == null)
  1155. {
  1156. event.reply('export-error');
  1157. return;
  1158. }
  1159. var pageCount = renderInfo.pageCount, bounds = null;
  1160. //For some reason, Electron 9 doesn't send this object as is without stringifying. Usually when variable is external to function own scope
  1161. try
  1162. {
  1163. bounds = JSON.parse(renderInfo.bounds);
  1164. }
  1165. catch(e)
  1166. {
  1167. bounds = null;
  1168. }
  1169. var pdfOptions = {pageSize: 'A4'};
  1170. var hasError = false;
  1171. if (bounds == null || bounds.width < 5 || bounds.height < 5) //very small page size never return from printToPDF
  1172. {
  1173. //A workaround to detect errors in the input file or being empty file
  1174. hasError = true;
  1175. }
  1176. else
  1177. {
  1178. //Chrome generates Pdf files larger than requested pixels size and requires scaling
  1179. var fixingScale = 0.959;
  1180. var w = Math.ceil(bounds.width * fixingScale);
  1181. // +0.1 fixes cases where adding 1px below is not enough
  1182. // Increase this if more cropped PDFs have extra empty pages
  1183. var h = Math.ceil(bounds.height * fixingScale + 0.1);
  1184. pdfOptions = {
  1185. printBackground: true,
  1186. pageSize : {
  1187. width: w * MICRON_TO_PIXEL,
  1188. height: (h + 2) * MICRON_TO_PIXEL //the extra 2 pixels to prevent adding an extra empty page
  1189. },
  1190. marginsType: 1 // no margin
  1191. }
  1192. }
  1193. var base64encoded = args.base64 == '1';
  1194. if (hasError)
  1195. {
  1196. event.reply('export-error');
  1197. }
  1198. else if (args.format == 'png' || args.format == 'jpg' || args.format == 'jpeg')
  1199. {
  1200. //Adds an extra pixel to prevent scrollbars from showing
  1201. var newBounds = {width: Math.ceil(bounds.width + bounds.x) + 1, height: Math.ceil(bounds.height + bounds.y) + 1};
  1202. browser.setBounds(newBounds);
  1203. //TODO The browser takes sometime to show the graph (also after resize it takes some time to render)
  1204. // 1 sec is most probably enough (for small images, 5 for large ones) BUT not a stable solution
  1205. setTimeout(function()
  1206. {
  1207. browser.capturePage().then(function(img)
  1208. {
  1209. //Image is double the given bounds, so resize is needed!
  1210. var tScale = 1;
  1211. //If user defined width and/or height, enforce it precisely here. Height override width
  1212. if (args.h)
  1213. {
  1214. tScale = args.h / newBounds.height;
  1215. }
  1216. else if (args.w)
  1217. {
  1218. tScale = args.w / newBounds.width;
  1219. }
  1220. newBounds.width *= tScale;
  1221. newBounds.height *= tScale;
  1222. img = img.resize(newBounds);
  1223. var data = args.format == 'png'? img.toPNG() : img.toJPEG(args.jpegQuality || 90);
  1224. if (args.dpi != null && args.format == 'png')
  1225. {
  1226. data = writePngWithText(data, 'dpi', args.dpi);
  1227. }
  1228. if (args.embedXml == "1" && args.format == 'png')
  1229. {
  1230. data = writePngWithText(data, "mxGraphModel", args.xml, true,
  1231. base64encoded);
  1232. }
  1233. else
  1234. {
  1235. if (base64encoded)
  1236. {
  1237. data = data.toString('base64');
  1238. }
  1239. }
  1240. event.reply('export-success', data);
  1241. });
  1242. }, bounds.width * bounds.height < LARGE_IMAGE_AREA? 1000 : 5000);
  1243. }
  1244. else if (args.format == 'pdf')
  1245. {
  1246. if (args.print)
  1247. {
  1248. pdfOptions = {
  1249. scaleFactor: args.pageScale,
  1250. printBackground: true,
  1251. pageSize : {
  1252. width: args.pageWidth * MICRON_TO_PIXEL,
  1253. //This height adjustment fixes the output. TODO Test more cases
  1254. height: (args.pageHeight * 1.025) * MICRON_TO_PIXEL
  1255. },
  1256. marginsType: 1 // no margin
  1257. };
  1258. contents.print(pdfOptions, (success, errorType) =>
  1259. {
  1260. //Consider all as success
  1261. event.reply('export-success', {});
  1262. });
  1263. }
  1264. else
  1265. {
  1266. contents.printToPDF(pdfOptions).then(async (data) =>
  1267. {
  1268. pdfs.push(data);
  1269. to = to > pageCount? pageCount : to;
  1270. from++;
  1271. if (from < to)
  1272. {
  1273. args.from = from;
  1274. args.to = from;
  1275. ipcMain.once('render-finished', renderingFinishHandler);
  1276. contents.send('render', args);
  1277. }
  1278. else
  1279. {
  1280. data = await mergePdfs(pdfs, args.embedXml == '1' ? args.xml : null);
  1281. event.reply('export-success', data);
  1282. }
  1283. })
  1284. .catch((error) =>
  1285. {
  1286. event.reply('export-error', error);
  1287. });
  1288. }
  1289. }
  1290. else if (args.format == 'svg')
  1291. {
  1292. contents.send('get-svg-data');
  1293. ipcMain.once('svg-data', (evt, data) =>
  1294. {
  1295. event.reply('export-success', data);
  1296. });
  1297. }
  1298. else
  1299. {
  1300. event.reply('export-error', 'Error: Unsupported format');
  1301. }
  1302. };
  1303. ipcMain.once('render-finished', renderingFinishHandler);
  1304. if (args.format == 'xml')
  1305. {
  1306. ipcMain.once('xml-data', (evt, data) =>
  1307. {
  1308. event.reply('export-success', data);
  1309. });
  1310. ipcMain.once('xml-data-error', () =>
  1311. {
  1312. event.reply('export-error');
  1313. });
  1314. }
  1315. args.border = args.border || 0;
  1316. args.scale = args.scale || 1;
  1317. contents.send('render', args);
  1318. });
  1319. }
  1320. catch (e)
  1321. {
  1322. if (browser != null)
  1323. {
  1324. browser.destroy();
  1325. }
  1326. event.reply('export-error', e);
  1327. console.log('export-error', e);
  1328. }
  1329. };
  1330. ipcMain.on('export', exportDiagram);
  1331. //================================================================
  1332. // Renderer Helper functions
  1333. //================================================================
  1334. const { O_SYNC, O_CREAT, O_WRONLY, O_TRUNC, O_RDONLY } = fs.constants;
  1335. const DRAFT_PREFEX = '.$';
  1336. const OLD_DRAFT_PREFEX = '~$';
  1337. const DRAFT_EXT = '.dtmp';
  1338. const BKP_PREFEX = '.$';
  1339. const OLD_BKP_PREFEX = '~$';
  1340. const BKP_EXT = '.bkp';
  1341. /**
  1342. * Checks the file content type
  1343. * Confirm content is xml, pdf, png, jpg, svg, vsdx ...
  1344. */
  1345. function checkFileContent(body, enc)
  1346. {
  1347. if (body != null)
  1348. {
  1349. let head, headBinay;
  1350. if (typeof body === 'string')
  1351. {
  1352. if (enc == 'base64')
  1353. {
  1354. headBinay = Buffer.from(body.substring(0, 22), 'base64');
  1355. head = headBinay.toString();
  1356. }
  1357. else
  1358. {
  1359. head = body.substring(0, 16);
  1360. headBinay = Buffer.from(head);
  1361. }
  1362. }
  1363. else
  1364. {
  1365. head = new TextDecoder("utf-8").decode(body.subarray(0, 16));
  1366. headBinay = body;
  1367. }
  1368. let c1 = head[0],
  1369. c2 = head[1],
  1370. c3 = head[2],
  1371. c4 = head[3],
  1372. c5 = head[4],
  1373. c6 = head[5],
  1374. c7 = head[6],
  1375. c8 = head[7],
  1376. c9 = head[8],
  1377. c10 = head[9],
  1378. c11 = head[10],
  1379. c12 = head[11],
  1380. c13 = head[12],
  1381. c14 = head[13],
  1382. c15 = head[14],
  1383. c16 = head[15];
  1384. let cc1 = headBinay[0],
  1385. cc2 = headBinay[1],
  1386. cc3 = headBinay[2],
  1387. cc4 = headBinay[3],
  1388. cc5 = headBinay[4],
  1389. cc6 = headBinay[5],
  1390. cc7 = headBinay[6],
  1391. cc8 = headBinay[7],
  1392. cc9 = headBinay[8],
  1393. cc10 = headBinay[9],
  1394. cc11 = headBinay[10],
  1395. cc12 = headBinay[11],
  1396. cc13 = headBinay[12],
  1397. cc14 = headBinay[13],
  1398. cc15 = headBinay[14],
  1399. cc16 = headBinay[15];
  1400. if (c1 == '<')
  1401. {
  1402. // text/html
  1403. if (c2 == '!'
  1404. || ((c2 == 'h'
  1405. && (c3 == 't' && c4 == 'm' && c5 == 'l'
  1406. || c3 == 'e' && c4 == 'a' && c5 == 'd')
  1407. || (c2 == 'b' && c3 == 'o' && c4 == 'd'
  1408. && c5 == 'y')))
  1409. || ((c2 == 'H'
  1410. && (c3 == 'T' && c4 == 'M' && c5 == 'L'
  1411. || c3 == 'E' && c4 == 'A' && c5 == 'D')
  1412. || (c2 == 'B' && c3 == 'O' && c4 == 'D'
  1413. && c5 == 'Y'))))
  1414. {
  1415. return true;
  1416. }
  1417. // application/xml
  1418. if (c2 == '?' && c3 == 'x' && c4 == 'm' && c5 == 'l'
  1419. && c6 == ' ')
  1420. {
  1421. return true;
  1422. }
  1423. // application/svg+xml
  1424. if (c2 == 's' && c3 == 'v' && c4 == 'g' && c5 == ' ')
  1425. {
  1426. return true;
  1427. }
  1428. }
  1429. // big and little (identical) endian UTF-8 encodings, with BOM
  1430. // application/xml
  1431. if (cc1 == 0xef && cc2 == 0xbb && cc3 == 0xbf)
  1432. {
  1433. if (c4 == '<' && c5 == '?' && c6 == 'x')
  1434. {
  1435. return true;
  1436. }
  1437. }
  1438. // big and little endian UTF-16 encodings, with byte order mark
  1439. // application/xml
  1440. if (cc1 == 0xfe && cc2 == 0xff)
  1441. {
  1442. if (cc3 == 0 && c4 == '<' && cc5 == 0 && c6 == '?' && cc7 == 0
  1443. && c8 == 'x')
  1444. {
  1445. return true;
  1446. }
  1447. }
  1448. // application/xml
  1449. if (cc1 == 0xff && cc2 == 0xfe)
  1450. {
  1451. if (c3 == '<' && cc4 == 0 && c5 == '?' && cc6 == 0 && c7 == 'x'
  1452. && cc8 == 0)
  1453. {
  1454. return true;
  1455. }
  1456. }
  1457. // big and little endian UTF-32 encodings, with BOM
  1458. // application/xml
  1459. if (cc1 == 0x00 && cc2 == 0x00 && cc3 == 0xfe && cc4 == 0xff)
  1460. {
  1461. if (cc5 == 0 && cc6 == 0 && cc7 == 0 && c8 == '<' && cc9 == 0
  1462. && cc10 == 0 && cc11 == 0 && c12 == '?' && cc13 == 0
  1463. && cc14 == 0 && cc15 == 0 && c16 == 'x')
  1464. {
  1465. return true;
  1466. }
  1467. }
  1468. // application/xml
  1469. if (cc1 == 0xff && cc2 == 0xfe && cc3 == 0x00 && cc4 == 0x00)
  1470. {
  1471. if (c5 == '<' && cc6 == 0 && cc7 == 0 && cc8 == 0 && c9 == '?'
  1472. && cc10 == 0 && cc11 == 0 && cc12 == 0 && c13 == 'x'
  1473. && cc14 == 0 && cc15 == 0 && cc16 == 0)
  1474. {
  1475. return true;
  1476. }
  1477. }
  1478. // application/pdf (%PDF-)
  1479. if (cc1 == 37 && cc2 == 80 && cc3 == 68 && cc4 == 70 && cc5 == 45)
  1480. {
  1481. return true;
  1482. }
  1483. // image/png
  1484. if ((cc1 == 137 && cc2 == 80 && cc3 == 78 && cc4 == 71 && cc5 == 13
  1485. && cc6 == 10 && cc7 == 26 && cc8 == 10) ||
  1486. (cc1 == 194 && cc2 == 137 && cc3 == 80 && cc4 == 78 && cc5 == 71 && cc6 == 13 //Our embedded PNG+XML
  1487. && cc7 == 10 && cc8 == 26 && cc9 == 10))
  1488. {
  1489. return true;
  1490. }
  1491. // image/jpeg
  1492. if (cc1 == 0xFF && cc2 == 0xD8 && cc3 == 0xFF)
  1493. {
  1494. if (cc4 == 0xE0 || cc4 == 0xEE)
  1495. {
  1496. return true;
  1497. }
  1498. /**
  1499. * File format used by digital cameras to store images.
  1500. * Exif Format can be read by any application supporting
  1501. * JPEG. Exif Spec can be found at:
  1502. * http://www.pima.net/standards/it10/PIMA15740/Exif_2-1.PDF
  1503. */
  1504. if ((cc4 == 0xE1) && (c7 == 'E' && c8 == 'x' && c9 == 'i'
  1505. && c10 == 'f' && cc11 == 0))
  1506. {
  1507. return true;
  1508. }
  1509. }
  1510. // vsdx, vssx (also zip, jar, odt, ods, odp, docx, xlsx, pptx, apk, aar)
  1511. if (cc1 == 0x50 && cc2 == 0x4B && cc3 == 0x03 && cc4 == 0x04)
  1512. {
  1513. return true;
  1514. }
  1515. else if (cc1 == 0x50 && cc2 == 0x4B && cc3 == 0x03 && cc4 == 0x06)
  1516. {
  1517. return true;
  1518. }
  1519. // mxfile, mxlibrary, mxGraphModel
  1520. if (c1 == '<' && c2 == 'm' && c3 == 'x')
  1521. {
  1522. return true;
  1523. }
  1524. }
  1525. return false;
  1526. };
  1527. function isConflict(origStat, stat)
  1528. {
  1529. return stat != null && origStat != null && stat.mtimeMs != origStat.mtimeMs;
  1530. };
  1531. function getDraftFileName(fileObject)
  1532. {
  1533. let filePath = fileObject.path;
  1534. let draftFileName = '', counter = 1, uniquePart = '';
  1535. do
  1536. {
  1537. draftFileName = path.join(path.dirname(filePath), DRAFT_PREFEX + path.basename(filePath) + uniquePart + DRAFT_EXT);
  1538. uniquePart = '_' + counter++;
  1539. } while (fs.existsSync(draftFileName));
  1540. return draftFileName;
  1541. };
  1542. async function getFileDrafts(fileObject)
  1543. {
  1544. let filePath = fileObject.path;
  1545. let draftsPaths = [], drafts = [], draftFileName, counter = 1, uniquePart = '';
  1546. do
  1547. {
  1548. draftsPaths.push(draftFileName);
  1549. draftFileName = path.join(path.dirname(filePath), DRAFT_PREFEX + path.basename(filePath) + uniquePart + DRAFT_EXT);
  1550. uniquePart = '_' + counter++;
  1551. } while (fs.existsSync(draftFileName)); //TODO this assume continuous drafts names
  1552. //Port old draft files to new prefex
  1553. counter = 1;
  1554. uniquePart = '';
  1555. let draftExists = false;
  1556. do
  1557. {
  1558. draftFileName = path.join(path.dirname(filePath), OLD_DRAFT_PREFEX + path.basename(filePath) + uniquePart + DRAFT_EXT);
  1559. draftExists = fs.existsSync(draftFileName);
  1560. if (draftExists)
  1561. {
  1562. const newDraftFileName = path.join(path.dirname(filePath), DRAFT_PREFEX + path.basename(filePath) + uniquePart + DRAFT_EXT);
  1563. await fsProm.rename(draftFileName, newDraftFileName);
  1564. draftsPaths.push(newDraftFileName);
  1565. }
  1566. uniquePart = '_' + counter++;
  1567. } while (draftExists); //TODO this assume continuous drafts names
  1568. //Skip the first null element
  1569. for (let i = 1; i < draftsPaths.length; i++)
  1570. {
  1571. try
  1572. {
  1573. let stat = await fsProm.lstat(draftsPaths[i]);
  1574. drafts.push({data: await fsProm.readFile(draftsPaths[i], 'utf8'),
  1575. created: stat.ctimeMs,
  1576. modified: stat.mtimeMs,
  1577. path: draftsPaths[i]});
  1578. }
  1579. catch (e){} // Ignore
  1580. }
  1581. return drafts;
  1582. };
  1583. async function saveDraft(fileObject, data)
  1584. {
  1585. if (!checkFileContent(data))
  1586. {
  1587. throw new Error('Invalid file data');
  1588. }
  1589. else
  1590. {
  1591. var draftFileName = fileObject.draftFileName || getDraftFileName(fileObject);
  1592. await fsProm.writeFile(draftFileName, data, 'utf8');
  1593. if (isWin)
  1594. {
  1595. try
  1596. {
  1597. // Add Hidden attribute:
  1598. spawn("attrib", ["+h", draftFileName]);
  1599. } catch(e) {}
  1600. }
  1601. return draftFileName;
  1602. }
  1603. }
  1604. async function saveFile(fileObject, data, origStat, overwrite, defEnc)
  1605. {
  1606. if (!checkFileContent(data))
  1607. {
  1608. throw new Error('Invalid file data');
  1609. }
  1610. var retryCount = 0;
  1611. var backupCreated = false;
  1612. var bkpPath = path.join(path.dirname(fileObject.path), BKP_PREFEX + path.basename(fileObject.path) + BKP_EXT);
  1613. const oldBkpPath = path.join(path.dirname(fileObject.path), OLD_BKP_PREFEX + path.basename(fileObject.path) + BKP_EXT);
  1614. var writeEnc = defEnc || fileObject.encoding;
  1615. var writeFile = async function()
  1616. {
  1617. let fh;
  1618. try
  1619. {
  1620. // O_SYNC is for sync I/O and reduce risk of file corruption
  1621. fh = await fsProm.open(fileObject.path, O_SYNC | O_CREAT | O_WRONLY | O_TRUNC);
  1622. await fsProm.writeFile(fh, data, writeEnc);
  1623. }
  1624. finally
  1625. {
  1626. await fh?.close();
  1627. }
  1628. let stat2 = await fsProm.stat(fileObject.path);
  1629. // Workaround for possible writing errors is to check the written
  1630. // contents of the file and retry 3 times before showing an error
  1631. let writtenData = await fsProm.readFile(fileObject.path, writeEnc);
  1632. if (data != writtenData)
  1633. {
  1634. retryCount++;
  1635. if (retryCount < 3)
  1636. {
  1637. return await writeFile();
  1638. }
  1639. else
  1640. {
  1641. throw new Error('all saving trials failed');
  1642. }
  1643. }
  1644. else
  1645. {
  1646. //We'll keep the backup file in case the original file is corrupted. TODO When should we delete the backup file?
  1647. if (backupCreated)
  1648. {
  1649. //fs.unlink(bkpPath, (err) => {}); //Ignore errors!
  1650. //Delete old backup file with old prefix
  1651. if (fs.existsSync(oldBkpPath))
  1652. {
  1653. fs.unlink(oldBkpPath, (err) => {}); //Ignore errors
  1654. }
  1655. }
  1656. return stat2;
  1657. }
  1658. };
  1659. async function doSaveFile(isNew)
  1660. {
  1661. if (enableStoreBkp && !isNew)
  1662. {
  1663. //Copy file to backup file (after conflict and stat is checked)
  1664. let bkpFh;
  1665. try
  1666. {
  1667. //Use file read then write to open the backup file direct sync write to reduce the chance of file corruption
  1668. let fileContent = await fsProm.readFile(fileObject.path, writeEnc);
  1669. bkpFh = await fsProm.open(bkpPath, O_SYNC | O_CREAT | O_WRONLY | O_TRUNC);
  1670. await fsProm.writeFile(bkpFh, fileContent, writeEnc);
  1671. backupCreated = true;
  1672. }
  1673. catch (e)
  1674. {
  1675. if (__DEV__)
  1676. {
  1677. console.log('Backup file writing failed', e); //Ignore
  1678. }
  1679. }
  1680. finally
  1681. {
  1682. await bkpFh?.close();
  1683. if (isWin)
  1684. {
  1685. try
  1686. {
  1687. // Add Hidden attribute:
  1688. spawn("attrib", ["+h", bkpPath]);
  1689. } catch(e) {}
  1690. }
  1691. }
  1692. }
  1693. return await writeFile();
  1694. };
  1695. if (overwrite)
  1696. {
  1697. return await doSaveFile(true);
  1698. }
  1699. else
  1700. {
  1701. let stat = fs.existsSync(fileObject.path)?
  1702. await fsProm.stat(fileObject.path) : null;
  1703. if (stat && isConflict(origStat, stat))
  1704. {
  1705. throw new Error('conflict');
  1706. }
  1707. else
  1708. {
  1709. return await doSaveFile(stat == null);
  1710. }
  1711. }
  1712. };
  1713. async function writeFile(path, data, enc)
  1714. {
  1715. if (!checkFileContent(data, enc))
  1716. {
  1717. throw new Error('Invalid file data');
  1718. }
  1719. else
  1720. {
  1721. return await fsProm.writeFile(path, data, enc);
  1722. }
  1723. };
  1724. function getAppDataFolder()
  1725. {
  1726. try
  1727. {
  1728. var appDataDir = app.getPath('appData');
  1729. var drawioDir = appDataDir + '/draw.io';
  1730. if (!fs.existsSync(drawioDir)) //Usually this dir already exists
  1731. {
  1732. fs.mkdirSync(drawioDir);
  1733. }
  1734. return drawioDir;
  1735. }
  1736. catch(e) {}
  1737. return '.';
  1738. };
  1739. function getDocumentsFolder()
  1740. {
  1741. //On windows, misconfigured Documents folder cause an exception
  1742. try
  1743. {
  1744. return app.getPath('documents');
  1745. }
  1746. catch(e) {}
  1747. return '.';
  1748. };
  1749. function checkFileExists(pathParts)
  1750. {
  1751. let filePath = path.join(...pathParts);
  1752. return {exists: fs.existsSync(filePath), path: filePath};
  1753. };
  1754. async function showOpenDialog(defaultPath, filters, properties)
  1755. {
  1756. return dialog.showOpenDialogSync({
  1757. defaultPath: defaultPath,
  1758. filters: filters,
  1759. properties: properties
  1760. });
  1761. };
  1762. async function showSaveDialog(defaultPath, filters)
  1763. {
  1764. return dialog.showSaveDialogSync({
  1765. defaultPath: defaultPath,
  1766. filters: filters
  1767. });
  1768. };
  1769. async function installPlugin(filePath)
  1770. {
  1771. var pluginsDir = path.join(getAppDataFolder(), '/plugins');
  1772. if (!fs.existsSync(pluginsDir))
  1773. {
  1774. fs.mkdirSync(pluginsDir);
  1775. }
  1776. var pluginName = path.basename(filePath);
  1777. var dstFile = path.join(pluginsDir, pluginName);
  1778. if (fs.existsSync(dstFile))
  1779. {
  1780. throw new Error('fileExists');
  1781. }
  1782. else
  1783. {
  1784. await fsProm.copyFile(filePath, dstFile);
  1785. }
  1786. return {pluginName: pluginName, selDir: path.dirname(filePath)};
  1787. }
  1788. function uninstallPlugin(plugin)
  1789. {
  1790. var pluginsFile = path.join(getAppDataFolder(), '/plugins', plugin);
  1791. if (fs.existsSync(pluginsFile))
  1792. {
  1793. fs.unlinkSync(pluginsFile);
  1794. }
  1795. }
  1796. function dirname(path_p)
  1797. {
  1798. return path.dirname(path_p);
  1799. }
  1800. async function readFile(filename, encoding)
  1801. {
  1802. let data = await fsProm.readFile(filename, encoding);
  1803. if (checkFileContent(data, encoding))
  1804. {
  1805. return data;
  1806. }
  1807. throw new Error('Invalid file data');
  1808. }
  1809. async function fileStat(file)
  1810. {
  1811. return await fsProm.stat(file);
  1812. }
  1813. async function isFileWritable(file)
  1814. {
  1815. try
  1816. {
  1817. await fsProm.access(file, fs.constants.W_OK);
  1818. return true;
  1819. }
  1820. catch (e)
  1821. {
  1822. return false;
  1823. }
  1824. }
  1825. function clipboardAction(method, data)
  1826. {
  1827. if (method == 'writeText')
  1828. {
  1829. clipboard.writeText(data);
  1830. }
  1831. else if (method == 'readText')
  1832. {
  1833. return clipboard.readText();
  1834. }
  1835. else if (method == 'writeImage')
  1836. {
  1837. clipboard.write({image:
  1838. nativeImage.createFromDataURL(data.dataUrl), html: '<img src="' +
  1839. data.dataUrl + '" width="' + data.w + '" height="' + data.h + '">'});
  1840. }
  1841. }
  1842. async function deleteFile(file)
  1843. {
  1844. // Reading the header of the file to confirm it is a file we can delete
  1845. let fh = await fsProm.open(file, O_RDONLY);
  1846. let buffer = Buffer.allocUnsafe(16);
  1847. await fh.read(buffer, 0, 16);
  1848. await fh.close();
  1849. if (checkFileContent(buffer))
  1850. {
  1851. await fsProm.unlink(file);
  1852. }
  1853. }
  1854. function windowAction(method)
  1855. {
  1856. let win = BrowserWindow.getFocusedWindow();
  1857. if (win)
  1858. {
  1859. if (method == 'minimize')
  1860. {
  1861. win.minimize();
  1862. }
  1863. else if (method == 'maximize')
  1864. {
  1865. win.maximize();
  1866. }
  1867. else if (method == 'unmaximize')
  1868. {
  1869. win.unmaximize();
  1870. }
  1871. else if (method == 'close')
  1872. {
  1873. win.close();
  1874. }
  1875. else if (method == 'isMaximized')
  1876. {
  1877. return win.isMaximized();
  1878. }
  1879. else if (method == 'removeAllListeners')
  1880. {
  1881. win.removeAllListeners();
  1882. }
  1883. }
  1884. }
  1885. const allowedUrls = /^(?:https?|mailto|tel|callto):/i;
  1886. function openExternal(url)
  1887. {
  1888. //Only open http(s), mailto, tel, and callto links
  1889. if (allowedUrls.test(url))
  1890. {
  1891. shell.openExternal(url);
  1892. return true;
  1893. }
  1894. return false;
  1895. }
  1896. function watchFile(path)
  1897. {
  1898. let win = BrowserWindow.getFocusedWindow();
  1899. if (win)
  1900. {
  1901. fs.watchFile(path, (curr, prev) => {
  1902. try
  1903. {
  1904. win.webContents.send('fileChanged', {
  1905. path: path,
  1906. curr: curr,
  1907. prev: prev
  1908. });
  1909. }
  1910. catch (e) {} // Ignore
  1911. });
  1912. }
  1913. }
  1914. function unwatchFile(path)
  1915. {
  1916. fs.unwatchFile(path);
  1917. }
  1918. function getCurDir()
  1919. {
  1920. return __dirname;
  1921. }
  1922. ipcMain.on("rendererReq", async (event, args) =>
  1923. {
  1924. try
  1925. {
  1926. let ret = null;
  1927. switch(args.action)
  1928. {
  1929. case 'saveFile':
  1930. ret = await saveFile(args.fileObject, args.data, args.origStat, args.overwrite, args.defEnc);
  1931. break;
  1932. case 'writeFile':
  1933. ret = await writeFile(args.path, args.data, args.enc);
  1934. break;
  1935. case 'saveDraft':
  1936. ret = await saveDraft(args.fileObject, args.data);
  1937. break;
  1938. case 'getFileDrafts':
  1939. ret = await getFileDrafts(args.fileObject);
  1940. break;
  1941. case 'getAppDataFolder':
  1942. ret = await getAppDataFolder();
  1943. break;
  1944. case 'getDocumentsFolder':
  1945. ret = await getDocumentsFolder();
  1946. break;
  1947. case 'checkFileExists':
  1948. ret = await checkFileExists(args.pathParts);
  1949. break;
  1950. case 'showOpenDialog':
  1951. dialogOpen = true;
  1952. ret = await showOpenDialog(args.defaultPath, args.filters, args.properties);
  1953. dialogOpen = false;
  1954. break;
  1955. case 'showSaveDialog':
  1956. dialogOpen = true;
  1957. ret = await showSaveDialog(args.defaultPath, args.filters);
  1958. dialogOpen = false;
  1959. break;
  1960. case 'installPlugin':
  1961. ret = await installPlugin(args.filePath);
  1962. break;
  1963. case 'uninstallPlugin':
  1964. ret = await uninstallPlugin(args.plugin);
  1965. break;
  1966. case 'dirname':
  1967. ret = await dirname(args.path);
  1968. break;
  1969. case 'readFile':
  1970. ret = await readFile(args.filename, args.encoding);
  1971. break;
  1972. case 'clipboardAction':
  1973. ret = await clipboardAction(args.method, args.data);
  1974. break;
  1975. case 'deleteFile':
  1976. ret = await deleteFile(args.file);
  1977. break;
  1978. case 'fileStat':
  1979. ret = await fileStat(args.file);
  1980. break;
  1981. case 'isFileWritable':
  1982. ret = await isFileWritable(args.file);
  1983. break;
  1984. case 'windowAction':
  1985. ret = await windowAction(args.method);
  1986. break;
  1987. case 'openExternal':
  1988. ret = await openExternal(args.url);
  1989. break;
  1990. case 'watchFile':
  1991. ret = await watchFile(args.path);
  1992. break;
  1993. case 'unwatchFile':
  1994. ret = await unwatchFile(args.path);
  1995. break;
  1996. case 'getCurDir':
  1997. ret = await getCurDir();
  1998. break;
  1999. };
  2000. event.reply('mainResp', {success: true, data: ret, reqId: args.reqId});
  2001. }
  2002. catch (e)
  2003. {
  2004. event.reply('mainResp', {error: true, msg: e.message, e: e, reqId: args.reqId});
  2005. }
  2006. });