Browse Source

9.4.1 release

Gaudenz Alder 6 years ago
parent
commit
ae98ad5856

+ 10 - 0
ChangeLog

@@ -1,3 +1,13 @@
+09-NOV-2018: 9.4.1
+
+- Fixes links to revision history from status
+- Hides static page text in embed mode
+- Enables import from device on iOS
+
+08-NOV-2018: 9.4.0
+
+- Disables Google realtime on 11/12/2018 at 9:00am (UTC)
+
 06-NOV-2018: 9.3.4
 
 - Improves sanity check for Google Drive files

+ 1 - 1
VERSION

@@ -1 +1 @@
-9.3.4
+9.4.1

+ 1 - 1
src/main/webapp/cache.manifest

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 11/06/2018 04:33 PM
+# 11/09/2018 01:00 PM
 
 app.html
 index.html?offline=1

+ 1 - 113
src/main/webapp/index.html

@@ -410,119 +410,7 @@
 /**
  * Main
  */
-App.main(function (ui)
-{
-	// **************************************************
-	// TESTING JSON TO XML CONVERTER FOR EOL OF GOOGLE RT
-	// **************************************************
-// 	ui.JSON_CHECK = 'JSON-CHECK7';
-	
-// 	DriveFile.prototype.runCheck = function(json, fileNode, fileData)
-// 	{
-// 		try
-// 		{
-// 			this.debug('JSON Check', this.desc, json);
-			
-// 			if (this.ui.getCurrentFile() == this && !this.isModified())
-// 			{
-// 				// Gets comparable XML structures
-// 				var convert = (json != null) ? this.getComparableFile(this.ui.drive.convertJsonToXml(json, true)) : null;
-// 				var remote = (fileNode != null) ? this.getComparableFile(fileNode) : null;
-// 				var local = this.getComparableFile(this.ui.getXmlFileData());
-				
-// 				// Uses the newer of the two
-// 				var age = this.ui.drive.getRealtimeAge(this.desc, json);
-// 				var relevant = (age < 0 && convert != null) ? convert : remote;
-				
-// 				if (local == null || relevant == null || !local.isEqualNode(relevant))
-// 				{
-// 					this.log('FAIL-REPORT');
-					
-// 					var summary = ((relevant == convert) ? 'Source: Convert' : 'Source: Remote') +
-// 						'\nFile modified: ' + new Date(this.desc.modifiedDate).toUTCString();
-					
-// 					if (json != null)
-// 					{
-// 						var mod = (json.value != null && json.value.modifiedDate != null) ? json.value.modifiedDate.json : null;
-						
-// 						if (mod != null)
-// 						{
-// 							summary += '\nJson modified: ' + new Date(mod).toUTCString();	
-// 						}
-						
-// 						var bak = (json.value != null && json.value.backupDate != null) ? json.value.backupDate.json : null;
-						
-// 						if (bak != null)
-// 						{
-// 							summary += '\nJson backedup: ' + new Date(bak).toUTCString();
-// 						}
-// 					}
-					
-// 					if (local != null)
-// 					{
-// 						if (convert != null)
-// 						{
-// 							summary += '\nConvert-Equals-Local: ' + convert.isEqualNode(local);
-// 						}
-						
-// 						if (remote != null)
-// 						{
-// 							summary += '\nRemote-Equals-Local: ' + remote.isEqualNode(local);
-// 						}
-// 					}
-					
-// 					this.report('Realtime Error Report ' + new Date() +
-// 						'\n\nDescription: ' + JSON.stringify({version: this.ui.JSON_CHECK,
-// 							realtimeAge: age, title: this.desc.title, editable: this.desc.editable,
-// 							copyable: this.desc.copyable, labels: this.desc.labels, id: this.desc.id,
-// 							userPermission: this.desc.userPermission, fileSize: this.desc.fileSize,
-// 							fileExtension: this.desc.fileExtension, modifiedDate: this.desc.modifiedDate,
-// 							mimeType: this.desc.mimeType}) +
-// 						'\n\nSummary:\n' + summary +
-// 						'\n\nLocal:\n' + this.getAnonymizedXml(local) +
-// 						'\n\nRemote:\n' + this.getAnonymizedXml(remote) +
-// 						((remote == null) ? ('\n\nData:\n' + fileData) : '') +
-// 						'\n\nConvert:\n' + this.getAnonymizedXml(convert) +
-// 						'\n\nJSON:\n' + this.getAnonymizedJson(json));
-// 				}
-// 				else
-// 				{
-// 					this.log('OK-age.' + age + '.');
-// 				}
-// 			}
-// 		}
-// 		catch (e)
-// 		{
-// 			this.log('CATCH-RUNCHECK-' + e.stack, true);
-// 			this.debug(e);
-// 		}
-// 	};
-	
-// 	ui.editor.addListener('fileLoaded', mxUtils.bind(this, function()
-// 	{
-// 		try
-// 		{
-// 			var file = ui.getCurrentFile();
-			
-// 			if (file != null && file.constructor == DriveFile && file.realtime != null)
-// 			{
-// 				// With a 30% probability
-// 				if (urlParams['json-check'] != '0' && (urlParams['json-check'] == '1' ||
-// 					(Math.random() > 0.3 && urlParams['dev'] != '1')))
-// 				{
-// 					file.checkConvert();
-// 				}
-// 			}
-// 		}
-// 		catch (e)
-// 		{
-// 			// ignore
-// 		}
-// 	}));
-	// ***********
-	// END OF TEST
-	// ***********
-});
+App.main();
 </script>
 </body>
 </html>

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


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


File diff suppressed because it is too large
+ 800 - 799
src/main/webapp/js/atlas.min.js


+ 130 - 43
src/main/webapp/js/diagramly/App.js

@@ -187,14 +187,16 @@ App.TRELLO_JQUERY_URL = 'https://code.jquery.com/jquery-1.7.1.min.js';
 App.FOOTER_PLUGIN_URL = 'https://www.jgraph.com/drawio-footer.js';
 
 /**
- * Switch to disable Google realtime. If true this will convert existing realtime files.
+ * Switch to disable Google realtime starting on 11/12/2018 at 9:00am (UTC).
  */
-App.GOOGLE_REALTIME = urlParams['google-realtime'] != '0';
+App.GOOGLE_REALTIME = urlParams['google-realtime'] != '0' && new Date().getTime() < 1542013200000;
 
 /**
- * Switch to disable Google realtime. If true this will convert existing realtime files.
+ * Google APIs to load. The realtime API is needed to notify collaborators of conversion
+ * of the realtime files, but after Dec 11 it's read-only and hence no longer needed.
  */
-App.GOOGLE_APIS = 'client,drive-share' + ((App.GOOGLE_REALTIME) ? ',drive-realtime' : ''); 
+App.GOOGLE_APIS = 'client,drive-share,drive-realtime';
+//App.GOOGLE_APIS = 'client,drive-share' + ((App.GOOGLE_REALTIME) ? ',drive-realtime' : ''); 
 
 /**
  * Defines plugin IDs for loading via p URL parameter. Update the table at
@@ -453,6 +455,19 @@ App.main = function(callback, createUi)
 		};
 	}
 	
+	/**
+	 * Removes info text in embed mode
+	 */
+	if (urlParams['embed'] == '1')
+	{
+		var geInfo = document.getElementById('geInfo');
+		
+		if (geInfo != null)
+		{
+			geInfo.parentNode.removeChild(geInfo);
+		}
+	}
+	
 	if (window.mxscript != null)
 	{
 		/**
@@ -1158,40 +1173,44 @@ App.prototype.checkLicense = function()
 			domain = email.substring(at + 1);
 			email = this.crc32(email.substring(0, at)) + '@' + domain;
 		}
-		
+
 		// Timestamp is workaround for cached response in certain environments
 		mxUtils.post('/license', 'domain=' + encodeURIComponent(domain) + '&email=' + encodeURIComponent(email) + 
 				'&ds=' + encodeURIComponent(driveUser.displayName) + '&lc=' + encodeURIComponent(driveUser.locale) + 
 				'&ts=' + new Date().getTime(),
 			mxUtils.bind(this, function(req)
 			{
-				var registered = false;
-				var exp = null;
-				
-				try
-				{
-					if (req.getStatus() >= 200 && req.getStatus() <= 299)
-					{
-						var value = req.getText();
-						registered = true;
-						
-						if (value.length > 0)
-						{
-							var lic = JSON.parse(value);
-							
-							if (lic != null)
-							{
-								exp = this.handleLicense(lic, domain);
-							}
-						}
-					}
-				}
-				catch (e)
-				{
-					// ignore
-				}
+// NOTE: RESPONSE IS IGNORED TO SHOW TEMPORARY WARNING NOTICE BELOW!
+//				var registered = false;
+//				var exp = null;
+//				
+//				try
+//				{
+//					if (req.getStatus() >= 200 && req.getStatus() <= 299)
+//					{
+//						var value = req.getText();
+//						registered = true;
+//						
+//						if (value.length > 0)
+//						{
+//							var lic = JSON.parse(value);
+//							
+//							if (lic != null)
+//							{
+//								exp = this.handleLicense(lic, domain);
+//							}
+//						}
+//					}
+//				}
+//				catch (e)
+//				{
+//					// ignore
+//				}
 			}));
 	}
+	
+	// NOTE: RESPONSE ABOVE IS IGNORED TO SHOW TEMPORARY WARNING NOTICE!
+	this.showFooterRealtimeNotice();
 };
 
 /**
@@ -1252,6 +1271,50 @@ App.prototype.handleLicense = function(lic, domain)
 	return expiry;
 };
 
+/**
+ * Returns true if the current domain is for the new drive app.
+ */
+App.prototype.showFooterRealtimeNotice = function()
+{
+	var footer = document.getElementById('geFooter');
+	
+	if (footer != null)
+	{
+		var alert = this.createRealtimeNotice();
+		alert.style.zIndex = '1';
+		alert.style.padding = '18px 0 14px 0';
+		alert.style.width = 'auto';
+		alert.style.top = '0px';
+		alert.style.left = '0px';
+		alert.style.right = '170px';
+
+		footer.appendChild(alert);
+	}
+};
+
+/**
+ * Returns true if the current domain is for the new drive app.
+ */
+App.prototype.createRealtimeNotice = function()
+{
+	var alert = document.createElement('a');
+	alert.className = 'geStatusAlert';
+	alert.style.display = 'block';
+	alert.style.position = 'absolute';
+	alert.style.overflow = 'hidden';
+	alert.style.cursor = 'pointer';
+	alert.style.bottom = '0';
+	alert.style.textAlign = 'center';
+	alert.style.textDecoration = 'none';
+	alert.style.fontWeight = 'bold';
+	
+	alert.setAttribute('href', 'https://desk.draw.io/support/solutions/articles/16000087215');
+	alert.setAttribute('target', '_blank');
+	mxUtils.write(alert, mxResources.get('collaborativeEditingNotice'));
+	
+	return alert;
+};
+
 /**
  * 
  */
@@ -2024,14 +2087,29 @@ App.prototype.showRefreshDialog = function(title, message)
 	{
 		this.showingRefreshDialog = true;
 
-		this.showError(title || mxResources.get('error'),
+		this.showError(title || mxResources.get('externalChanges'),
 			message || mxResources.get('redirectToNewApp'),
 			mxResources.get('refresh'), mxUtils.bind(this, function()
 		{
 			this.spinner.spin(document.body, mxResources.get('connecting'));
 			this.editor.graph.setEnabled(false);
 			window.location.reload();
-		}));
+		}), null, null, null, null, null, 340, 180);
+		
+		// Adds important notice to dialog
+		if (this.dialog != null && this.dialog.container != null)
+		{
+			var alert = this.createRealtimeNotice();
+			alert.style.left = '0';
+			alert.style.right = '0';
+			alert.style.borderRadius = '0';
+			alert.style.borderLeftStyle = 'none';
+			alert.style.borderRightStyle = 'none';
+			alert.style.marginBottom = '26px';
+			alert.style.padding = '8px 0 8px 0';
+
+			this.dialog.container.appendChild(alert);
+		}
 	}
 };
 
@@ -2968,7 +3046,7 @@ App.prototype.saveLibrary = function(name, images, file, mode, noSpin, noReload,
 /**
  * Adds the label menu items to the given menu and parent.
  */
-App.prototype.saveFile = function(forceDialog)
+App.prototype.saveFile = function(forceDialog, success)
 {
 	var file = this.getCurrentFile();
 	
@@ -2979,15 +3057,23 @@ App.prototype.saveFile = function(forceDialog)
 		{
 			this.removeDraft();
 			
-			// Workaround for possible status update while save as dialog is showing
-			// is to show no saved status for device files
-			if (file.getMode() != App.MODE_DEVICE)
+			if (this.getCurrentFile() != file && !file.isModified())
 			{
-				this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
+				// Workaround for possible status update while save as dialog is showing
+				// is to show no saved status for device files
+				if (file.getMode() != App.MODE_DEVICE)
+				{
+					this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
+				}
+				else
+				{
+					this.editor.setStatus('');
+				}
 			}
-			else
+			
+			if (success != null)
 			{
-				this.editor.setStatus('');
+				success();
 			}
 		});
 		
@@ -3351,7 +3437,7 @@ App.prototype.fileCreated = function(file, libs, replace, done)
 				
 				if (replace)
 				{
-					this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
+					file.addAllSavedStatus();
 				}
 				
 				if (libs != null)
@@ -3407,7 +3493,8 @@ App.prototype.fileCreated = function(file, libs, replace, done)
 		
 		// Updates data in memory for local files and save is implicit
 		// via start of realtime for DriveFiles
-		if (file.constructor == LocalFile || file.constructor == DriveFile)
+		if (file.constructor == LocalFile || (file.constructor == DriveFile &&
+			file.realtime != null))
 		{
 			fn();
 		}
@@ -4097,7 +4184,7 @@ App.prototype.save = function(name, done)
 	var file = this.getCurrentFile();
 	var msg = mxResources.get('saving');
 	
-	if (file != null && file.constructor == DriveFile)
+	if (file != null && file.constructor == DriveFile && file.realtime != null)
 	{
 		msg = mxResources.get('createRevision');
 	}
@@ -4126,7 +4213,7 @@ App.prototype.save = function(name, done)
 				}
 				else
 				{
-					this.editor.setStatus(mxUtils.htmlEntities(mxResources.get('allChangesSaved')));
+					file.addAllSavedStatus();
 				}
 			}
 			

+ 18 - 5
src/main/webapp/js/diagramly/Dialogs.js

@@ -2413,6 +2413,10 @@ var ParseDialog = function(editorUi, title, defaultType)
 					layout.disableEdgeStyle = false;
 					layout.forceConstant = 120;
 					layout.execute(graph.getDefaultParent());
+					
+					var edgeLayout = new mxParallelEdgeLayout(graph);
+					edgeLayout.spacing = 20;
+					edgeLayout.execute(graph.getDefaultParent());
 				}
 				finally
 				{
@@ -3104,8 +3108,8 @@ var NewDialog = function(editorUi, compact, showName, callback, createOnly, canc
 		}
 		else
 		{
-			elt.innerHTML = '<table width="100%" height="100%"><tr><td align="center" valign="middle">' +
-				mxResources.get(title) + '</td></tr></table>';
+			elt.innerHTML = '<table width="100%" height="100%" style="line-height:1em;"><tr>' +
+				'<td align="center" valign="middle">' + mxResources.get(title) + '</td></tr></table>';
 			
 			if (select)
 			{
@@ -5255,7 +5259,7 @@ var RevisionDialog = function(editorUi, revs, restoreFn)
 		}
 	});
 	
-	var newBtn = mxUtils.button(mxResources.get('openInNewWindow'), function()
+	var newBtn = mxUtils.button(mxResources.get('open'), function()
 	{
 		if (currentDoc != null)
 		{
@@ -5468,9 +5472,18 @@ var RevisionDialog = function(editorUi, revs, restoreFn)
 								parseGraphModel(node);
 							}
 							
+							var shortUser = item.lastModifyingUserName;
+							
+							if (shortUser != null && shortUser.length > 20)
+							{
+								shortUser = shortUser.substring(0, 20) + '...';
+							}
+							
 							fileInfo.innerHTML = '';
-							mxUtils.write(fileInfo, ts.toLocaleDateString() + ' ' +
-									ts.toLocaleTimeString());
+							mxUtils.write(fileInfo, ((shortUser != null) ?
+								(shortUser + ' ') : '') + ts.toLocaleDateString() +
+								' ' + ts.toLocaleTimeString());
+							
 							fileInfo.setAttribute('title', row.getAttribute('title'));
 							zoomInBtn.removeAttribute('disabled');
 							zoomOutBtn.removeAttribute('disabled');

+ 1 - 3
src/main/webapp/js/diagramly/DrawioFile.js

@@ -337,9 +337,7 @@ DrawioFile.prototype.open = function()
  */
 DrawioFile.prototype.addAllSavedStatus = function()
 {
-	var file = this.ui.getCurrentFile();
-	
-	if (file != null && (file.constructor == DriveFile || file.constructor == DropboxFile))
+	if (this.constructor == DriveFile || this.constructor == DropboxFile)
 	{
 		this.ui.editor.setStatus('<div title="'+ mxUtils.htmlEntities(mxResources.get('revisionHistory')) +
 			'" style="text-decoration:underline;cursor:pointer;">' +

+ 136 - 42
src/main/webapp/js/diagramly/DriveClient.js

@@ -572,7 +572,7 @@ DriveClient.prototype.copyFile = function(id, title, success, error)
 	{
 		this.executeRequest(gapi.client.drive.files.copy({'fileId': id,
 			'fields': this.allFields, 'supportsTeamDrives': true,
-			'resource': {'title' : title}}), success, error);
+			'resource': {'title': title}}), success, error);
 	}
 };
 
@@ -713,11 +713,7 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
 						{
 							if (this.isGoogleRealtimeMimeType(resp.mimeType))
 							{
-								this.convertRealtimeFile(resp, mxUtils.bind(this, function(file)
-								{
-									this.notifyRealtimeConverted(file);
-									success(file);
-								}), error);
+								this.convertRealtimeFile(resp, success, error);
 							}
 							else
 							{
@@ -735,12 +731,20 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
 										doc.getModel().getRoot().isEmpty() || (doc.getModel().getRoot().has('cells') &&
 										!doc.getModel().getRoot().has(DriveRealtime.prototype.diagramsKey)))
 						    		{
-						    			this.getXmlFile(resp, doc, success, error);
+										this.getXmlFile(resp, doc, success, error);
 						    		}
 						    		else
 						    		{
-						        		// XML not required here since the realtime model is not empty
-						    			success(new DriveFile(this.ui, null, resp, doc));
+						        		// Uses JSON or XML data if flagged
+						    			if (doc.getModel().getRoot().has('realtimeConverted'))
+										{
+						    				doc.close();
+											this.convertRealtimeFile(resp, success, error);
+										}
+										else
+										{
+							    			success(new DriveFile(this.ui, null, resp, doc));
+										}
 						    		}
 								}
 								catch (e)
@@ -784,7 +788,7 @@ DriveClient.prototype.getRealtimeData = function(id, success, error, retryCount)
 		}
 		else if (error != null)
 		{
-			error();
+			error({message: 'realtime.get returned invalid data for ' + id});
 		}
 	}), mxUtils.bind(this, function(resp)
 	{
@@ -795,11 +799,14 @@ DriveClient.prototype.getRealtimeData = function(id, success, error, retryCount)
 		
 		if (retryCount < 3)
 		{
-			this.getRealtimeData(id, success, error, retryCount + 1);
+			window.setTimeout(mxUtils.bind(this, function()
+			{
+				this.getRealtimeData(id, success, error, retryCount + 1);
+			}), (retryCount + 1) * 100);
 		}
 		else if (error != null)
 		{
-			error();
+			error({message: 'realtime.get failed for ' + id});
 		}
 	}));
 };
@@ -895,7 +902,28 @@ DriveClient.prototype.getXmlFile = function(resp, doc, success, error, ignoreMim
 					}
 					else
 					{
-						// TODO: Import PNG
+						// Checks if the file contains XML data which can happen when we insert
+						// the file and then don't post-process it when loaded into the UI which
+						// is required for creating the images for .PNG and .SVG files.
+						try
+						{
+							var temp = atob(data.substring(index + 1));
+							
+							if (temp != null && (temp.substring(0, 8) === '<mxfile ' ||
+								temp.substring(0, 14) === '<mxGraphModel ' ||
+								temp.substring(0, 14) === '<mxGraphModel>'))
+	    					{
+								data = temp;
+	    					}
+							else
+							{
+								// TODO: Import as PNG
+							}
+						}
+						catch (e)
+						{
+							// ignore
+						}
 					}
 				}
 			}
@@ -904,7 +932,7 @@ DriveClient.prototype.getXmlFile = function(resp, doc, success, error, ignoreMim
 	
 			// Checks if mime-type needs to be updated if the file is editable and no viewer app
 			if (App.GOOGLE_REALTIME && !ignoreMime && this.appId != '850530949725' && file.isEditable() &&
-				resp.mimeType != this.mimeType)
+				resp.mimeType != this.mimeType && resp.mimeType != this.xmlMimeType)
 			{
 				// Overwrites mime-type (only mutable on update when uploading new content)
 				this.saveFile(file, true, mxUtils.bind(this, function(resp)
@@ -949,6 +977,8 @@ DriveClient.prototype.saveFile = function(file, revision, success, error, noChec
 		// Adds optional thumbnail to upload request
 		var doSave = mxUtils.bind(this, function(thumb, thumbMime, keepExisting)
 		{
+			var prevDesc = null;
+			var pinned = false;
 			var meta =
 			{
 				'mimeType': file.desc.mimeType,
@@ -956,11 +986,12 @@ DriveClient.prototype.saveFile = function(file, revision, success, error, noChec
 			};
 			
 			// Overrides old mime type and creates a revision
-			if (!App.GOOGLE_REALTIME && file.realtime == null &&
-				this.isGoogleRealtimeMimeType(file.desc.mimeType))
+			if (file.realtime == null && this.isGoogleRealtimeMimeType(file.desc.mimeType))
 			{
+				prevDesc = file.desc;
 				meta.mimeType = this.xmlMimeType;
 				revision = true;
+				pinned = true;
 			}
 			
 			// Inserts a channel ID
@@ -998,13 +1029,42 @@ DriveClient.prototype.saveFile = function(file, revision, success, error, noChec
 			}
 
 			// Updates saveDelay on drive file
-			var wrapper = function()
+			var wrapper = mxUtils.bind(this, function()
 			{
 		    	file.saveDelay = new Date().getTime() - t0;
 		    	success.apply(this, arguments);
-			};
+
+		    	if (prevDesc != null)
+				{
+		    		// Pins previous revision
+					this.executeRequest(gapi.client.drive.revisions.get(
+					{
+						'fileId': prevDesc.id,
+					    'revisionId': prevDesc.headRevisionId,
+					    'supportsTeamDrives': true
+					}), mxUtils.bind(this, mxUtils.bind(this, function(resp)
+					{
+						resp.pinned = true;
+						
+						this.executeRequest(gapi.client.drive.revisions.update(
+			    		{
+		    		      'fileId': prevDesc.id,
+		    		      'revisionId': prevDesc.headRevisionId,
+		    		      'resource': resp
+		    		    }));
+					})));
+					
+					// Logs conversion
+					this.ui.logEvent({category: 'RT-CONVERT',
+						action: 'from-' + prevDesc.id + '.' +
+						prevDesc.headRevisionId + '-to-' +
+						file.desc.id + '.' + file.desc.headRevisionId + '-' +
+						file.convertedFrom,
+						label: (this.user != null) ? this.user.id : 'unknown-user'});
+				}
+			});
 			
-			var fn = mxUtils.bind(this, function(data, binary)
+			var doExecuteRequest = mxUtils.bind(this, function(data, binary)
 			{
 				if (properties != null)
 				{
@@ -1016,21 +1076,21 @@ DriveClient.prototype.saveFile = function(file, revision, success, error, noChec
 					file.realtime == null) ? file.desc.etag : null;
 				
 				this.executeRequest(this.createUploadRequest(file.getId(), meta,
-					data, revision || (file.desc.mimeType != this.mimeType &&
-					file.desc.mimeType != this.libraryMimeType), binary, etag),
-					wrapper, error);
+					data, revision || (file.desc.mimeType != this.xmlMimeType &&
+					file.desc.mimeType != this.mimeType && file.desc.mimeType !=
+					this.libraryMimeType), binary, etag, pinned), wrapper, error);
 			});
 			
 			if (this.ui.useCanvasForExport && /(\.png)$/i.test(file.getTitle()))
 			{
 				this.ui.getEmbeddedPng(mxUtils.bind(this, function(data)
 				{
-					fn(data, true);
+					doExecuteRequest(data, true);
 				}), error, (this.ui.getCurrentFile() != file) ? file.getData() : null);
 			}
 			else
 			{
-				fn(file.getData(), false);
+				doExecuteRequest(file.getData(), false);
 			}
 		});
 		
@@ -1107,13 +1167,13 @@ DriveClient.prototype.saveFile = function(file, revision, success, error, noChec
 /**
  * Sends a message to all collaborators and stores the head revision ID.
  */
-DriveClient.prototype.notifyRealtimeConverted = function(file)
+DriveClient.prototype.notifyRealtimeConverted = function(desc)
 {
 	try
 	{
 		if (gapi.drive.realtime != null)
 		{
-			gapi.drive.realtime.load(file.getId(), mxUtils.bind(this, function(doc)
+			gapi.drive.realtime.load(desc.id, mxUtils.bind(this, function(doc)
 			{
 				try
 				{
@@ -1121,7 +1181,7 @@ DriveClient.prototype.notifyRealtimeConverted = function(file)
 						doc.getModel().getRoot() != null)
 					{
 						doc.getModel().getRoot().set('realtimeConverted',
-							file.desc.headRevisionId);
+							desc.headRevisionId);
 					}
 				}
 				catch (e)
@@ -1186,7 +1246,7 @@ DriveClient.prototype.redirectToNewApp = function(error, fileId)
 		this.redirectDialogShowing = true;
 		
 		var url = window.location.protocol + '//' + this.newAppHostname + '/' + this.ui.getSearch(
-			['create', 'title', 'mode', 'url', 'drive', 'splash']) + '#G' + fileId;
+			['create', 'title', 'mode', 'url', 'drive', 'splash', 'state']) + '#G' + fileId;
 		
 		if (error != null)
 		{
@@ -1284,7 +1344,7 @@ DriveClient.prototype.insertFile = function(title, data, folderId, success, erro
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-DriveClient.prototype.createUploadRequest = function(id, metadata, data, revision, binary, etag)
+DriveClient.prototype.createUploadRequest = function(id, metadata, data, revision, binary, etag, pinned)
 {
 	binary = (binary != null) ? binary : false;
 	var bd = '-------314159265358979323846';
@@ -1315,6 +1375,11 @@ DriveClient.prototype.createUploadRequest = function(id, metadata, data, revisio
 		reqObj.params['newRevision'] = false;
 	}
 	
+	if (pinned)
+	{
+		reqObj.params['pinned'] = true;
+	}
+	
 	reqObj.params['supportsTeamDrives'] = true;
 	reqObj.params['fields'] = this.allFields;
 	
@@ -1685,26 +1750,55 @@ DriveClient.prototype.getRealtimeAge = function(desc, json)
  */
 DriveClient.prototype.convertRealtimeFile = function(desc, success, error)
 {
+	var xmlSuccess = mxUtils.bind(this, function(file)
+	{
+		file.convertedFrom = 'xml';
+		success(file);
+	});
+	
+	var jsonSuccess = mxUtils.bind(this, function(file)
+	{
+		file.convertedFrom = 'json';
+		success(file);
+	});
+
 	this.getRealtimeData(desc.id, mxUtils.bind(this, function(json)
 	{
-		var age = this.getRealtimeAge(desc, json);
-		
-		// Uses the newer of the two
-		if (age < 0)
+		try
 		{
-			var node = this.convertJsonToXml(json);
-			console.log('converted realtime model', age, json, node);
-			success(new DriveFile(this.ui, mxUtils.getXml(node), desc));
+			var age = this.getRealtimeAge(desc, json);
+			this.notifyRealtimeConverted(desc);
+			
+			// Uses realtime if newer or less than 5 minutes old
+			if (age < 300000)
+			{
+				jsonSuccess(new DriveFile(this.ui, mxUtils.getXml(
+					this.convertJsonToXml(json)), desc));
+			}
+			else
+			{
+				this.getXmlFile(desc, null, xmlSuccess, mxUtils.bind(this, function()
+				{
+					try
+					{
+						jsonSuccess(new DriveFile(this.ui, mxUtils.getXml(
+							this.convertJsonToXml(json)), desc));
+
+					}
+					catch (e)
+					{
+						this.getXmlFile(desc, null, xmlSuccess, error);
+					}
+				}));
+			}
 		}
-		else
+		catch (e)
 		{
-			console.log('using newer XML file', age, json);
-			this.getXmlFile(desc, null, success, error);
+			this.getXmlFile(desc, null, xmlSuccess, error);
 		}
 	}), mxUtils.bind(this, function()
 	{
-		console.log('no realtime data, using XML file');
-		this.getXmlFile(desc, null, success, error);
+		this.getXmlFile(desc, null, xmlSuccess, error);
 	}));
 };
 
@@ -1791,7 +1885,7 @@ DriveClient.prototype.decodeJsonPage = function(json, node, uncompressed)
 			else
 			{
 				// Workaround for missing page ID in JSON
-				this.node.setAttribute('id', Editor.guid());
+				node.setAttribute('id', Editor.guid());
 			}
 		
 			if (json.name != null)

+ 17 - 113
src/main/webapp/js/diagramly/DriveFile.js

@@ -84,7 +84,7 @@ DriveFile.prototype.getPublicUrl = function(fn)
  */
 DriveFile.prototype.isAutosaveOptional = function()
 {
-	return true;
+	return this.realtime == null;
 };
 
 /**
@@ -453,7 +453,17 @@ DriveFile.prototype.showConflictDialog = function(retry, error)
 		{
 			this.showingConflictDialog = false;
 			this.changeListenerEnabled = prev;
-			this.makeCopy(retry, error, true);
+			
+			if (this.isRestricted())
+			{
+				this.ui.editor.editAsNew(this.ui.getFileData(true));
+				resume();
+				error();
+			}
+			else
+			{
+				this.makeCopy(retry, error, true);
+			}
 		}), null, mxResources.get('overwrite'), mxUtils.bind(this, function()
 		{
 			this.showingConflictDialog = false;
@@ -472,91 +482,17 @@ DriveFile.prototype.showConflictDialog = function(retry, error)
 		// Adds important notice to dialog
 		if (this.ui.dialog != null && this.ui.dialog.container != null)
 		{
-			var alert = document.createElement('a');
-			alert.className = 'geStatusAlert';
-			alert.style.display = 'block';
-			alert.style.position = 'absolute';
-			alert.style.cursor = 'pointer';
-			alert.style.bottom = '0';
-			alert.style.padding = '8px 0 8px 0';
-			alert.style.marginBottom = '26px';
+			var alert = this.ui.createRealtimeNotice();
 			alert.style.left = '0';
 			alert.style.right = '0';
-			alert.style.textAlign = 'center';
 			alert.style.borderRadius = '0';
 			alert.style.borderLeftStyle = 'none';
 			alert.style.borderRightStyle = 'none';
-			alert.style.textDecoration = 'none';
-			alert.style.fontWeight = 'bold';
-			
-			alert.setAttribute('href', 'https://desk.draw.io/support/solutions/articles/16000087215');
-			alert.setAttribute('target', '_blank');
-			mxUtils.write(alert, mxResources.get('collaborativeEditingNotice'));
-			
-			this.ui.dialog.container.appendChild(alert);
-		}
-	}
-};
+			alert.style.marginBottom = '26px';
+			alert.style.padding = '8px 0 8px 0';
 
-/**
- * Checks the conversion of the realtime model for this file.
- */
-DriveFile.prototype.checkConvert = function()
-{
-	var doCheck = mxUtils.bind(this, function(json)
-	{
-		try
-		{
-			if (this.ui.getCurrentFile() == this && !this.isModified())
-			{
-				this.ui.drive.getXmlFile(this.desc, null, mxUtils.bind(this, function(file)
-				{
-					try
-					{
-						var data = file.getData();
-						var node = (data != null) ? mxUtils.parseXml(data).documentElement : null;
-						
-						if (node != null)
-						{
-							var tmp = this.ui.editor.extractGraphModel(node, true);
-							
-							if (tmp != null)
-							{
-								node = tmp;
-							}
-						}
-						
-						if (this.runCheck != null)
-						{
-							this.runCheck(json, node, data);
-						}
-					}
-					catch (e)
-					{
-						this.log('CATCH-PARSEFILE-' + e.stack);
-						this.runCheck(json, null, 'CATCH-PARSEFILE-' + e.stack);
-					}
-				}), mxUtils.bind(this, function(err)
-				{
-					this.log('ERROR-GETFILE');
-					this.runCheck(json, null, 'ERROR-GETFILE');
-				}), true);
-			}
-		}
-		catch (e)
-		{
-			this.log('CATCH-GETFILE-' + e.stack);
-			this.runCheck(json, null, 'CATCH-GETFILE-' + e.stack);
+			this.ui.dialog.container.appendChild(alert);
 		}
-	});
-	
-	try
-	{
-		this.ui.drive.getRealtimeData(this.desc.id, doCheck, doCheck);
-	}
-	catch (e)
-	{
-		this.log('CATCH-GETJSON-' + e.stack, true);
 	}
 };
 
@@ -708,7 +644,7 @@ DriveFile.prototype.log = function(msg, sendReport)
 		
 		if (sendReport)
 		{
-			this.report('Realtime Log Report ' + new Date() +
+			this.ui.sendReport('Realtime Log Report ' + new Date() +
 				'\n\nDescription: ' + JSON.stringify({version: this.ui.JSON_CHECK,
 					title: this.desc.title, editable: this.desc.editable,
 					copyable: this.desc.copyable, labels: this.desc.labels, id: this.desc.id,
@@ -723,35 +659,3 @@ DriveFile.prototype.log = function(msg, sendReport)
 		// ignore
 	}
 };
-
-/**
- * Debug output.
- */
-DriveFile.prototype.report = function(data)
-{
-	try
-	{
-		if (data.length > 3000000)
-		{
-			data = data.substring(0, 3000000) + '\n...[REPORT SHORTENED]'
-		}
-		
-		this.debug(data);
-		
-		mxUtils.post('/email', 'version=' + encodeURIComponent(EditorUi.VERSION) +
-			'&url=' + encodeURIComponent(window.location.href) +
-			'&data=' + encodeURIComponent(data),
-			mxUtils.bind(this, function(req)
-			{
-				this.debug('report sent');
-			}),
-			mxUtils.bind(this, function()
-			{
-				this.debug('report failed');
-			}));
-	}
-	catch (e)
-	{
-		// ignore
-	}
-};

+ 27 - 3
src/main/webapp/js/diagramly/EditorUi.js

@@ -1664,7 +1664,7 @@
 			}
 			
 			var ts = new Date();
-			title += '-' + getFormattedTime();
+			title += ' ' + getFormattedTime();
 		}
 		
 		title = mxResources.get('copyOf', [title]) + extension;
@@ -1848,7 +1848,7 @@
 					else if (oldFile != null)
 					{
 						// Workaround for close realtime model is to reload the file from scratch
-						if (oldFile.constructor == DriveFile)
+						if (oldFile.constructor == DriveFile && oldFile.realtime != null)
 						{
 							this.loadFile(oldFile.getHash());
 						}
@@ -1902,6 +1902,30 @@
 		}
 	};
 
+	/**
+	 * Debug output.
+	 */
+	EditorUi.prototype.sendReport = function(data, maxLength)
+	{
+		maxLength = (maxLength != null) ? maxLength : 3000000;
+		
+		try
+		{
+			if (data.length > maxLength)
+			{
+				data = data.substring(0, maxLength) + '\n...[SHORTENED]'
+			}
+			
+			mxUtils.post('/email', 'version=' + encodeURIComponent(EditorUi.VERSION) +
+				'&url=' + encodeURIComponent(window.location.href) +
+				'&data=' + encodeURIComponent(data));
+		}
+		catch (e)
+		{
+			// ignore
+		}
+	};
+
 	/**
 	 * Updates action states depending on the selection.
 	 */
@@ -3119,7 +3143,7 @@
    	    // Checks if output is invalid or empty
    	    if (data.length <= 6 || data == canvas.cloneNode(false).toDataURL('image/' + format))
    	    {
-   	    		throw {message: 'Invalid image'};
+   	    	throw {message: 'Invalid image'};
    	    }
    	    
    	    if (xml != null)

+ 9 - 12
src/main/webapp/js/diagramly/Menus.js

@@ -344,11 +344,11 @@
 							{
 								item.getXml = function(success, error)
 								{
-									// Workaround for vanished head revision is to use current head revision from descriptor
-									// TODO: Allow team drives
-									editorUi.drive.executeRequest(gapi.client.drive.revisions.get({'fileId': file.getId(),
-										'revisionId': (resp.items[resp.items.length - 1] === item) ?
-										file.desc.headRevisionId : item.id}), function(resp)
+									editorUi.drive.executeRequest(gapi.client.drive.revisions.get(
+									{
+										'fileId': file.getId(),
+										'revisionId': item.id
+									}), function(resp)
 									{
 										editorUi.drive.getXmlFile(resp, null, function(file2)
 							   			{
@@ -1695,13 +1695,10 @@
 				}, parent);
 			}
 
-			if (!mxClient.IS_IOS)
+			menu.addItem(mxResources.get('device') + '...', null, function()
 			{
-				menu.addItem(mxResources.get('device') + '...', null, function()
-				{
-					editorUi.importLocalFile(true);
-				}, parent);
-			}
+				editorUi.importLocalFile(true);
+			}, parent);
 
 			if (!editorUi.isOffline())
 			{
@@ -2724,7 +2721,7 @@
 					this.addMenuItems(menu, ['-', 'revisionHistory'], parent);
 				}
 				
-				if (file != null && file.constructor == DriveFile)
+				if (file != null && file.constructor == DriveFile && file.realtime != null)
 				{
 					this.addMenuItems(menu, ['createRevision'], parent);
 				}

+ 12 - 3
src/main/webapp/js/diagramly/Minimal.js

@@ -779,7 +779,16 @@ EditorUi.initMinimalTheme = function()
 			
 			if (file != null && file.constructor == DriveFile)
 			{
-				ui.menus.addMenuItems(menu, ['createRevision', 'makeCopy', '-', 'rename', 'moveToFolder'], parent);
+				if (file.realtime == null)
+				{
+					ui.menus.addMenuItems(menu, ['save'], parent);
+				}
+				else
+				{
+					ui.menus.addMenuItems(menu, ['createRevision'], parent);
+				}
+				
+				ui.menus.addMenuItems(menu, ['makeCopy', '-', 'rename', 'moveToFolder'], parent);
 			}
 			else
 			{
@@ -798,12 +807,12 @@ EditorUi.initMinimalTheme = function()
 				}
 			}
 
+			ui.menus.addMenuItems(menu, ['-', 'autosave'], parent);
+			
 			if (file != null && (file.constructor == DriveFile || file.constructor == DropboxFile))
 			{
 				ui.menus.addMenuItems(menu, ['-', 'revisionHistory'], parent);
 			}
-			
-			ui.menus.addMenuItems(menu, ['-', 'autosave'], parent);
         })));
         
         // Augments the existing export menu

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


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


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


+ 1 - 1
src/main/webapp/resources/dia_de.txt

@@ -90,7 +90,7 @@ chatLeft={1} hat den Chat verlassen
 chatWindowTitle=Chat
 chooseAnOption=Wählen Sie eine Option
 chromeApp=Chrome app
-collaborativeEditingNotice=Wichtige Mitteilung für gemeinesames Bearbeiten
+collaborativeEditingNotice=Wichtige Mitteilung für gemeinsames Bearbeiten
 compressed=Komprimiert
 commitMessage=Commit-Nachricht
 csv=CSV