electron.js 54 KB

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