text: window.PLUGINS_BASE_PATH = '.';
  window.TEMPLATE_PATH = 'templates';
  window.DRAW_MATH_URL = 'math';
  FeedbackDialog.feedbackUrl = 'https://log.draw.io/email';
  
  //Disables eval for JS (uses shapes.min.js)
  mxStencilRegistry.allowEval = false;
  
  (function()
  {
  	// Overrides default mode
  	App.mode = App.MODE_DEVICE;
  	
  	// Disables preview option in embed dialog
  	EmbedDialog.showPreviewOption = false;
  
  	// Disables new window option in edit diagram dialog
  	EditDiagramDialog.showNewWindowOption = false;
  
  	// Redirects printing to iframe to avoid document.write
  	var printDialogCreatePrintPreview = PrintDialog.createPrintPreview; 
  	
  	PrintDialog.createPrintPreview = function()
  	{
  		var iframe = document.createElement('iframe');
  		document.body.appendChild(iframe);
  
  		var result = printDialogCreatePrintPreview.apply(this, arguments);
  		result.wnd = iframe.contentWindow;
  		result.iframe = iframe;
  				
  		// Workaround for lost gradients in print output
  		result.previousGetBaseUrl = mxSvgCanvas2D.prototype.getBaseUrl;
  		
  		mxSvgCanvas2D.prototype.getBaseUrl = function()
  		{
  			return '';
  		};
  		
  		return result;
  	};
  	
  	var oldWindowOpen = window.open;
  	window.open = function(url)
  	{
  		if (url != null && url.startsWith('http'))
  		{
  			const {shell} = require('electron');
  			shell.openExternal(url);
  		}
  		else
  		{
  			return oldWindowOpen(url);
  		}
  	}
  
  	var origAppMain = App.main;
  	
  	App.main = function()
  	{
  		//TODO Move all file system operations to this worker to offload the renderer thread
  		//TODO Use async version of any sync function used here especially if it is in critical path. For example, open dialog sync block the UI until dialog is shown
  		App.filesWorker = new Worker('electronFilesWorker.js');
  		
  		App.filesWorkerReqId = 1;
  		App.filesWorkerReqInfo = {};
  
  		App.filesWorkerReq = function(msg, callback, error)
  		{
  			msg.reqId = App.filesWorkerReqId++;
  			App.filesWorkerReqInfo[msg.reqId] = {callback: callback, error: error};
  			App.filesWorker.postMessage(msg);	
  		};
  		
  		App.filesWorker.onmessage = function(e) 
  		{
  			var resp = e.data;
  			var callbacks = App.filesWorkerReqInfo[resp.reqId];
  			
  			if (resp.error)
  			{
  				callbacks.error(resp.msg, resp.e);
  			}
  			else
  			{
  				callbacks.callback(resp.data);
  			}
  			
  			delete App.filesWorkerReqInfo[resp.reqId];
  		};
  		
  		//Load desktop plugins
  		var plugins = (mxSettings.settings != null) ? mxSettings.getPlugins() : null;
  		App.initPluginCallback();
  
  		if (plugins != null && plugins.length > 0)
  		{
  			for (var i = 0; i < plugins.length; i++)
  			{
  				try
  				{
  					if (plugins[i].startsWith('/plugins/'))
  					{
  						plugins[i] = '.' + plugins[i];
  					}
  					//Support old plugins added using file:// workaround
  					else if (!plugins[i].startsWith('file://'))
  					{
  						var fs = require('fs');
  						var sysPath = require('path');
  			        	var pluginsFile = sysPath.join(getAppDataFolder(), '/plugins', plugins[i]);
  			        	
  			        	if (fs.existsSync(pluginsFile))
  			        	{
  			        		plugins[i] = 'file://' + pluginsFile;
  			        	}
  			        	else
  		        		{
  			        		continue; //skip not found files
  		        		}
  					}
  						
  					mxscript(plugins[i]);
  				}
  				catch (e)
  				{
  					// ignore
  				}
  			}
  		}
  		
  		//Disable web plugins loading
  		urlParams['plugins'] = '0';
  		origAppMain.apply(this, arguments);
  	};
  	
  	mxPrintPreview.prototype.addPageBreak = function(doc)
  	{
  		// Do nothing
  	};
  
  	mxPrintPreview.prototype.closeDocument = function()
  	{
  		var doc = this.wnd.document;
  		
  		// Removes all event handlers in the print output
  		mxEvent.release(doc.body);
  	};
  	
  	PrintDialog.printPreview = function(preview)
  	{
  		if (preview.iframe != null)
  		{
  			preview.iframe.contentWindow.print();
  			preview.iframe.parentNode.removeChild(preview.iframe);
  		
  			mxSvgCanvas2D.prototype.getBaseUrl = preview.previousGetBaseUrl;
  			preview.iframe = null;
  		}
  	};
  	
  	PrintDialog.previewEnabled = false;
  	
  	var menusInit = Menus.prototype.init;
  	Menus.prototype.init = function()
  	{
  		menusInit.apply(this, arguments);
  
  		var editorUi = this.editorUi;
  
  		editorUi.actions.put('useOffline', new Action(mxResources.get('useOffline') + '...', function()
  		{
  			editorUi.openLink('https://www.draw.io/')
  		}));
  		
  		this.put('openRecent', new Menu(function(menu, parent)
  		{
  			var recent = editorUi.getRecent();
  
  			if (recent != null)
  			{
  				for (var i = 0; i < recent.length; i++)
  				{
  					(function(entry)
  					{
  						menu.addItem(entry.title, null, function()
  						{
  							function doOpenRecent()
  							{
  								//Simulate opening a file via args
  								editorUi.loadArgs({args: [entry.id]});
  							};
  							
  							var file = editorUi.getCurrentFile();
  							
  							if (file != null && file.isModified())
  							{
  								editorUi.confirm(mxResources.get('allChangesLost'), null, doOpenRecent,
  									mxResources.get('cancel'), mxResources.get('discardChanges'));
  							}
  							else
  							{
  								doOpenRecent();
  							}
  						}, parent);
  					})(recent[i]);
  				}
  
  				menu.addSeparator(parent);
  			}
  
  			menu.addItem(mxResources.get('reset'), null, function()
  			{
  				editorUi.resetRecent();
  			}, parent);
  		}));
  		
  		// Replaces file menu to replace openFrom menu with open and rename downloadAs to export
  		this.put('file', new Menu(mxUtils.bind(this, function(menu, parent)
  		{
  			this.addMenuItems(menu, ['new', 'open'], parent);
  			this.addSubmenu('openRecent', menu, parent);
  			this.addMenuItems(menu, ['-', 'synchronize', '-', 'save', 'saveAs', '-', 'import'], parent);
  			this.addSubmenu('exportAs', menu, parent);
  			menu.addSeparator(parent);
  			this.addSubmenu('embed', menu, parent);
  			menu.addSeparator(parent);
  			this.addMenuItems(menu, ['newLibrary', 'openLibrary'], parent);
  
  			var file = editorUi.getCurrentFile();
  			
  			if (file != null && editorUi.fileNode != null)
  			{
  				var filename = (file.getTitle() != null) ?
  					file.getTitle() : editorUi.defaultFilename;
  				
  				if (!/(\.html)$/i.test(filename) &&
  					!/(\.svg)$/i.test(filename))
  				{
  					this.addMenuItems(menu, ['-', 'properties']);
  				}
  			}
  			
  			this.addMenuItems(menu, ['-', 'pageSetup', 'print', '-', 'close'], parent);
  			// LATER: Find API for application.quit
  		})));
  	};
  	
  	function getDocumentsFolder()
  	{
  		//On windows, misconfigured Documents folder cause an exception
  		try
  		{
  			return require('electron').remote.app.getPath('documents');
  		}
  		catch(e) {}
  		
  		return '.';
  	};
  
  	function getAppDataFolder()
  	{
  		try
  		{
  			var fs = require('fs');
  			var appDataDir = require('electron').remote.app.getPath('appData');
          	var drawioDir = appDataDir + '/draw.io';
          	
          	if (!fs.existsSync(drawioDir)) //Usually this dir already exists
          	{
          		fs.mkdirSync(drawioDir);
          	}
          	
  			return drawioDir;
  		}
  		catch(e) {}
  		
  		return '.';
  	};
  	
  	var graphCreateLinkForHint = Graph.prototype.createLinkForHint;
  	
  	Graph.prototype.createLinkForHint = function(href, label)
  	{
  		var a = graphCreateLinkForHint.call(this, href, label);
  		
  		if (href != null && !this.isCustomLink(href))
  		{
  			// KNOWN: Event with gesture handler mouseUp the middle click opens a framed window
  			mxEvent.addListener(a, 'click', mxUtils.bind(this, function(evt)
  			{
  				this.openLink(a.getAttribute('href'), a.getAttribute('target'));
  				mxEvent.consume(evt);
  			}));
  		}
  		
  		return a;
  	};
  	
  	Graph.prototype.openLink = function(url, target)
  	{
  		require('electron').shell.openExternal(url);
  	};
  	
  	// Initializes the user interface
  	var editorUiInit = EditorUi.prototype.init;
  	EditorUi.prototype.init = function()
  	{
  		editorUiInit.apply(this, arguments);
  
  		var editorUi = this;
  		var graph = this.editor.graph;
  		
  		global.__emt_isModified =
  		e => {
  			if (editorUi.getCurrentFile())
  			{
  				return editorUi.getCurrentFile().isModified()
  			}
  
  			return false
  		}
  		
  		// global.__emt_getCurrentFile = e => {
  		// 	return this.getCurrentFile()
  		// }
  
  		// Adds support for libraries
  		this.actions.addAction('newLibrary...', mxUtils.bind(this, function()
  		{
  			editorUi.showLibraryDialog(null, null, null, null, App.MODE_DEVICE);
  		}));
  		
  		this.actions.addAction('openLibrary...', mxUtils.bind(this, function()
  		{
  			editorUi.pickLibrary(App.MODE_DEVICE);
  		}));
  
  		// Replaces import action
  		this.actions.addAction('import...', mxUtils.bind(this, function()
  		{
  			if (editorUi.getCurrentFile() != null)
  			{
  				const electron = require('electron');
  				var remote = electron.remote;
  				var dialog = remote.dialog;
  				const sysPath = require('path')
  				var lastDir = localStorage.getItem('.lastImpDir');
  				
  		        var paths = dialog.showOpenDialogSync({
  		        	defaultPath: lastDir || getDocumentsFolder(),
  		        	properties: ['openFile']
  		        });
  			           
  		        if (paths !== undefined && paths[0] != null)
  		        {
  		        	var path = paths[0];
  		        	localStorage.setItem('.lastImpDir', sysPath.dirname(path));
  		        	var asImage = /\.png$/i.test(path) || /\.gif$/i.test(path) || /\.jpe?g$/i.test(path);
  		        	var encoding = (asImage || /\.pdf$/i.test(path) || /\.vsdx$/i.test(path) || /\.vssx$/i.test(path)) ?
  		        		'base64' : 'utf-8';
  
  					if (editorUi.spinner.spin(document.body, mxResources.get('loading')))
  					{
  						var fs = require('fs');
  
  						fs.readFile(path, encoding, mxUtils.bind(this, function (e, data)
  				        {
  			        		if (e)
  			        		{
  			        			editorUi.spinner.stop();
  			        			editorUi.handleError(e);
  			        		}
  			        		else
  				        	{
  								try
  								{
  									if (editorUi.isLucidChartData(data))
  									{
  										editorUi.convertLucidChart(data, function(xml)
  										{
  											editorUi.spinner.stop();
  											graph.setSelectionCells(editorUi.importXml(xml));
  										}, function(e)
  										{
  											editorUi.spinner.stop();
  											editorUi.handleError(e);
  										});
  									}
  									else if  (/(\.vsdx)($|\?)/i.test(path))
  									{
  										editorUi.importVisio(editorUi.base64ToBlob(data, 'application/octet-stream'), function(xml)
  										{
  											editorUi.spinner.stop();
  											graph.setSelectionCells(editorUi.importXml(xml));
  										});
  									}
  									else if (!editorUi.isOffline() && new XMLHttpRequest().upload && editorUi.isRemoteFileFormat(data, path))
  									{
  										// Asynchronous parsing via server
  										editorUi.parseFile(new Blob([data], {type : 'application/octet-stream'}), mxUtils.bind(this, function(xhr)
  										{
  											if (xhr.readyState == 4)
  											{
  												editorUi.spinner.stop();
  												
  												if (xhr.status >= 200 && xhr.status <= 299)
  												{
  													graph.setSelectionCells(editorUi.importXml(xhr.responseText));
  												}
  											}
  										}), path);
  									}
  									else
  									{
  										if (/\.pdf$/i.test(path))
  									    {
  											var tmp = Editor.extractGraphModelFromPdf(data);
  											
  											if (tmp != null)
  											{
  												data = tmp;
  											}
  							    		}
  										else if (/\.png$/i.test(path))
  										{
  											var tmp = editorUi.extractGraphModelFromPng(data);
  											
  											if (tmp != null)
  											{
  												asImage = false;
  												data = tmp;
  											}
  										}
  										else if (/\.svg$/i.test(path))
  						    			{
  											// LATER: Use importXml without throwing exception if no data
  						    				// Checks if SVG contains content attribute
  					    					var root = mxUtils.parseXml(data);
  				    						var svgs = root.getElementsByTagName('svg');
  				    						
  				    						if (svgs.length > 0)
  					    					{
  				    							var svgRoot = svgs[0];
  						    					var cont = svgRoot.getAttribute('content');
  		
  						    					if (cont != null && cont.charAt(0) != '<' && cont.charAt(0) != '%')
  						    					{
  						    						cont = unescape((window.atob) ? atob(cont) : Base64.decode(cont, true));
  						    					}
  						    					
  						    					if (cont != null && cont.charAt(0) == '%')
  						    					{
  						    						cont = decodeURIComponent(cont);
  						    					}
  		
  						    					if (cont != null && (cont.substring(0, 8) === '<mxfile ' ||
  						    						cont.substring(0, 14) === '<mxGraphModel '))
  						    					{
  						    						asImage = false;
  						    						data = cont;
  						    					}
  						    					else
  						    					{
  						    						asImage = true;
  						    						data = btoa(data);
  						    					}
  					    					}
  						    			}
  										
  										if (asImage)
  										{
  											var img = new Image();
  											img.onload = function()
  											{
  												editorUi.resizeImage(img, img.src, function(data2, w, h)
  												{
  													editorUi.spinner.stop();
  													var pt = graph.getInsertPoint();
  													graph.setSelectionCell(graph.insertVertex(null, null, '', pt.x, pt.y, w, h,
  														'shape=image;aspect=fixed;image=' + editorUi.convertDataUri(data2) + ';'));
  												}, true);
  											};
  											
  											img.onerror = function(e)
  											{
  												editorUi.spinner.stop();
  												editorUi.handleError();
  											};
  											
  											var format = path.substring(path.lastIndexOf('.') + 1);
  											
  											if (format == 'svg')
  											{
  												format = 'svg+xml';
  											}
  											
  											img.src = 'data:image/' + format + ';base64,' + data;
  										}
  										else
  										{
  											editorUi.spinner.stop();
  											
  											if (data != null)
  											{
  												graph.setSelectionCells(editorUi.importXml(data));
  											}
  										}
  									}
  								}
  								catch(e)
  								{
  									editorUi.spinner.stop();
  									editorUi.handleError(e);
  								}
  			        		}
  			        	}));
  					}
  		        }
  			}
  		}));
  		
  		// Replaces new action
  		var oldNew = this.actions.get('new').funct;
  		
  		this.actions.addAction('new...', mxUtils.bind(this, function()
  		{
  			if (this.getCurrentFile() == null)
  			{
  				oldNew();
  			}
  			else
  			{
  				const ipc = require('electron').ipcRenderer
  				ipc.sendSync('winman', {action: 'newfile', opt: {width: 1600}})
  			}
  		}), null, null, Editor.ctrlKey + '+N');
  		
  		this.actions.get('open').shortcut = Editor.ctrlKey + '+O';
  		
  		// Adds shortcut keys for file operations
  		editorUi.keyHandler.bindAction(78, true, 'new'); // Ctrl+N
  		editorUi.keyHandler.bindAction(79, true, 'open'); // Ctrl+O
  		
  		var copyAsImage = this.actions.addAction('copyAsImage', mxUtils.bind(this, function()
  		{
  			const electron = require('electron');
  			var remote = electron.remote;
  			var clipboard = remote.clipboard;
  			var nativeImage = remote.nativeImage;
  			
  			if (editorUi.spinner.spin(document.body, mxResources.get('exporting')))
  			{
  				editorUi.exportToCanvas(function(canvas)
  				{
  			   		try
  			   		{
  			   			var img = nativeImage.createFromDataURL(editorUi.createImageDataUri(canvas, null, 'png'));
  			   			clipboard.writeImage(img);
  		   				editorUi.spinner.stop();
  			   		}
  			   		catch (e)
  			   		{
  			   			editorUi.handleError(e);
  			   		}
  				}, null, null, null, function(e)
  				{
  					editorUi.spinner.stop();
  					editorUi.handleError(e);
  			   	}, null, false, 1, true);
  			}
  		}));
  		
  		copyAsImage.isEnabled = function()
  		{
  			return editorUi.isExportToCanvas() && !editorUi.editor.graph.isSelectionEmpty();
  		}
  	
  		// Inserts copyAsImage into popup menu
  		editorUi.menus.addPopupMenuEditItems = function(menu, cell, evt)
  		{
  			if (editorUi.editor.graph.isSelectionEmpty())
  			{
  				this.addMenuItems(menu, ['pasteHere'], null, evt);
  			}
  			else
  			{
  				this.addMenuItems(menu, ['delete', '-', 'cut', 'copy', 'copyAsImage', '-', 'duplicate'], null, evt);
  			}
  		};
  		
  		editorUi.actions.addAction('plugins...', function()
  		{
  			editorUi.showDialog(new PluginsDialog(editorUi, function(callback)
  			{
  				var div = document.createElement('div');
  				
  				var title = document.createElement('span');
  				title.style.marginTop = '6px';
  				mxUtils.write(title, mxResources.get('builtinPlugins') + ': ');
  				div.appendChild(title);
  				
  				var pluginsSelect = document.createElement('select');
  				pluginsSelect.style.width = '150px';
  				
  				for (var i = 0; i < App.publicPlugin.length; i++)
  				{
  					var option = document.createElement('option');
  					mxUtils.write(option, App.publicPlugin[i]);
  					option.value = App.publicPlugin[i];
  					pluginsSelect.appendChild(option);
  				}
  				
  				div.appendChild(pluginsSelect);
  				mxUtils.br(div);
  				mxUtils.br(div);
  				
  				title = document.createElement('span');
  				mxUtils.write(title, mxResources.get('extPlugins') + ': ');
  				div.appendChild(title);
  				
  				var extPluginsBtn = mxUtils.button(mxResources.get('selectFile') + '...', function()
  				{
  					const electron = require('electron');
  					var remote = electron.remote;
  					var dialog = remote.dialog;
  					const sysPath = require('path');
  					var lastDir = localStorage.getItem('.lastPluginDir');
  					
  			        var paths = dialog.showOpenDialogSync({
  			        	defaultPath: lastDir || getDocumentsFolder(),
  			        	filters: [
  			        	    { name: 'draw.io Plugins', extensions: ['js'] },
  			        	    { name: 'All Files', extensions: ['*'] }
  			    	    ],
  			        	properties: ['openFile']
  			        });
  				           
  			        if (paths !== undefined && paths[0] != null)
  			        {
  			        	localStorage.setItem('.lastPluginDir', sysPath.dirname(paths[0]));
  			        	var fs = require('fs');
  			        	var pluginsDir = sysPath.join(getAppDataFolder(), '/plugins');
  			        	
  			        	if (!fs.existsSync(pluginsDir))
  			        	{
  			        		fs.mkdirSync(pluginsDir);
  			        	}
  			        	
  			        	var pluginName = sysPath.basename(paths[0]);
  			        	var dstFile = sysPath.join(pluginsDir, pluginName);
  			        	
  			        	if (fs.existsSync(dstFile))
  		        		{
  			        		alert(mxResources.get('fileExists'));
  		        		}
  			        	else
  			        	{
  				        	fs.copyFile(paths[0], dstFile, (err) => 
  				        	{
  				        		if (err)
  				        		{
  				        			alert('Adding plugin failed.');
  				        		}
  				        		else
  				        		{
  					        		callback(pluginName);
  					        		editorUi.hideDialog();
  				        		}
  			        		});
  			        	}
  			        }
  				});
  				
  				extPluginsBtn.className = 'geBtn';
  				div.appendChild(extPluginsBtn);
  							
  				var dlg = new CustomDialog(editorUi, div, mxUtils.bind(this, function()
  				{
  	        		callback(App.pluginRegistry[pluginsSelect.value]);
  				}));
  				editorUi.showDialog(dlg.container, 300, 110, true, true);
  			},
  			function(plugin)
  			{
  				var fs = require('fs');
  				const sysPath = require('path')
  				var pluginsFile = sysPath.join(getAppDataFolder(), '/plugins', plugin);
  	        	
  	        	if (fs.existsSync(pluginsFile))
  	        	{
  	        		fs.unlinkSync(pluginsFile);
  	        	}
  			}).container, 360, 170, true, false);
  		});
  	}
  	
  	var appLoad = App.prototype.load;
  
  	App.prototype.load = function()
  	{
  		appLoad.apply(this, arguments);
  		const {ipcRenderer} = require('electron');
  		
  		ipcRenderer.on('args-obj', (event, argsObj) =>
  		{
  			this.loadArgs(argsObj)
  		})
  
  		var editorUi = this;
  		
  		ipcRenderer.on('export-vsdx', (event, argsObj) =>
  		{
  			var file = new LocalFile(editorUi, argsObj.xml, '');
  			
  			editorUi.fileLoaded(file);
  
  			try
  			{
  				editorUi.saveData = function(filename, format, data, mimeType, base64Encoded)
  				{
  					ipcRenderer.send('export-vsdx-finished', data);
  				};
  				
  				var expSuccess = new VsdxExport(editorUi).exportCurrentDiagrams();
  
  				if (!expSuccess)
  				{
  					ipcRenderer.send('export-vsdx-finished', null);
  				}
  			}
  			catch (e)
  			{
  				ipcRenderer.send('export-vsdx-finished', null);
  			}
  		})	
  
  		//We do some async stuff during app loading so we need to know exactly when loading is finished (it is not when onload is finished)
  		ipcRenderer.send('app-load-finished', null);
  	}
  	
  	App.prototype.loadArgs = function(argsObj)
  	{
  		var paths = argsObj.args;
  		
  		// If a file is passed, and it is not an argument (has a leading -) 
  		if (paths !== undefined && paths[0] != null && paths[0].indexOf('-') != 0 && this.spinner.spin(document.body, mxResources.get('loading')))
  		{
  			var path = paths[0];
  			this.hideDialog();
  			
  			var success = mxUtils.bind(this, function(fileEntry, data, stat, name, isModified)
  			{
  				this.spinner.stop();
  				
  				if (data != null)
  				{
  					var file = new LocalFile(this, data, name || '');
  					file.fileObject = fileEntry;
  					file.stat = stat;
  					file.setModified(isModified? true : false);
  					this.fileLoaded(file);
  				}
  			});
  			
  			var error = mxUtils.bind(this, function(e)
  			{
  				this.spinner.stop();
  				
  				if (e.code === 'ENOENT')
  				{
  					var title = path.replace(/^.*[\\\/]/, '');
  					var data = this.emptyDiagramXml;
  					var file = new LocalFile(this, data, title, null);
  					
  					file.fileObject = new Object();
  					file.fileObject.path = path;
  					file.fileObject.name = title;
  					file.fileObject.type = 'utf-8';
  					this.fileCreated(file, null, null, null);					
  					this.saveFile();
  				}
  				else
  				{
  					this.handleError(e);
  				}
  				
  			});
  			
  			// Tries to open the file
  			this.readGraphFile(success, error, path);
  		}
  		// If no file is passed, but there is the "create-if-not-exists" flag
  		else if (argsObj.create != null)
  		{
  			var title = 'Untitled document';
  			var data = this.emptyDiagramXml;
  			var file = new LocalFile(this, data, title, null);
  			this.fileCreated(file, null, null, null);
  		}		
  	}
  
  	var origFileLoaded = EditorUi.prototype.fileLoaded;
  	
  	EditorUi.prototype.fileLoaded = function(file)
  	{
  		
  		if (file != null)
  		{
  			if (file.fileObject == null)
  			{
  				var fname = file.getTitle();
  				
  				var fileInfo = openFilesMap[fname];
  				
  				if (fileInfo != null)
  				{
  					file.fileObject = {
  						name: fileInfo.name,
  						path: fileInfo.path,
  						type: fileInfo.type || 'utf-8'
  					};
  					//delete it such that it is not used again incorrectly
  					delete openFilesMap[fname];
  				}
  			}
  			
  			if (file.fileObject != null)
  			{
  				var title = file.fileObject.path;
  				
  				if (title.length > 100)
  				{
  					title = '...' + title.substr(title.length - 97);
  				}
  				
  				this.addRecent({id: file.fileObject.path, title: title});
  			}
  		}
  		
  		origFileLoaded.apply(this, arguments);
  	};
  	
  	// Uses local picker
  	App.prototype.pickFile = function()
  	{
  		var doPickFile = mxUtils.bind(this, function()
  		{
  			this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data, stat, name, isModified)
  			{
  				var file = new LocalFile(this, data, '');
  				file.fileObject = fileEntry;
  				file.stat = stat;
  				file.setModified(isModified? true : false);
  				this.fileLoaded(file);
  			}));
  		});
  
  		var file = this.getCurrentFile();
  		
  		if (file != null && file.isModified())
  		{
  			this.confirm(mxResources.get('allChangesLost'), null, doPickFile,
  				mxResources.get('cancel'), mxResources.get('discardChanges'));
  		}
  		else
  		{
  			doPickFile();
  		}
  	};
  	
  	/**
  	 * Selects a library to load from a picker
  	 * 
  	 * @param mode the device mode, ignored in this case
  	 */
  	App.prototype.pickLibrary = function(mode)
  	{
  		this.chooseFileEntry(mxUtils.bind(this, function(fileEntry, data, stat)
  		{
  			try
  			{
  				var library = new DesktopLibrary(this, data, fileEntry);
  				this.loadLibrary(library);
  			}
  			catch (e)
  			{
  				this.handleError(e, mxResources.get('errorLoadingFile'));
  			}
  		}));
  	};
  	
  	// Uses local picker
  	App.prototype.chooseFileEntry = function(fn)
  	{
  		const electron = require('electron');
  		var remote = electron.remote;
  		var dialog = remote.dialog;
  		const sysPath = require('path')
  		var lastDir = localStorage.getItem('.lastOpenDir');
  		
          var paths = dialog.showOpenDialogSync({
          	defaultPath: lastDir || getDocumentsFolder(),
          	filters: [
          	    { name: 'draw.io Diagrams', extensions: ['drawio', 'xml'] },
          	    { name: 'VSDX Documents', extensions: ['vsdx'] },
          	    { name: 'All Files', extensions: ['*'] }
      	    ],
          	properties: ['openFile']
          });
  	           
          if (paths !== undefined && paths[0] != null)
          {
          	localStorage.setItem('.lastOpenDir', sysPath.dirname(paths[0]));
  
  			this.readGraphFile(fn, mxUtils.bind(this, function(err)
  			{
  				this.handleError(err);
  			}), paths[0]);
          }
          else
          {
          	this.spinner.stop();
          }
  	};
  
  	//In order not to repeat the logic for opening a file, we collect files information here and use them in openLocalFile
  	var origOpenFiles = EditorUi.prototype.openFiles;
  	var openFilesMap = {};
  	
  	EditorUi.prototype.openFiles = function(files, temp)
  	{
  		openFilesMap = {};
  
  		for (var i = 0; i < files.length; i++)
  		{
  			openFilesMap[files[i].name] = files[i];
  		}
  		
  		origOpenFiles.apply(this, arguments);
  	};
  	
  	App.prototype.readGraphFile = function(fn, fnErr, path)
  	{
  		var fs = require('fs');
  		var index = path.lastIndexOf('.png');
  		var isPng = index > -1 && index == path.length - 4;
  		var isVsdx = /\.vsdx$/i.test(path) || /\.vssx$/i.test(path);
  		var encoding = isVsdx? null : ((isPng || /\.pdf$/i.test(path)) ? 'base64' : 'utf-8');
  		var isModified = false, fileLoaded = false;
  
  		var readData = mxUtils.bind(this, function (e, data)
  		{
  			if (e)
  			{
  				fnErr(e);
  				fileLoaded = true;
  			}
  			else
  			{
  				var fileEntry = new Object();
  				fileEntry.path = path;
  				fileEntry.name = path.replace(/^.*[\\\/]/, '');
  				fileEntry.type = encoding;
  
  				// VSDX and PDF files are imported instead of being opened
  				if (isVsdx)
  				{
  					var name = fileEntry.name;
  
  					this.importVisio(data, mxUtils.bind(this, function(xml)
  					{
  						var dot = name.lastIndexOf('.');
  						
  						if (dot >= 0)
  						{
  							name = name.substring(0, name.lastIndexOf('.')) + '.drawio';
  						}
  						else
  						{
  							name = name + '.drawio';
  						}
  						
  						if (xml.substring(0, 10) == '<mxlibrary')
  						{
  							// Creates new temporary file if library is dropped in splash screen
  							if (this.getCurrentFile() == null && urlParams['embed'] != '1')
  							{
  								this.openLocalFile(this.emptyDiagramXml, this.defaultFilename);
  							}
  						
  							try
  			    			{
  								this.loadLibrary(new LocalLibrary(this, xml, name));
  			    			}
  							catch (e)
  			    			{
  			    				this.handleError(e, mxResources.get('errorLoadingFile'));
  			    			}
  							
  							fn();
  						}
  						else
  						{
  							fn(null, xml, null, name, isModified);
  						}
  						
  						fileLoaded = true;
  					}), null, name);
  					
  					return;
  				}
  				else if (/\.pdf$/i.test(path))
  			    {
  					var tmp = Editor.extractGraphModelFromPdf('data:application/pdf;base64,' + data);
  					
  					if (tmp != null)
  					{
  						var name = fileEntry.name;
  						fn(null, tmp, null, name.substring(0, name.lastIndexOf('.')) + '.drawio', isModified);
  						fileLoaded = true;
  
  						return;
  					}
  	    		}
  				else if (isPng)
  				{
  					// Detecting png by extension. Would need https://github.com/mscdex/mmmagic
  					// to do it by inspection
  					data = this.extractGraphModelFromPng('data:image/png;base64,' + data);
  				}
  
  				fs.stat(path, function(err, stat)
  				{
  					if (err)
  					{
  						fnErr(err);
  					}
  					else
  					{
  						fn(fileEntry, data, stat, null, isModified);
  					}
  					
  					fileLoaded = true;
  				});
  			}
  		});
   
  		fs.readFile(path, encoding, readData);
  
      	//Check if a bkp file exists, if one exists, ask user to restore/ignore
  		var checkBkpFile = mxUtils.bind(this, function (e, data)
  		{
  			//Backup file must be loaded after actual file
  			if (!fileLoaded)
  			{
  				setTimeout(function()
  				{
  					checkBkpFile(e, data);
  				}, 10);
  				return;
  			}
  			
  			if (!e)
  			{
  				var dlg = new DraftDialog(this, mxResources.get('backupFound'),
  						data, mxUtils.bind(this, function()
  				{
  					this.hideDialog();
  					isModified = true;
  					readData(null, data);
  					fs.unlink(bkpFile, (err) => {}); //Ignore errors!
  				}), mxUtils.bind(this, function()
  				{
  					this.hideDialog();
  					fs.unlink(bkpFile, (err) => {}); //Ignore errors!
  				}));
  				
  				this.showDialog(dlg.container, 640, 480, true, false, mxUtils.bind(this, function(cancel)
  				{
  					if (cancel)
  					{
  						//TODO Rename backup file?
  					}
  				}));
  				
  				dlg.init();
  			}
  		});
  		
  		var bkpFile = getBkpFilePath(path);
  		fs.readFile(bkpFile, encoding, checkBkpFile);		
  	};
  
  	// Disables temp files in Electron
  	var LocalFileCtor = LocalFile;
  	
  	LocalFile = function(ui, data, title, temp)
  	{
  		LocalFileCtor.call(this, ui, data, title, false);
  	};
  
  	mxUtils.extend(LocalFile, LocalFileCtor);
  
  	LocalFile.prototype.getLatestVersion = function(success, error)
  	{
  		if (this.fileObject == null)
  		{
  			if (error != null)
  			{
  				error({message: mxResources.get('fileNotFound')});
  			}
  		}
  		else
  		{
  			this.ui.readGraphFile(mxUtils.bind(this, function(fileEntry, data, stat, name, isModified)
  			{
  				var file = new LocalFile(this, data, '');
  				file.stat = stat;
  				file.setModified(isModified? true : false);
  				success(file);
  			}), error, this.fileObject.path);
  		}
  	};
  	
  	// Call save as for copy
  	LocalFile.prototype.copyFile = function(success, error)
  	{
  		this.saveAs(this.ui.getCopyFilename(this), success, error);
  	};
  	
  	/**
  	 * Adds all listeners.
  	 */
  	LocalFile.prototype.getDescriptor = function()
  	{
  		return this.stat;
  	};
  
  	/**
  	* Updates the descriptor of this file with the one from the given file.
  	*/
  	LocalFile.prototype.setDescriptor = function(stat)
  	{
  		this.stat = stat;
  	};
  	
  	LocalFile.prototype.reloadFile = function(success)
  	{
  		if (this.fileObject == null)
  		{
  			this.ui.handleError({message: mxResources.get('fileNotFound')});
  		}
  		else
  		{
  			this.ui.spinner.stop();
  			
  			var fn = mxUtils.bind(this, function()
  			{
  				this.setModified(false);
  				var page = this.ui.currentPage;
  				var viewState = this.ui.editor.graph.getViewState();
  				var selection = this.ui.editor.graph.getSelectionCells();
  				
  				if (this.ui.spinner.spin(document.body, mxResources.get('loading')))
  				{
  					this.ui.readGraphFile(mxUtils.bind(this, function(fileEntry, data, stat, name, isModified)
  					{
  						this.ui.spinner.stop();
  						
  						var file = new LocalFile(this.ui, data, '');
  						file.fileObject = fileEntry;
  						file.stat = stat;
  						file.setModified(isModified? true : false);
  						this.ui.fileLoaded(file);
  						this.ui.restoreViewState(page, viewState, selection);
  		
  						if (this.backupPatch != null)
  						{
  							this.patch([this.backupPatch]);
  						}
  						
  						if (success != null)
  						{
  							success();
  						}
  					}), mxUtils.bind(this, function(err)
  					{
  						this.handleFileError(err);
  					}), this.fileObject.path);
  				}
  			});
  	
  			if (this.isModified() && this.backupPatch == null)
  			{
  				this.ui.confirm(mxResources.get('allChangesLost'), mxUtils.bind(this, function()
  				{
  					this.handleFileSuccess(DrawioFile.SYNC == 'manual');
  				}), fn, mxResources.get('cancel'), mxResources.get('discardChanges'));
  			}
  			else
  			{
  				fn();
  			}
  		}
  	};
  
  	LocalFile.prototype.isAutosave = function()
  	{
  		return this.fileObject != null && DrawioFile.prototype.isAutosave.apply(this, arguments);
  	};
  	
  	LocalFile.prototype.isAutosaveOptional = function()
  	{
  		return this.fileObject != null;
  	};
  	
  	LocalFile.prototype.getTitle = function()
  	{
  		return (this.fileObject != null) ? this.fileObject.name : this.title;
  	};
  
  	LocalFile.prototype.isRenamable = function()
  	{
  		return false;
  	};
  	
  	// Restores default implementation of open with autosave
  	LocalFile.prototype.open = DrawioFile.prototype.open;
  	
  	LocalFile.prototype.save = function(revision, success, error, unloading, overwrite)
  	{
  		DrawioFile.prototype.save.apply(this, [revision, mxUtils.bind(this, function()
  		{
  			this.saveFile(revision, success, error, unloading, overwrite);
  		}), error, unloading, overwrite]);
  	};
  
  	LocalFile.prototype.isConflict = function(stat)
  	{
  		return stat != null && this.stat != null && stat.mtimeMs != this.stat.mtimeMs;
  	};
  	
  	LocalFile.prototype.getFilename = function()
  	{
  		var filename = this.title;
  		
  		// Adds default extension
  		if (filename.length > 0 && (!/(\.xml)$/i.test(filename) && !/(\.html)$/i.test(filename) &&
  			!/(\.svg)$/i.test(filename) && !/(\.png)$/i.test(filename) && !/(\.drawio)$/i.test(filename)))
  		{
  			filename += '.drawio';
  		}
  		
  		return filename;
  	};
  	
  	function getBkpFilePath(filePath)
  	{
  		const path = require('path');
  		return path.join(path.dirname(filePath), '~$' + path.basename(filePath) + '.bkp');
  	};
  	
  	// Prototype inheritance needs new functions to be added to subclasses
  	LocalLibrary.prototype.getFilename = LocalFile.prototype.getFilename;
  	
  	LocalFile.prototype.saveFile = function(revision, success, error, unloading, overwrite)
  	{
  		if (!this.savingFile)
  		{
  			var fn = mxUtils.bind(this, function()
  			{
  				var doSave = mxUtils.bind(this, function(data, enc)
  				{
  					var savedData = this.data;
  					
  					// Makes sure no changes get lost while the file is saved
  					var prevModified = this.isModified;
  					var modified = this.isModified();
  					this.setModified(false);
  					this.savingFile = true;
  					
  					var errorWrapper = mxUtils.bind(this, function(e)
  					{
  						this.savingFile = false;
  						this.isModified = prevModified;
  						this.setModified(modified || this.isModified());
  						
  						if (error != null)
  						{
  	        				error(e);
  						}
  					});
  
  					if (this.fileObject.bkpPath == null)
  					{
  						this.fileObject.bkpPath = getBkpFilePath(this.fileObject.path);
  					}
  					
  					App.filesWorkerReq({
  						action: 'saveFile',
  						fileObject: this.fileObject,
  						defEnc: enc,
  						data: data,
  						origStat: this.stat,
  						overwrite: overwrite
  					}, mxUtils.bind(this, function(resp)
  					{
  						this.savingFile = false;
  						this.isModified = prevModified;
  						var lastDesc = this.stat;
  						this.stat = resp.stat;
  						
  						this.fileSaved(savedData, lastDesc, mxUtils.bind(this, function()
  						{
  							this.contentChanged();
  							
  							if (success != null)
  							{
  								success();
  							}
  						}), error);
  					}), 
  					mxUtils.bind(this, function(errMsg, err)
  					{
  						if (errMsg == 'empty data')
  						{
  							this.ui.handleError({message: mxResources.get('errorSavingFile')});
  						}
  						else if (errMsg == 'conflict')
  						{
  							this.inConflictState = true;
  						}
  						
  						errorWrapper();
  					}));
  				});
  	
  				if (!/(\.png)$/i.test(this.fileObject.name))
  				{
  					doSave(this.getData());
  				}
  				else
  				{
  					var p = this.ui.getPngFileProperties(this.ui.fileNode);
  
  					this.ui.getEmbeddedPng(function(data)
  					{
  						doSave(atob(data), 'binary');
  					}, error, null, p.scale, p.border);
  				}
  			});
  			
  			if (this.fileObject == null)
  			{
  				const electron = require('electron');
  				var remote = electron.remote;
  				var dialog = remote.dialog;
  				const sysPath = require('path')
  				var lastDir = localStorage.getItem('.lastSaveDir');
  				
  				var path = dialog.showSaveDialogSync({defaultPath: (lastDir || getDocumentsFolder()) + '/' + this.getFilename()});
  	
  		        if (path != null)
  		        {
  		        	localStorage.setItem('.lastSaveDir', sysPath.dirname(path));
  					this.fileObject = new Object();
  					this.fileObject.path = path;
  					this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  					this.fileObject.type = 'utf-8';
  					fn();
  				}
  		        else
  		        {
  	            	this.ui.spinner.stop();
  		        }
  			}
  			else
  			{
  				fn();
  			}
  		}
  	};
  
  	LocalFile.prototype.saveAs = function(title, success, error)
  	{
  		const electron = require('electron');
  		var remote = electron.remote;
  		var dialog = remote.dialog;
  		const sysPath = require('path')
  		var lastDir = localStorage.getItem('.lastSaveDir');
  		
  		var path = dialog.showSaveDialogSync({
  			defaultPath: (lastDir || getDocumentsFolder()) + '/' + this.getFilename(),
  			filters: [
  				{ name: 'XML File (.drawio)', extensions: ['drawio'] },
  				{ name: 'Editable Bitmap Image (.png)', extensions: ['png'] },
  				{ name: 'Editable Vector Image (.svg)', extensions: ['svg'] },
  				{ name: 'HTML File (.html)', extensions: ['html'] },
  				{ name: 'XML File (.xml)', extensions: ['xml'] }
  	        ]
  		});
          
          if (path != null)
          {
          	localStorage.setItem('.lastSaveDir', sysPath.dirname(path));
  			this.fileObject = new Object();
  			this.fileObject.path = path;
  			this.fileObject.name = path.replace(/^.*[\\\/]/, '');
  			this.fileObject.type = 'utf-8';
  			
  			this.save(false, success, error, null, true);
  		}
  	};
  	
  	App.prototype.saveFile = function(forceDialog)
  	{
  		var file = this.getCurrentFile();
  		
  		if (file != null)
  		{
  			if (!forceDialog && file.getTitle() != null)
  			{
  				file.save(true, mxUtils.bind(this, function()
  				{
  					file.handleFileSuccess(true);
  				}), mxUtils.bind(this, function(err)
  				{
  					file.handleFileError(err, true);
  				}));
  			}
  			else
  			{
  				file.saveAs(null, mxUtils.bind(this, function()
  				{
  					file.handleFileSuccess(true);
  				}), mxUtils.bind(this, function(err)
  				{
  					file.handleFileError(err, true);
  				}));
  			}
  		}
  	};
  	
  	/**
  	 * Translates this point by the given vector.
  	 * 
  	 * @param {number} dx X-coordinate of the translation.
  	 * @param {number} dy Y-coordinate of the translation.
  	 */
  	App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload, fn)
  	{
  		mode = (mode != null) ? mode : this.mode;
  		noSpin = (noSpin != null) ? noSpin : false;
  		noReload = (noReload != null) ? noReload : false;
  		var xml = this.createLibraryDataFromImages(images);
  		
  		var error = mxUtils.bind(this, function(resp)
  		{
  			this.spinner.stop();
  			
  			if (fn != null)
  			{
  				fn();
  			}
  			
  			// Null means cancel by user and is ignored
  			if (resp != null)
  			{
  				this.handleError(resp, mxResources.get('errorSavingFile'));
  			}
  		});
  	
  		// Handles special case for local libraries
  		if (file == null)
  		{
  			file = new LocalLibrary(this, xml, name);
  		}
  		
  		if (noSpin || this.spinner.spin(document.body, mxResources.get('saving')))
  		{
  			file.setData(xml);
  			
  			var doSave = mxUtils.bind(this, function()
  			{
  				file.save(true, mxUtils.bind(this, function(resp)
  				{
  					this.spinner.stop();
  					this.hideDialog(true);
  					
  					if (!noReload)
  					{
  						this.libraryLoaded(file, images)
  					}
  					
  					if (fn != null)
  					{
  						fn();
  					}
  				}), error);
  			});
  			
  			if (name != file.getTitle())
  			{
  				var oldHash = file.getHash();
  				
  				file.rename(name, mxUtils.bind(this, function(resp)
  				{
  					// Change hash in stored settings
  					if (file.constructor != LocalLibrary && oldHash != file.getHash())
  					{
  						mxSettings.removeCustomLibrary(oldHash);
  						mxSettings.addCustomLibrary(file.getHash());
  					}
  	
  					// Workaround for library files changing hash so
  					// the old library cannot be removed from the
  					// sidebar using the updated file in libraryLoaded
  					this.removeLibrarySidebar(oldHash);
  	
  					doSave();
  				}), error)
  			}
  			else
  			{
  				doSave();
  			}
  		}
  	};
  	
  	
  	/**
  	 * Updates action states depending on the selection.
  	 */
  	var editorUiUpdateActionStates = EditorUi.prototype.updateActionStates;
  	EditorUi.prototype.updateActionStates = function()
  	{
  		editorUiUpdateActionStates.apply(this, arguments);
  
  		var file = this.getCurrentFile();
  		var syncEnabled = file != null && file.fileObject != null;
  		this.actions.get('synchronize').setEnabled(syncEnabled);
  	};
  	
  	EditorUi.prototype.saveLocalFile = function(data, filename, mimeType, base64Encoded, format, allowBrowser)
  	{
  		this.saveData(filename, format, data, mimeType, base64Encoded);
  	};
  	
  	EditorUi.prototype.saveRequest = function(filename, format, fn, data, base64Encoded, mimeType)
  	{
  		var xhr = fn(null, '1');
  		
  		if (xhr != null && this.spinner.spin(document.body, mxResources.get('saving')))
  		{
  			xhr.send(mxUtils.bind(this, function()
  			{
  				this.spinner.stop();
  				
  				if (xhr.getStatus() >= 200 && xhr.getStatus() <= 299)
  				{
  					this.saveData(filename, format, xhr.getText(), mimeType, true);
  				}
  				else
  				{
  					this.handleError({message: mxResources.get('errorSavingFile')});
  				}
  			}), mxUtils.bind(this, function(resp)
  			{
  				this.spinner.stop();
  				this.handleError(resp);
  			}));
  		}
  	};
  
  	function mxElectronRequest(reqType, reqObj)
  	{
  		this.reqType = reqType;
  		this.reqObj = reqObj;
  	};
  
  	//Extends mxXmlRequest
  	mxUtils.extend(mxElectronRequest, mxXmlRequest);
  	
  	mxElectronRequest.prototype.send = function(callback, error)
  	{
  		const ipcRenderer = require('electron').ipcRenderer;
  		ipcRenderer.send(this.reqType, this.reqObj);
  		
  		ipcRenderer.once(this.reqType + '-success', (event, data) => 
  		{
  			this.response = data;
  			callback();
  			ipcRenderer.send(this.reqType + '-finalize');
  		})
  
  		ipcRenderer.once(this.reqType + '-error', (event, err) => 
  		{
  			this.hasError = true;
  			error(err);
  			ipcRenderer.send(this.reqType + '-finalize');
  		})
  	};
  	
  	mxElectronRequest.prototype.getStatus = function()
  	{
  		return this.hasError? 500 : 200; 
  	}
  	
  	mxElectronRequest.prototype.getText = function()
  	{
  		return this.response;
  	}
  	
  		//Direct export to pdf
  		EditorUi.prototype.createDownloadRequest = function(filename, format, ignoreSelection, base64, transparent, currentPage, scale, border, grid)
  		{
  			var graph = this.editor.graph;
  			var bounds = graph.getGraphBounds();
  			
  			// Exports only current page for images that does not contain file data, but for
  			// the other formats with XML included or pdf with all pages, we need to send the complete data and use
  			// the from/to URL parameters to specify the page to be exported.
  			var data = this.getFileData(true, null, null, null, ignoreSelection, currentPage == false? false : format != 'xmlpng');
  			var range = null;
  			var allPages = null;
  			
  			if (bounds.width * bounds.height > MAX_AREA || data.length > MAX_REQUEST_SIZE)
  			{
  				throw {message: mxResources.get('drawingTooLarge')};
  			}
  			
  			var embed = '0';
  			
  			if (format == 'pdf' && currentPage == false)
  			{
  				allPages = '1';
  			}
  			
  			if (format == 'xmlpng')
  	       	{
  	       		embed = '1';
  	       		format = 'png';
  	       		
  	       		// Finds the current page number
  	       		if (this.pages != null && this.currentPage != null)
  	       		{
  	       			for (var i = 0; i < this.pages.length; i++)
  	       			{
  	       				if (this.pages[i] == this.currentPage)
  	       				{
  	       					range = i;
  	       					break;
  	       				}
  	       			}
  	       		}
  	       	}
  			
  			var bg = graph.background;
  			
  			if (format == 'png' && transparent)
  			{
  				bg = mxConstants.NONE;
  			}
  			else if (!transparent && (bg == null || bg == mxConstants.NONE))
  			{
  				bg = '#ffffff';
  			}
  			
  			var extras = {globalVars: graph.getExportVariables()};
  			
  			if (grid)
  			{
  				extras.grid = {
  					size: graph.gridSize,
  					steps: graph.view.gridSteps,
  					color: graph.view.gridColor
  				};
  			}
  			
  			return new mxElectronRequest('export', {
  				format: format,
  				xml: data,
  				from: range,
  				bg: (bg != null) ? bg : mxConstants.NONE,
  				filename: (filename != null) ? filename : null,
  				allPages: allPages,
  				base64: base64,
  				embedXml: embed,
  				extras: encodeURIComponent(JSON.stringify(extras)),
  				scale: scale,
  				border: border
  			});
  		};
  		
  	//Export Dialog Pdf case
  	var origExportFile = ExportDialog.exportFile;
  	
  	ExportDialog.exportFile = function(editorUi, name, format, bg, s, b, dpi)
  	{
  		var graph = editorUi.editor.graph;
  		
  		if (format == 'xml' || format == 'svg')
  		{
  			return origExportFile.apply(this, arguments);
  		}
  		else
  		{
  			var data = editorUi.getFileData(true, null, null, null, null, true);
      		var bounds = graph.getGraphBounds();
  			var w = Math.floor(bounds.width * s / graph.view.scale);
  			var h = Math.floor(bounds.height * s / graph.view.scale);
  			
  			if (data.length <= MAX_REQUEST_SIZE && w * h < MAX_AREA)
  			{
  				editorUi.hideDialog();
  				
  				if ((format == 'png' || format == 'jpg' || format == 'jpeg') && editorUi.isExportToCanvas())
  				{
  					if (format == 'png')
  					{
  						editorUi.exportImage(s, bg == null || bg == 'none', true,
  					   		false, false, b, true, false, null, null, dpi);
  					}
  					else 
  					{
  						editorUi.exportImage(s, false, true,
  							false, false, b, true, false, 'jpeg');
  					}
  				}
  				else 
  				{
  					var extras = {globalVars: graph.getExportVariables()};
  					
  					editorUi.saveRequest(name, format,
  						function(newTitle, base64)
  						{
  							return new mxElectronRequest('export', {
  								format: format,
  								xml: data,
  								bg: (bg != null) ? bg : mxConstants.NONE,
  								filename: (newTitle != null) ? newTitle : null,
  								w: w,
  								h: h,
  								border: b,
  								base64: (base64 || '0'),
  								extras: JSON.stringify(extras),
  								dpi: dpi > 0? dpi : null
  							}); 
  						});
  				}
  			}
  			else
  			{
  				mxUtils.alert(mxResources.get('drawingTooLarge'));
  			}
  		}
  	};
  	
  	EditorUi.prototype.saveData = function(filename, format, data, mimeType, base64Encoded)
  	{
  		const electron = require('electron');
  		var remote = electron.remote;
  		var dialog = remote.dialog;
  		var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
  		const sysPath = require('path')
  		var lastDir = localStorage.getItem('.lastExpDir');
  		
  		// Spinner.stop is asynchronous so we must invoke save dialog asynchronously
  		// to give the spinner some time to stop spinning
  		window.setTimeout(mxUtils.bind(this, function()
  		{
  			var dlgConfig = {defaultPath: (lastDir || getDocumentsFolder()) + '/' + filename};
  			var filters = null;
  			
  			switch (format)
  			{
  				case 'xmlpng':
  				case 'png':
  					filters = [
  				          { name: 'PNG Images', extensions: ['png'] }
  				       ];
  				break;
  				case 'jpg':
  				case 'jpeg':
  					filters = [
  				          { name: 'JPEG Images', extensions: ['jpg', 'jpeg'] }
  				       ];
  				break;
  				case 'svg':
  					filters = [
  				          { name: 'SVG Images', extensions: ['svg'] }
  				       ];
  				break;
  				case 'pdf':
  					filters = [
  				          { name: 'PDF Documents', extensions: ['pdf'] }
  				       ];
  				break;
  				case 'vsdx':
  					filters = [
  				          { name: 'VSDX Documents', extensions: ['vsdx'] }
  				       ];
  				break;
  				case 'html':
  					filters = [
  				          { name: 'HTML Documents', extensions: ['html'] }
  				       ];
  				break;
  				case 'xml':
  					filters = [
  				          { name: 'XML Documents', extensions: ['xml'] }
  				       ];
  				break;
  			};
  			
  			dlgConfig['filters'] = filters;
  			var path = dialog.showSaveDialogSync(dlgConfig);
  	
  	        if (path != null)
  	        {
  	        	localStorage.setItem('.lastExpDir', sysPath.dirname(path));
  
  	        	if (data == null || data.length == 0)
  				{
  					this.handleError({message: mxResources.get('errorSavingFile')});
  				}
  				else
  				{
  					var fs = require('fs');
  					resume();
  					
  					var fileObject = new Object();
  					fileObject.path = path;
  					fileObject.name = path.replace(/^.*[\\\/]/, '');
  					fileObject.type = (base64Encoded) ? 'base64' : 'utf-8';
  					
  					fs.writeFile(fileObject.path, data, fileObject.type, mxUtils.bind(this, function (e)
  				    {
  						this.spinner.stop();
  						
  						if (e)
  						{
  							this.handleError({message: mxResources.get('errorSavingFile')});
  						}
  		        	}));
  				}
  			}
  		}), 50);
  	};
  
  	EditorUi.prototype.addBeforeUnloadListener = function() {};
  	
  	EditorUi.prototype.loadDesktopLib = function(libPath, success, error)
  	{
  		this.readGraphFile(mxUtils.bind(this, function(fileEntry, data, stat)
  		{
  			var library = new DesktopLibrary(this, data, fileEntry);
  			this.loadLibrary(library);
  			success(library);
  		}), error, libPath);
  	};
  })();
  