Browse Source

13.7.0 release

David Benson [draw.io] 4 years ago
parent
commit
8113fff10d

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+14-SEP-2020: 13.7.0
+
+- Adds conf cloud change notifications for collab
+- Adds Cmd+Click for blue arrow on macOS
+- Uses mxGraph 4.2.1 beta 17
+
 11-SEP-2020: 13.6.10
 
 - Adds Shift+Delete/Backsapce to clear labels

+ 1 - 1
VERSION

@@ -1 +1 @@
-13.6.10
+13.7.0

+ 2 - 0
etc/build/build.xml

@@ -276,6 +276,7 @@
 				<file name="RemoteFile.js" />
 				<file name="RemoteLibrary.js" />
 				<file name="UrlLibrary.js" />
+				<file name="EmbedFile.js" />
 				<file name="Dialogs.js" />
 				<file name="Editor.js" />
 				<file name="EditorUi.js" />
@@ -359,6 +360,7 @@
 				<file name="RemoteFile.js" />
 				<file name="RemoteLibrary.js" />
 				<file name="UrlLibrary.js" />
+				<file name="EmbedFile.js" />
 				<file name="Dialogs.js" />
 				<file name="Editor.js" />
 				<file name="EditorUi.js" />

File diff suppressed because it is too large
+ 2 - 2
etc/mxgraph/mxClient.js


File diff suppressed because it is too large
+ 535 - 533
src/main/webapp/js/app.min.js


+ 32 - 1
src/main/webapp/js/diagramly/App.js

@@ -229,6 +229,11 @@ App.MODE_BROWSER = 'browser';
  */
 App.MODE_TRELLO = 'trello';
 
+/**
+ * Embed App Mode
+ */
+App.MODE_EMBED = 'embed';
+
 /**
  * Sets the delay for autosave in milliseconds. Default is 2000.
  */
@@ -613,7 +618,7 @@ App.main = function(callback, createUi)
 		
 		// Loads Pusher API
 		if (('ArrayBuffer' in window) && !mxClient.IS_CHROMEAPP && !EditorUi.isElectronApp &&
-			DrawioFile.SYNC == 'auto' && urlParams['embed'] != '1' && urlParams['local'] != '1' &&
+			DrawioFile.SYNC == 'auto' && (urlParams['embed'] != '1' || urlParams['embedRT'] == '1') && urlParams['local'] != '1' &&
 			(urlParams['chrome'] != '0' || urlParams['rt'] == '1') &&
 			urlParams['stealth'] != '1' && urlParams['offline'] != '1')
 		{
@@ -4698,6 +4703,32 @@ App.prototype.loadFile = function(id, sameWindow, file, success, force)
 					success();
 				}
 			}
+			else if (id.charAt(0) == 'E') // Embed file
+			{
+				//Currently we only reload current file. Id is not used!
+				var currentFile = this.getCurrentFile();
+				
+				if (currentFile == null)
+				{
+					this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'));
+				}
+				else
+				{
+					this.remoteInvoke('getDraftFileContent', null, null, mxUtils.bind(this, function(data, desc)
+					{
+						this.spinner.stop();
+						this.fileLoaded(new EmbedFile(this, data, desc));
+						
+						if (success != null)
+						{
+							success();
+						}
+					}), mxUtils.bind(this, function()
+					{
+						this.handleError({message: mxResources.get('serviceUnavailableOrBlocked')}, mxResources.get('errorLoadingFile'));
+					}));
+				}
+			}
 			else if (id.charAt(0) == 'U')
 			{
 				var url = decodeURIComponent(id.substring(1));

+ 1 - 0
src/main/webapp/js/diagramly/Devel.js

@@ -157,6 +157,7 @@ mxscript(drawDevUrl + 'js/diagramly/StorageFile.js');
 mxscript(drawDevUrl + 'js/diagramly/StorageLibrary.js');
 mxscript(drawDevUrl + 'js/diagramly/RemoteFile.js');
 mxscript(drawDevUrl + 'js/diagramly/RemoteLibrary.js');
+mxscript(drawDevUrl + 'js/diagramly/EmbedFile.js');
 mxscript(drawDevUrl + 'js/diagramly/Dialogs.js');
 mxscript(drawDevUrl + 'js/diagramly/Editor.js');
 mxscript(drawDevUrl + 'js/diagramly/EditorUi.js');

+ 5 - 1
src/main/webapp/js/diagramly/DiffSync.js

@@ -227,7 +227,11 @@ EditorUi.prototype.patchViewState = function(page, diff)
 
 		for (var key in diff)
 		{
-			page.viewState[key] = JSON.parse(diff[key]);
+			try
+			{
+				page.viewState[key] = JSON.parse(diff[key]);
+			}
+			catch(e) {} //Ignore TODO Is this correct, we encountered an undefined value for a key (extFonts)
 		}
 		
 		if (page == this.currentPage)

+ 6 - 0
src/main/webapp/js/diagramly/DrawioFileSync.js

@@ -305,6 +305,12 @@ DrawioFileSync.prototype.isConnected = function()
  */
 DrawioFileSync.prototype.updateOnlineState = function()
 {
+	//For RT in embeded mode, we don't need this icon
+	if (urlParams['embedRT'] == '1')
+	{
+		return;
+	}
+	
 	var addClickHandler = mxUtils.bind(this, function(elt)
 	{
 		mxEvent.addListener(elt, 'click', mxUtils.bind(this, function(evt)

+ 9 - 9
src/main/webapp/js/diagramly/EditorUi.js

@@ -10810,8 +10810,8 @@
 					}
 					
 					// Creates temporary file for diff sync in embed mode
-					this.setCurrentFile(new LocalFile(this, xml,
-						this.defaultFilename, true));
+					this.setCurrentFile(new EmbedFile(this, xml, {}));
+					this.mode = App.MODE_EMBED;
 					this.setFileData(xml);
 					
 					if (!this.editor.isChromelessView())
@@ -11568,10 +11568,8 @@
 						{
 							var msg = this.createLoadMessage('autosave');
 							msg.xml = data;
-							data = JSON.stringify(msg);
-							
 							var parent = window.opener || window.parent;
-							parent.postMessage(data, '*');
+							parent.postMessage(JSON.stringify(msg), '*');
 						}
 						
 						lastData = data;
@@ -11689,8 +11687,9 @@
 			{
 				if (urlParams['saveAndExit'] != '0')
 				{
-					mxUtils.write(button, mxResources.get('saveAndExit'));
-					button.setAttribute('title', mxResources.get('saveAndExit'));
+					var saveAndExitTitle = urlParams['publishClose'] == '1' ? mxResources.get('publish') : mxResources.get('saveAndExit');
+					mxUtils.write(button, saveAndExitTitle);
+					button.setAttribute('title', saveAndExitTitle);
 					
 					mxEvent.addListener(button, 'click', mxUtils.bind(this, function()
 					{
@@ -11733,8 +11732,9 @@
 			if (urlParams['noExitBtn'] != '1')
 			{
 				button = document.createElement('a');
-				mxUtils.write(button, mxResources.get('exit'));
-				button.setAttribute('title', mxResources.get('exit'));
+				var exitTitle = urlParams['publishClose'] == '1' ? mxResources.get('close') : mxResources.get('exit');
+				mxUtils.write(button, exitTitle);
+				button.setAttribute('title', exitTitle);
 				button.className = 'geBigButton geBigStandardButton';
 				button.style.marginLeft = '6px';
 				

+ 26 - 0
src/main/webapp/js/diagramly/EmbedFile.js

@@ -0,0 +1,26 @@
+/**
+ * Copyright (c) 2020, JGraph Ltd
+ * Copyright (c) 2020, Gaudenz Alder
+ */
+EmbedFile = function(ui, data, desc)
+{
+	DrawioFile.call(this, ui, data);
+	
+	this.desc = desc || {};
+	this.mode = App.MODE_EMBED;
+};
+
+//Extends DrawioFile
+mxUtils.extend(EmbedFile, DrawioFile);
+
+EmbedFile.prototype.getMode = function()
+{
+	return this.mode;
+};
+
+EmbedFile.prototype.getTitle = function()
+{
+	return this.desc.title || '';
+};
+
+//This class need to be implemented by integrations if some file features like real-time collaboration is needed

+ 2 - 2
src/main/webapp/js/diagramly/Extensions.js

@@ -6555,7 +6555,7 @@ LucidImporter = {};
 					var keyboard = null;
 					var statusBar = null;
 					
-					if (p.AndroidDeviceName == 'Tablet' || p.AndroidDeviceName == 'Mini Tablet')
+					if (p.AndroidDeviceName == 'Tablet' || p.AndroidDeviceName == 'Mini Tablet' ||  (p.AndroidDeviceName == 'custom' && p.CustomDeviceType == 'Tablet'))
 					{
 						v.style += "shape=mxgraph.android.tab2;"
 						background = new mxCell('', new mxGeometry(w * 0.112, h * 0.077, w * 0.77, h * 0.85), '');
@@ -6570,7 +6570,7 @@ LucidImporter = {};
 							statusBar = new mxCell('', new mxGeometry(w * 0.112, h * 0.077, w * 0.77, h * 0.03), 'shape=mxgraph.android.statusBar;strokeColor=#33b5e5;fillColor=#000000;fontColor=#33b5e5;fontSize=' + h * 0.015 + ';');
 						}
 					}
-					else if (p.AndroidDeviceName == 'Large Phone' || p.AndroidDeviceName == 'Phone')
+					else if (p.AndroidDeviceName == 'Large Phone' || p.AndroidDeviceName == 'Phone' ||  (p.AndroidDeviceName == 'custom' && p.CustomDeviceType == 'Phone'))
 					{
 						v.style += "shape=mxgraph.android.phone2;"
 						background = new mxCell('', new mxGeometry(w * 0.04, h * 0.092, w * 0.92, h * 0.816), '');

+ 4 - 2
src/main/webapp/js/diagramly/Menus.js

@@ -1962,17 +1962,19 @@
 				//Add support to saving files if embedded mode is running with files
 				var file = editorUi.getCurrentFile();
 				
-				if (file != null && (file.constructor != LocalFile || file.mode != null))
+				if (file != null && file.constructor != EmbedFile && (file.constructor != LocalFile || file.mode != null))
 				{
 					editorUi.saveFile();
 				}
 			};
 	
-			editorUi.actions.addAction('saveAndExit', function()
+			var saveAndExitAction = editorUi.actions.addAction('saveAndExit', function()
 			{
 				editorUi.actions.get('save').funct(true);
 			});
 			
+			saveAndExitAction.label = urlParams['publishClose'] == '1' ? mxResources.get('publish') : mxResources.get('saveAndExit');
+			
 			editorUi.actions.addAction('exit', function()
 			{
 				var fn = function()

File diff suppressed because it is too large
+ 28 - 28
src/main/webapp/js/extensions.min.js


+ 1 - 1
src/main/webapp/js/mxgraph/EditorUi.js

@@ -1206,7 +1206,7 @@ EditorUi.prototype.installShapePicker = function()
 		{
 			var evt = me.getEvent();
 			
-			if (!mxEvent.isControlDown(evt) && !mxEvent.isShiftDown(evt))
+			if (!this.graph.isCloneEvent(evt) && !mxEvent.isShiftDown(evt))
 			{
 				this.graph.connectVertex(state.cell, dir, this.graph.defaultEdgeLength, evt, null, null, mxUtils.bind(this, function(x, y, execute)
 				{

+ 15 - 10
src/main/webapp/js/mxgraph/Graph.js

@@ -4130,7 +4130,7 @@ HoverIcons.prototype.init = function()
 HoverIcons.prototype.isResetEvent = function(evt, allowShift)
 {
 	return mxEvent.isAltDown(evt) || (this.activeArrow == null && mxEvent.isShiftDown(evt)) ||
-		mxEvent.isMetaDown(evt) || (mxEvent.isPopupTrigger(evt) && !mxEvent.isControlDown(evt));
+		(mxEvent.isPopupTrigger(evt) && !this.graph.isCloneEvent(evt));
 };
 
 /**
@@ -4357,8 +4357,8 @@ HoverIcons.prototype.click = function(state, dir, me)
 	var y = me.getGraphY();
 	
 	var tmp = this.getStateAt(state, x, y);
-	
-	if (tmp != null && this.graph.model.isEdge(tmp.cell) && !mxEvent.isControlDown(evt) &&
+
+	if (tmp != null && this.graph.model.isEdge(tmp.cell) && !this.graph.isCloneEvent(evt) &&
 		(tmp.getVisibleTerminalState(true) == state || tmp.getVisibleTerminalState(false) == state))
 	{
 		this.graph.setSelectionCell(tmp.cell);
@@ -4380,8 +4380,8 @@ HoverIcons.prototype.execute = function(state, dir, me)
 	var evt = me.getEvent();
 
 	this.graph.selectCellsForConnectVertex(this.graph.connectVertex(
-		state.cell, dir, this.graph.defaultEdgeLength, evt, mxEvent.isControlDown(evt),
-		mxEvent.isControlDown(evt)), evt, this);
+		state.cell, dir, this.graph.defaultEdgeLength, evt, this.graph.isCloneEvent(evt),
+		this.graph.isCloneEvent(evt)), evt, this);
 };
 
 /**
@@ -6368,12 +6368,17 @@ if (typeof mxVertexHandler != 'undefined')
 					// Merges into unlocked current layer if one layer is pasted
 					if (layers.length == 1 && !this.isCellLocked(this.getDefaultParent()))
 					{
-						cells = this.moveCells(tempModel.getChildren(layers[0]),
-							dx, dy, false, this.getDefaultParent());
+						var children = tempModel.getChildren(layers[0]);
 						
-						// Imported default parent maps to local default parent
-						cellMapping[tempModel.getChildAt(tempModel.root, 0).getId()] =
-							this.getDefaultParent().getId();
+						if (children != null)
+						{
+							cells = this.moveCells(children,
+								dx, dy, false, this.getDefaultParent());
+							
+							// Imported default parent maps to local default parent
+							cellMapping[tempModel.getChildAt(tempModel.root, 0).getId()] =
+								this.getDefaultParent().getId();
+						}
 					}
 					else
 					{

File diff suppressed because it is too large
+ 70 - 70
src/main/webapp/js/viewer-static.min.js


File diff suppressed because it is too large
+ 70 - 70
src/main/webapp/js/viewer.min.js


+ 684 - 10
src/main/webapp/plugins/cConf-1-4-8.js

@@ -45,6 +45,44 @@ Draw.loadPlugin(function(ui)
 		return macroData.diagramDisplayName != null;
 	}
 	
+	function descriptorChangedListener()
+	{
+		var curFile = ui.getCurrentFile();
+		var fileTitle = curFile.getTitle();
+		
+		//Update file name in the UI
+		var tmp = document.createElement('span');
+		mxUtils.write(tmp, mxUtils.htmlEntities(fileTitle));
+		
+		if (ui.embedFilenameSpan != null)
+		{
+			ui.embedFilenameSpan.parentNode.removeChild(ui.embedFilenameSpan);
+		}
+
+		ui.buttonContainer.appendChild(tmp);
+		ui.embedFilenameSpan = tmp;
+		macroData.diagramDisplayName = fileTitle;
+		
+		var vSettings = curFile.desc.viewerSettings;
+		
+		if (vSettings != null)
+		{
+			macroData.tbstyle = vSettings.tbstyle;
+			macroData.links = vSettings.links;
+			macroData.simple = vSettings.simple;
+			macroData.lbox = vSettings.lbox;
+			macroData.zoom = vSettings.zoom;
+			macroData.pCenter = vSettings.pCenter;
+			macroData.aspect =	vSettings.aspect;
+			macroData.hiResPreview = vSettings.hiResPreview;
+			
+			if (ui.format != null)
+			{
+				ui.format.refresh();
+			}
+		}
+	};
+
 	renameAction.funct = function()
 	{
 		var dlg = new FilenameDialog(ui, macroData.diagramDisplayName || "",
@@ -52,20 +90,13 @@ Draw.loadPlugin(function(ui)
 		{
 			if (newName != null && newName.length > 0)
 			{
+				//TODO This is not needed with RT since title is added to desc
 				macroData.diagramDisplayName = newName;
 				var parent = window.opener || window.parent;
 				parent.postMessage(JSON.stringify({event: 'rename', name: newName}), '*'); 
-				//Update file name in the UI
-				var tmp = document.createElement('span');
-				mxUtils.write(tmp, mxUtils.htmlEntities(newName));
 				
-				if (ui.embedFilenameSpan != null)
-				{
-					ui.embedFilenameSpan.parentNode.removeChild(ui.embedFilenameSpan);
-				}
-
-				ui.buttonContainer.appendChild(tmp);
-				ui.embedFilenameSpan = tmp;
+				//Update and sync new name
+				ui.getCurrentFile().rename(newName);
 			}
 		}, mxResources.get('rename'), function(name)
 		{
@@ -100,6 +131,30 @@ Draw.loadPlugin(function(ui)
 		if (eventName == 'export')
 		{
 			msg.macroData = macroData;
+			
+			var desc = ui.getCurrentFile().getDescriptor();
+			
+			//Until app.min.js is propagated, this code is necessary
+			if (desc != null)
+			{
+				if (desc.key == null)
+				{
+					desc.key = Editor.guid(32);
+					desc.channel = Editor.guid(32);
+					desc.etagP = Editor.guid(32);
+					desc.title = macroData.diagramDisplayName;
+				}
+				else if (desc.title)
+				{
+					macroData.diagramDisplayName = desc.title;
+				}
+				
+				msg.desc = desc;
+			}
+			else
+			{
+				msg.desc = {};
+			}
 		}
 
 		return msg;
@@ -604,4 +659,623 @@ Draw.loadPlugin(function(ui)
 			ui.openLink('https://about.draw.io/support/');
 		});
 	});
+
+	//=============Embed File with real-time collab support (based on remote invocation)
+	//Until app.min.js is propagated, this code is necessary
+	if (typeof EmbedFile === 'undefined')
+	{
+		var origInstallMessageHandler = ui.installMessageHandler;
+		
+		ui.installMessageHandler = function()
+		{
+			var parent = window.opener || window.parent;
+			parent.postMessage(JSON.stringify({event: 'disableRT'}), '*');
+			
+			origInstallMessageHandler.apply(this, arguments);
+		}
+		
+		return;	
+	}
+	
+	/**
+	 * Workaround for changing etag after save is higher autosave delay to allow
+	 * for preflight etag update and decrease possible conflicts on file save.
+	 */
+	EmbedFile.prototype.autosaveDelay = 2500;
+
+	/**
+	 * Delay for last save in ms.
+	 */
+	EmbedFile.prototype.saveDelay = 0;
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.isConflict = function(err)
+	{
+		return err != null && err.status == 409;
+	};
+
+	/**
+	 * Returns the current user.
+	 */
+	EmbedFile.prototype.getCurrentUser = function()
+	{
+		return ui.getCurrentUser();
+	};
+
+	/**
+	 * Returns true if an autosave is required at the time of execution.
+	 */
+	EmbedFile.prototype.isAutosave = function()
+	{
+		return this.desc.id != null;
+	};
+
+	/**
+	 * Specifies if the autosave checkbox should be shown in the document
+	 * properties dialog. Default is false.
+	 */
+	EmbedFile.prototype.isAutosaveOptional = function()
+	{
+		return this.desc.id == null;
+	};
+	
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.isRenamable = function()
+	{
+		return this.isEditable() && DrawioFile.prototype.isEditable.apply(this, arguments);
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.save = function(revision, success, error, unloading, overwrite)
+	{
+		this.saveStarted = true;
+		
+		DrawioFile.prototype.save.apply(this, [revision, mxUtils.bind(this, function()
+		{
+			this.saveFile(null, revision, success, error, unloading, overwrite);
+			this.saveStarted = false;
+		}), error, unloading, overwrite]);
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.setModified = function(value)
+	{
+		DrawioFile.prototype.setModified.apply(this, arguments);
+		
+		//Set editor modified also to prevent accidental closure or exiting without saving  
+		ui.editor.modified = value;
+	};
+	
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.saveFile = function(title, revision, success, error, unloading, overwrite)
+	{
+		try
+		{
+			if (!this.isEditable())
+			{
+				if (success != null)
+				{
+					success();
+				}
+			}
+			else if (!this.savingFile)
+			{
+				// Sets shadow modified state during save
+				this.savingFileTime = new Date();
+				this.setShadowModified(false);
+				this.savingFile = true;
+
+				this.createSecret(mxUtils.bind(this, function(secret, token)
+				{
+					var doSave = mxUtils.bind(this, function(realOverwrite, realRevision)
+					{
+						try
+						{
+							var lastDesc = this.desc;
+							var savedData = this.getData();
+							this.desc.secret = secret;
+							this.desc.key = this.desc.key? this.desc.key : Editor.guid(32);
+							this.desc.channel = this.desc.channel? this.desc.channel : Editor.guid(32);
+							this.desc.etagP = this.desc.etagP? this.desc.etagP : Editor.guid(32);
+							this.desc.title = this.desc.title? this.desc.title : macroData.diagramDisplayName;
+							
+							ui.remoteInvoke('saveDraftWithFileDesc', [savedData, this.desc], null, mxUtils.bind(this, function(resp)
+							{
+								try
+								{
+									this.savingFile = false;
+									
+									// Handles special case where resp is false eg
+									// if the old file was converted to realtime
+									if (resp != false)
+									{
+										// Checks for changes during save
+										this.setModified(this.getShadowModified());
+										
+										if (revision)
+										{
+											this.lastAutosaveRevision = new Date().getTime();
+										}
+					
+										// Adaptive autosave delay
+										this.autosaveDelay = Math.min(8000,
+											Math.max(this.saveDelay + 500,
+											EmbedFile.prototype.autosaveDelay));
+										this.desc = resp;
+										
+										// Shows possible errors but keeps the modified flag as the
+										// file was saved but the cache entry could not be written
+										if (token != null)
+										{
+											this.fileSaved(savedData, lastDesc, mxUtils.bind(this, function()
+											{
+												this.contentChanged();
+												
+												if (success != null)
+												{
+													success(resp);
+												}
+											}), error, token);
+										}
+										else
+										{
+											success(resp);
+										}
+									}
+									else if (error != null)
+									{
+										error(resp);
+									}
+								}
+								catch (e)
+								{
+									this.savingFile = false;
+									
+									if (error != null)
+									{
+										error(e);
+									}
+									else
+									{
+										throw e;
+									}
+								}
+							}),
+							mxUtils.bind(this, function(err, desc)
+							{
+								//TODO EMBED desc is null here 
+								try
+								{
+									this.savingFile = false;
+								
+									if (this.isConflict(err))
+									{
+										this.inConflictState = true;
+										
+										if (this.sync != null)
+										{
+											this.savingFile = true;
+											
+											this.sync.fileConflict(desc, mxUtils.bind(this, function()
+											{
+												// Adds random cool-off
+												window.setTimeout(mxUtils.bind(this, function()
+												{
+													this.updateFileData();
+													this.setShadowModified(false);
+													doSave(realOverwrite, true);
+												}), 100 + Math.random() * 500 + (err.isLocked? 500 : 0));
+											}), mxUtils.bind(this, function()
+											{
+												this.savingFile = false;
+												
+												if (error != null)
+												{
+													error();
+												}
+											}));
+										}
+										else if (error != null)
+										{
+											error();
+										}
+									}
+									else if (error != null)
+									{
+										error(err);
+									}
+								}
+								catch (e)
+								{
+									this.savingFile = false;
+									
+									if (error != null)
+									{
+										error(e);
+									}
+									else
+									{
+										throw e;
+									}
+								}
+							}));
+						}
+						catch (e)
+						{
+							this.savingFile = false;
+							
+							if (error != null)
+							{
+								error(e);
+							}
+							else
+							{
+								throw e;
+							}
+						}
+					});
+					
+					doSave(overwrite, revision);				
+				}));
+			}
+		}
+		catch (e)
+		{
+			if (error != null)
+			{
+				error(e);
+			}
+			else
+			{
+				throw e;
+			}
+		}
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.copyFile = function(success, error)
+	{
+		//Download a copy of the file since it is difficult to add a copy to current confluence page
+		this.updateFileData();
+		ui.doSaveLocalFile(this.data, this.getTitle(), 'text/xml');
+		error(); //Since the problem is not fixed //TODO Confirm this is OK??
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.rename = function(title, success, error)
+	{
+		var etag = this.getCurrentEtag();
+		this.desc.title = title;
+		
+		ui.remoteInvoke('setFileDescriptor', [this.desc], null, mxUtils.bind(this, function(desc)
+		{
+			this.desc = desc;
+			this.descriptorChanged();
+			
+			if (this.sync != null)
+			{
+				this.sync.descriptorChanged(etag);
+			}
+			
+			if (success != null)
+			{
+				success(desc);
+			}
+		}), error);
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getTitle = function()
+	{
+		return this.desc.title || macroData.diagramDisplayName;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getHash = function()
+	{
+		return 'E' + this.getId();
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getId = function()
+	{
+		return this.desc.id;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.isSyncSupported = function()
+	{
+		return this.desc.id != null;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.isRevisionHistorySupported = function()
+	{
+		return true;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getLatestVersion = function(success, error)
+	{
+		ui.remoteInvoke('getDraftFileContent', null, null, mxUtils.bind(this, function(data, desc)
+		{
+			success(new EmbedFile(ui, data, desc));
+		}), error);
+	};
+
+	/**
+	 * Gets the channel ID from the given descriptor.
+	 */
+	EmbedFile.prototype.getChannelId = function()
+	{
+		var chan = this.desc.channel;
+		
+		if (chan != null)
+		{
+			chan = 'E-' + this.getId() + '.' + chan;
+		}
+		
+		return chan;
+	};
+
+	/**
+	 * Gets the channel key from the given descriptor.
+	 */
+	EmbedFile.prototype.getChannelKey = function()
+	{
+		return this.desc.key;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getLastModifiedDate = function()
+	{
+		return new Date(this.desc.modifiedDate);
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getDescriptor = function()
+	{
+		return this.desc;
+	};
+
+	/**
+	* Updates the descriptor of this file with the one from the given file.
+	*/
+	EmbedFile.prototype.setDescriptor = function(desc)
+	{
+		this.desc = desc;
+	};
+
+	/**
+	 * Returns the secret from the given descriptor.
+	 */
+	EmbedFile.prototype.getDescriptorSecret = function(desc)
+	{
+		return desc.secret;
+	};
+
+	/**
+	 * Updates the revision ID on the given descriptor.
+	 */
+	EmbedFile.prototype.setDescriptorRevisionId = function(desc, id)
+	{
+		desc.headRevisionId = id;
+	};
+
+	/**
+	 * Returns the revision ID from the given descriptor.
+	 */
+	EmbedFile.prototype.getDescriptorRevisionId = function(desc)
+	{
+		return desc.headRevisionId;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.getDescriptorEtag = function(desc)
+	{
+		return desc.etag;
+	};
+
+	/**
+	 *
+	 */
+	EmbedFile.prototype.setDescriptorEtag = function(desc, etag)
+	{
+		desc.etag = etag;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.patchDescriptor = function(desc, patch)
+	{
+		DrawioFile.prototype.patchDescriptor.apply(this, arguments);
+		
+		desc.headRevisionId = patch.headRevisionId;
+		desc.modifiedDate = patch.modifiedDate;
+	};
+
+	/**
+	 * 
+	 */
+	EmbedFile.prototype.loadDescriptor = function(success, error)
+	{
+		ui.remoteInvoke('getFileDescriptor', null, null, success, error);
+	};
+	
+	var allowAutoSave = true;
+	
+	EmbedFile.prototype.isAutosaveNow = function(success, error)
+	{
+		return allowAutoSave;
+	};
+	
+	//Ensure saving of draft before publishing
+	var origSaveAction = ui.actions.get('save').funct;
+	
+	ui.actions.get('save').funct = function(exit)
+	{
+		var actArgs = arguments;
+		var curFile = ui.getCurrentFile();
+		var desc = curFile.getDescriptor();
+
+		if (exit)
+		{
+			//Prevent stpping the spinner early by creating our own spinner
+			var spinner = new Spinner({
+				lines: 12, // The number of lines to draw
+				length: 24, // The length of each line
+				width: 8, // The line thickness
+				radius: 12, // The radius of the inner circle
+				rotate: 0, // The rotation offset
+				color: '#000', // #rgb or #rrggbb
+				speed: 1.5, // Rounds per second
+				trail: 60, // Afterglow percentage
+				shadow: false, // Whether to render a shadow
+				hwaccel: false, // Whether to use hardware acceleration
+				zIndex: 2e9 // The z-index (defaults to 2000000000)
+			});
+	
+			spinner.spin(document.body);
+			allowAutoSave = false;
+			
+			if (desc != null)
+			{
+				desc.viewerSettings = {
+					tbstyle: macroData.tbstyle,
+					links: macroData.links,
+					simple: macroData.simple,
+					lbox: macroData.lbox,
+					zoom: macroData.zoom,
+					pCenter: macroData.pCenter,
+					aspect:	macroData.aspect,
+					hiResPreview: macroData.hiResPreview
+				};
+			}
+			
+			var etag = curFile.getCurrentEtag();
+		}
+
+		function doActions()
+		{
+			origSaveAction.apply(ui, actArgs);
+			
+			if (exit && curFile.sync != null)
+			{
+				curFile.sync.descriptorChanged(etag);
+			}
+		};
+		
+		function doSave()
+		{
+			if (curFile.saveStarted || curFile.savingFile)
+			{
+				setTimeout(doSave, 100);
+				return;
+			}
+			
+			if (curFile.isModified())
+			{
+				//Save file (draft) first
+				ui.saveFile(null, doActions);
+			}
+			else if (exit) //Save descriptor only to update the viewer settings
+			{
+				ui.remoteInvoke('setFileDescriptor', [desc], null, doActions, doActions);
+			}
+			else
+			{
+				doActions();
+			}
+		};
+		
+		if (desc == null || desc.key == null)
+		{
+			//New files are saved directly and descriptor is added during publishing after creating the custom content
+			doActions();
+		}
+		else
+		{
+			doSave();
+		}
+	};
+	
+	//Add file opening here (or it should be for all in EditorUi?)
+	var origInstallMessageHandler =  ui.installMessageHandler;
+	
+	ui.installMessageHandler = function(callback)
+	{
+		origInstallMessageHandler.call(this, function()
+		{
+			callback.apply(this, arguments);
+			
+			var file = ui.getCurrentFile();
+			
+			file.loadDescriptor(function(desc)
+			{
+				file.desc = desc;
+				ui.fileLoaded(file, true);
+				
+				if (file.desc)
+				{
+					var descChangedNeeded = false;
+					
+					if (file.desc.title && file.desc.title != macroData.diagramDisplayName)
+					{
+						macroData.diagramDisplayName = file.desc.title;
+						descChangedNeeded = true;
+					}
+					
+					if (file.desc.viewerSettings != null)
+					{
+						descChangedNeeded = true;
+					}
+					
+					if (descChangedNeeded)
+					{
+						descriptorChangedListener();
+					}
+				}
+			});
+			
+			file.addListener('descriptorChanged', descriptorChangedListener);
+		});
+	}
+	
+	ui.editor.setModified = function()
+	{
+		//Cancel set modified of the editor and use the file's one
+	};
 });

+ 3 - 3
src/main/webapp/service-worker.js

@@ -6,11 +6,11 @@ if (workbox)
 	workbox.precaching.precacheAndRoute([
   {
     "url": "js/app.min.js",
-    "revision": "87323179705982af08a96565b107e618"
+    "revision": "08b997f0e7d5f8c5d354a80078d2a27f"
   },
   {
     "url": "js/extensions.min.js",
-    "revision": "86201a81db0f64044e2aa1b96008fb6a"
+    "revision": "b062463af07ad2be2d6d413059358317"
   },
   {
     "url": "js/shapes.min.js",
@@ -34,7 +34,7 @@ if (workbox)
   },
   {
     "url": "styles/atlas.css",
-    "revision": "40f54334c7a62821dbf1f7c7d8ad62cc"
+    "revision": "07ff03bf40985d2740d142012b24c7d1"
   },
   {
     "url": "styles/dark.css",

+ 1 - 1
src/main/webapp/styles/atlas.css

@@ -136,7 +136,7 @@ html body .geMenubarContainer .geStatus {
 	color: rgb(179, 179, 179);
 }
 .geMenubarContainer .geItem:hover {
-	background-color: rgba(9, 30, 66, 0.48);
+	background-color: rgba(9, 30, 66, 0.48) !important;
 }
 html body .geToolbarContainer .geLabel {
 	margin:0px;