Gaudenz Alder před 6 roky
rodič
revize
67e621046f

+ 5 - 0
ChangeLog

@@ -1,3 +1,8 @@
+20-DEC-2018: 10.0.6
+
+- Performance fixes for collaborative editing
+- Fixes collaboration conflict errors
+
 19-DEC-2018: 10.0.5
 
 - Fixes for collaborative editing

+ 1 - 1
VERSION

@@ -1 +1 @@
-10.0.5
+10.0.6

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

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 12/19/2018 05:08 PM
+# 12/20/2018 01:32 PM
 
 app.html
 index.html?offline=1

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 763 - 767
src/main/webapp/js/app.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 708 - 707
src/main/webapp/js/atlas-viewer.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 580 - 579
src/main/webapp/js/atlas.min.js


+ 3 - 8
src/main/webapp/js/diagramly/App.js

@@ -201,11 +201,6 @@ App.PUSHER_CLUSTER = 'eu';
  */
 App.PUSHER_URL = 'https://js.pusher.com/4.3/pusher.min.js';
 
-/**
- * Switch to disable Google realtime starting on 11/12/2018 at 9:00am (UTC).
- */
-App.GOOGLE_REALTIME = urlParams['google-realtime'] != '0' && new Date().getTime() < 1542013200000;
-
 /**
  * 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.
@@ -3431,7 +3426,7 @@ App.prototype.fileCreated = function(file, libs, replace, done)
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-App.prototype.loadFile = function(id, sameWindow, file, success)
+App.prototype.loadFile = function(id, sameWindow, file, success, force)
 {
 	this.hideDialog();
 	
@@ -3716,7 +3711,7 @@ App.prototype.loadFile = function(id, sameWindow, file, success)
 	
 	var fn = mxUtils.bind(this, function()
 	{
-		if (currentFile == null || !currentFile.isModified())
+		if (force || currentFile == null || !currentFile.isModified())
 		{
 			fn2();
 		}
@@ -4309,7 +4304,7 @@ App.prototype.exportFile = function(data, filename, mimeType, base64Encoded, mod
 			{
 				this.spinner.stop();
 				this.handleError(resp);
-			}), mimeType, base64Encoded, false);
+			}), mimeType, base64Encoded);
 		}
 	}
 	else if (mode == App.MODE_ONEDRIVE)

+ 52 - 58
src/main/webapp/js/diagramly/DiffSync.js

@@ -31,23 +31,23 @@ EditorUi.prototype.viewStateWhitelist = ['background', 'backgroundImage', 'foldi
 /**
  * Removes all labels, user objects and styles from the given node in-place.
  */
-EditorUi.prototype.patchPages = function(pages, diff, markPages, shadow, updateEdgeParents)
+EditorUi.prototype.patchPages = function(pages, diff, markPages, resolver, updateEdgeParents)
 {
-	var shadowLookup = {};
+	var resolverLookup = {};
 	var newPages = [];
 	var inserted = {};
 	var removed = {};
 	var lookup = {};
 	var moved = {};
 	
-  	if (shadow != null)
+  	if (resolver != null && resolver[EditorUi.DIFF_UPDATE] != null)
 	{
-		for (var i = 0; i < shadow.length; i++)
-		{
-			shadowLookup[shadow[i].getId()] = shadow[i];
+  		for (var id in resolver[EditorUi.DIFF_UPDATE])
+  		{
+  			resolverLookup[id] = resolver[EditorUi.DIFF_UPDATE][id];
 		}
 	}
-	
+
 	if (diff[EditorUi.DIFF_REMOVE] != null)
 	{
 		for (var i = 0; i < diff[EditorUi.DIFF_REMOVE].length; i++)
@@ -129,7 +129,9 @@ EditorUi.prototype.patchPages = function(pages, diff, markPages, shadow, updateE
 				
 				if (pageDiff.cells != null)
 				{
-					this.patchPage(page, pageDiff.cells, shadowLookup[page.getId()], updateEdgeParents);
+					this.patchPage(page, pageDiff.cells,
+						resolverLookup[page.getId()],
+						updateEdgeParents);
 				}
 				
 				if (markPages && (pageDiff.cells != null ||
@@ -303,19 +305,27 @@ EditorUi.prototype.createParentLookup = function(model, diff)
 /**
  * Removes all labels, user objects and styles from the given node in-place.
  */
-EditorUi.prototype.patchPage = function(page, diff, shadowPage, updateEdgeParents)
+EditorUi.prototype.patchPage = function(page, diff, resolver, updateEdgeParents)
 {
 	var model = (page == this.currentPage) ? this.editor.graph.model : new mxGraphModel(page.root);
-	var shadowModel = (shadowPage != null) ? new mxGraphModel(shadowPage.root) : null;
 	var parentLookup = this.createParentLookup(model, diff);
 
 	model.beginUpdate();
 	try
 	{
-		// Disables update of edge parents during update to avoid interference with
-		// remote updated edge parents
-		var prev = model.maintainEdgeParent;
-		model.maintainEdgeParent = false;
+		// Disables or delays update of edge parents to after patch
+		var prev = model.updateEdgeParent;
+		var dict = new mxDictionary();
+		var pendingUpdates = [];
+		
+		model.updateEdgeParent = function(edge, root)
+		{
+			if (!dict.get(edge) && updateEdgeParents)
+			{
+				dict.put(edge, true);
+				pendingUpdates.push(edge);
+			}
+		};
 
 		// Handles new root cells
 		var temp = parentLookup[''];
@@ -359,15 +369,19 @@ EditorUi.prototype.patchPage = function(page, diff, shadowPage, updateEdgeParent
 		}
 		
 		// Patches cell structure
-		this.patchCellRecursive(page, model, model.root, parentLookup, diff, shadowModel);
+		this.patchCellRecursive(page, model, model.root, parentLookup, diff);
 
 		// Applies patches and changes terminals after all cells are inserted
 		if (diff[EditorUi.DIFF_UPDATE] != null)
 		{
+			var res = (resolver != null && resolver.cells != null) ? 
+				resolver.cells[EditorUi.DIFF_UPDATE] : null;
+			
 			for (var id in diff[EditorUi.DIFF_UPDATE])
 			{
-				this.patchCell(model, model.getCell(id), diff[EditorUi.DIFF_UPDATE][id],
-					(shadowModel != null) ? shadowModel.getCell(id) : null);
+				this.patchCell(model, model.getCell(id),
+					diff[EditorUi.DIFF_UPDATE][id],
+					(res != null) ? res[id] : null);
 			}
 		}
 
@@ -387,13 +401,18 @@ EditorUi.prototype.patchPage = function(page, diff, shadowPage, updateEdgeParent
 			}
 		}
 
-		// Only updates edge parents if there are possible changes which is the case
-		// for patching the local pages in the case where the file was modified
-		model.maintainEdgeParent = prev;
+		// Updates edge parents after all patches have been applied
+		model.updateEdgeParent = prev;
 		
-		if (updateEdgeParents)
+		if (updateEdgeParents && pendingUpdates.length > 0)
 		{
-			model.updateEdgeParents(model.root);
+			for (var i = 0; i < pendingUpdates.length; i++)
+			{
+				if (model.contains(pendingUpdates[i]))
+				{
+					model.updateEdgeParent(pendingUpdates[i]);
+				}
+			}
 		}
 	}
 	finally
@@ -405,7 +424,7 @@ EditorUi.prototype.patchPage = function(page, diff, shadowPage, updateEdgeParent
 /**
  * Removes all labels, user objects and styles from the given node in-place.
  */
-EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup, diff, shadowModel)
+EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup, diff)
 {
 	var temp = parentLookup[cell.getId()];
 	var inserted = (temp != null && temp.inserted != null) ? temp.inserted : {};
@@ -443,8 +462,8 @@ EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup
 				model.add(cell, child, index);
 			}
 
-			this.patchCellRecursive(page, model, child,
-				parentLookup, diff, shadowModel);
+			this.patchCellRecursive(page, model,
+				child, parentLookup, diff);
 			index++;
 		}
 
@@ -485,51 +504,26 @@ EditorUi.prototype.patchCellRecursive = function(page, model, cell, parentLookup
 /**
  * Removes all labels, user objects and styles from the given node in-place.
  */
-EditorUi.prototype.patchCell = function(model, cell, diff, shadowCell)
+EditorUi.prototype.patchCell = function(model, cell, diff, resolve)
 {
-	function isNode(value)
-	{
-		return value != null && typeof value === 'object' && typeof value.nodeType === 'number' &&
-			typeof value.nodeName === 'string' && typeof value.getAttribute === 'function';
-	};
-	
-	function isLocalValueChanged()
-	{
-		if (shadowCell != null && shadowCell.value != null && cell.value != null)
-		{
-			if (isNode(cell.value) && isNode(shadowCell.value))
-			{
-				return !cell.value.isEqualNode(shadowCell.value);
-			}
-			else if (cell.value != shadowCell.value)
-			{
-				return cell.value != '';
-			}
-		}
-		
-		return false;
-	};
-	
 	if (cell != null && diff != null)
 	{
-		// Last write wins for value and style
-		if (diff.value != null)
+		// Last write wins for value except if label is empty
+		if (resolve == null || (resolve.xmlValue == null &&
+			(resolve.value == null || resolve.value == '')))
 		{
-			if (!isLocalValueChanged())
+			if (diff.value != null)
 			{
 				model.setValue(cell, diff.value);
 			}
-		}
-		else if (diff.xmlValue != null)
-		{
-			if (!isLocalValueChanged())
+			else if (diff.xmlValue != null)
 			{
 				model.setValue(cell, mxUtils.parseXml(diff.xmlValue).documentElement);
 			}
 		}
 		
-		if (diff.style != null && !(shadowCell != null && shadowCell.style != null &&
-			cell.style != null && cell.style != shadowCell.style))
+		// Last write wins for style
+		if ((resolve == null || resolve.style == null) && diff.style != null)
 		{
 			model.setStyle(cell, diff.style);
 		}

+ 217 - 161
src/main/webapp/js/diagramly/DrawioFile.js

@@ -209,47 +209,71 @@ DrawioFile.prototype.mergeFile = function(file, success, error)
 		var shadow = (this.shadowPages != null) ? this.shadowPages :
 			this.ui.getPagesForNode(mxUtils.parseXml(
 			this.shadowData).documentElement);
+		
+		// Should never happen
+		if (shadow == null || shadow.length == 0)
+		{
+			this.sendErrorReport(
+				'Shadow is null or empty',
+				'shadowPages: ' + (this.shadowPages != null) +
+				'\nshadowData: ' + (this.shadowData != null));
+		}
 	
 		// Loads new document as shadow document
-		this.shadowPages = this.ui.getPagesForNode(mxUtils.parseXml(
-			file.data).documentElement)
+		this.shadowPages = this.ui.getPagesForNode(
+			mxUtils.parseXml(file.data).
+			documentElement)
 					
 		// Creates a patch for backup if the checksum fails
-		this.backupPatch = (this.isModified()) ? this.ui.diffPages(
-			shadow, this.ui.pages) : null;
+		this.backupPatch = (this.isModified()) ?
+			this.ui.diffPages(shadow,
+			this.ui.pages) : null;
 		
 		// Patches the current document
-		var patch = this.ui.diffPages(shadow, this.shadowPages);
-		this.patch([patch], (DrawioFile.LAST_WRITE_WINS &&
-			this.isEditable() && this.isModified()) ? shadow : null);
-		
-		// Patching previous shadow to verify checksum
-		var patchedShadow = this.ui.patchPages(shadow, patch);
-		var checksum = this.ui.getHashValueForPages(patchedShadow);
-		var current = this.ui.getHashValueForPages(this.shadowPages);
-		
-		EditorUi.debug('File.mergeFile', [this], 'patch', patch, 'checksum',
-			current == checksum, checksum);
-		
-		if (checksum != current)
-		{
-			this.stats.checksumErrors++;
-			this.checksumError(error, [patch]);
-		}
-		else
+		var patches = [this.ui.diffPages(shadow, this.shadowPages)];
+
+		if (!this.ignorePatches(patches))
 		{
-			this.backupPatch = null;
-			this.invalidChecksum = false;
-			this.inConflictState = false;
+			// Patching previous shadow to verify checksum
+			var patchedShadow = this.ui.patchPages(shadow, patches[0]);
+			var checksum = this.ui.getHashValueForPages(patchedShadow);
+			var current = this.ui.getHashValueForPages(this.shadowPages);
 			
-			this.setDescriptor(file.getDescriptor());
-			this.descriptorChanged();
+			if (urlParams['test'] == '1')
+			{
+				EditorUi.debug('File.mergeFile', [this],
+					'patches', patches, 'backup', this.backupPatch,
+					'checksum', current == checksum, checksum);
+			}
 			
-			if (success != null)
+			if (checksum != null && checksum != current)
+			{
+				this.stats.checksumErrors++;
+				this.checksumError(error, patches,
+					'checksum: ' + checksum +
+					'\ncurrent: ' + current);
+				
+				// Abnormal termination
+				return;
+			}
+			else
 			{
-				success();
+				this.patch(patches,
+					(DrawioFile.LAST_WRITE_WINS) ?
+					this.backupPatch : null);
 			}
 		}
+
+		this.invalidChecksum = false;
+		this.inConflictState = false;
+		this.setDescriptor(file.getDescriptor());
+		this.descriptorChanged();
+		this.backupPatch = null;
+		
+		if (success != null)
+		{
+			success();
+		}
 	}
 	catch (e)
 	{
@@ -289,7 +313,7 @@ DrawioFile.prototype.mergeFile = function(file, success, error)
 /**
  * Adds the listener for automatically saving the diagram for local changes.
  */
-DrawioFile.prototype.checksumError = function(error, patches)
+DrawioFile.prototype.checksumError = function(error, patches, details)
 {
 	this.inConflictState = true;
 	this.invalidChecksum = true;
@@ -329,15 +353,6 @@ DrawioFile.prototype.checksumError = function(error, patches)
 			}
 		}
 
-		var user = this.getCurrentUser();
-		var uid = (user != null) ? this.ui.hashValue(user.id) : 'unknown';
-
-		if (this.stats.start != null)
-		{
-			this.stats.uptime = Math.round((new Date().getTime() -
-				new Date(this.stats.start).getTime()) / 1000);
-		}
-		
 		var data = mxUtils.getPrettyXml(file);
 		
 		if (data != null && data.length > 10000)
@@ -352,21 +367,66 @@ DrawioFile.prototype.checksumError = function(error, patches)
 			json = this.ui.editor.graph.compress(json);
 		}
 		
-		EditorUi.sendReport('Checksum Error ' + new Date().toISOString() + ':\n\n' +
-			'File=' + this.getId() + ' (' + this.getMode() + ')\n' +
-			((this.sync != null) ? ('Client=' + this.sync.clientId + '\n') : '') +
-			'User=' + uid + '\n' +
-			'Size=' + this.getSize() + '\n' +
-			'Sync=' + DrawioFile.SYNC + '\n\n' +
-			'Stats:\n' + JSON.stringify(this.stats, null, 2) + '\n\n' +
-			'Data:\n' + data + '\n' +
-			'Patches:\n' + json + '\n\n' +
-			'Stack:\n' + new Error().stack);
+		this.sendErrorReport(
+			'Checksum Error',
+			((details != null) ? (details + '\n') : '') +
+			'Data:\n' + data +
+			'\nPatches:\n' + json);
+	}
+	catch (e)
+	{
+		// ignore
+	}
+};
+
+/**
+ * Adds the listener for automatically saving the diagram for local changes.
+ */
+DrawioFile.prototype.sendErrorReport = function(title, details)
+{
+	try
+	{
+		var user = this.getCurrentUser();
+		var uid = (user != null) ? this.ui.hashValue(user.id) : 'unknown';
+	
+		if (this.stats.start != null)
+		{
+			this.stats.uptime = Math.round((new Date().getTime() -
+				new Date(this.stats.start).getTime()) / 1000);
+		}
+	
+		var filename = this.getTitle();
+		var dot = filename.lastIndexOf('.');
+		var ext = 'none';
+		
+		if (dot > 0)
+		{
+			ext = filename.substring(dot);
+		}
+		
+		var stack = new Error().stack;
+		var lf = stack.indexOf('\n');
+		
+		if (lf > 0)
+		{
+			stack = stack.substring(lf + 1);
+		}
+		
+		EditorUi.sendReport(title + ' ' + new Date().toISOString() + ':' +
+			'\n\nBrowser=' + navigator.userAgent +
+			'\nFile=' + this.getId() + ' (' + this.getMode() + ')' +
+			((this.sync != null) ? ('\nClient=' + this.sync.clientId) : '') +
+			'\nUser=' + uid +
+			'\nExt=' + ext +
+			'\nSize=' + this.getSize() +
+			'\nSync=' + DrawioFile.SYNC +
+			'\n\nStats:\n' + JSON.stringify(this.stats, null, 2) +
+			((details != null) ? ('\n\n' + details) : '') + 
+			'\n\nStack:\n' + stack);
 	}
 	catch (e)
 	{
 		// ignore
-		console.error(e);
 	}
 };
 
@@ -381,35 +441,39 @@ DrawioFile.prototype.reloadFile = function(success, error)
 		
 		var fn = mxUtils.bind(this, function()
 		{
-			this.setModified(false);
-			var page = this.ui.currentPage;
+			this.stats.reloads++;
+			this.reportEnabled = false;
+			
+			// Restores view state and current page
 			var viewState = this.ui.editor.graph.getViewState();
 			var selection = this.ui.editor.graph.getSelectionCells();
-			this.reportEnabled = false;
-			this.stats.reloads++;
+			var page = this.ui.currentPage;
 			
 			this.ui.loadFile(this.getHash(), true, null, mxUtils.bind(this, function()
 			{
-				this.ui.restoreViewState(page, viewState, selection);
-				
-				if (this.backupPatch != null)
-				{
-					this.patch([this.backupPatch]);
-				}
-				
-				// Carry-over
-				var file = this.ui.getCurrentFile();
-				
-				if (file != null)
-				{
-					file.stats = this.stats;
-				}
-				
-				if (success != null)
+				if (this.ui.fileLoadedError == null)
 				{
-					success();
+					this.ui.restoreViewState(page, viewState, selection);
+					
+					if (this.backupPatch != null)
+					{
+						this.patch([this.backupPatch]);
+					}
+					
+					// Carry-over stats
+					var file = this.ui.getCurrentFile();
+					
+					if (file != null)
+					{
+						file.stats = this.stats;
+					}
+					
+					if (success != null)
+					{
+						success();
+					}
 				}
-			}));
+			}), true);
 		});
 	
 		if (this.isModified() && this.backupPatch == null)
@@ -480,104 +544,101 @@ DrawioFile.prototype.ignorePatches = function(patches)
 /**
  * Applies the given patches to the file.
  */
-DrawioFile.prototype.patch = function(patches, shadow)
+DrawioFile.prototype.patch = function(patches, resolver)
 {
-	if (!this.ignorePatches(patches))
-	{
-		// Saves state of undo history
-		var undoMgr = this.ui.editor.undoManager;
-		var history = undoMgr.history.slice();
-		var nextAdd = undoMgr.indexOfNextAdd;
-		
-		// Hides graph during updates
-		var graph = this.ui.editor.graph;
-		graph.container.style.visibility = 'hidden';
+	// Saves state of undo history
+	var undoMgr = this.ui.editor.undoManager;
+	var history = undoMgr.history.slice();
+	var nextAdd = undoMgr.indexOfNextAdd;
 	
-		// Ignores change events
-		var prev = this.changeListenerEnabled;
-		this.changeListenerEnabled = false;
-		
-		// Folding and math change require special handling
-		var fold = graph.foldingEnabled;
-		var math = graph.mathEnabled;
-		
-		// Updates text editor if cell changes during validation
-		var redraw = graph.cellRenderer.redraw;
+	// Hides graph during updates
+	var graph = this.ui.editor.graph;
+	graph.container.style.visibility = 'hidden';
+
+	// Ignores change events
+	var prev = this.changeListenerEnabled;
+	this.changeListenerEnabled = false;
 	
-		graph.cellRenderer.redraw = function(state)
-	    {
-	        if (state.view.graph.isEditing(state.cell))
-	        {
-	            state.view.graph.scrollCellToVisible(state.cell);
-	        	state.view.graph.cellEditor.resize();
-	        }
-	        
-	        redraw.apply(this, arguments);
-	    };
-		
-		graph.model.beginUpdate();
-		try
+	// Folding and math change require special handling
+	var fold = graph.foldingEnabled;
+	var math = graph.mathEnabled;
+	
+	// Updates text editor if cell changes during validation
+	var redraw = graph.cellRenderer.redraw;
+
+	graph.cellRenderer.redraw = function(state)
+    {
+        if (state.view.graph.isEditing(state.cell))
+        {
+            state.view.graph.scrollCellToVisible(state.cell);
+        	state.view.graph.cellEditor.resize();
+        }
+        
+        redraw.apply(this, arguments);
+    };
+	
+	graph.model.beginUpdate();
+	try
+	{
+	    // Applies patches
+		for (var i = 0; i < patches.length; i++)
 		{
-		    // Applies patches
-			for (var i = 0; i < patches.length; i++)
-			{
-				this.ui.pages = this.ui.patchPages(this.ui.pages,
-					patches[i], true, shadow, this.isModified());
-			}
+			this.ui.pages = this.ui.patchPages(this.ui.pages,
+				patches[i], true, resolver, this.isModified());
 		}
-		finally
+	}
+	finally
+	{
+		graph.model.endUpdate();
+
+		// Checks if current page was removed
+		if (this.ui.pages != null && this.ui.pages.length > 0 &&
+			mxUtils.indexOf(this.ui.pages, this.ui.currentPage) < 0)
 		{
-			graph.model.endUpdate();
+			this.ui.selectPage(this.ui.pages[0], true);
+		}
+	
+		// Restores previous state
+		graph.cellRenderer.redraw = redraw;
+		this.changeListenerEnabled = prev;
+		graph.container.style.visibility = '';
 	
-			// Checks if current page was removed
-			if (this.ui.pages != null && this.ui.pages.length > 0 &&
-				mxUtils.indexOf(this.ui.pages, this.ui.currentPage) < 0)
+		// Restores history state
+		undoMgr.history = history;
+		undoMgr.indexOfNextAdd = nextAdd;
+		undoMgr.fireEvent(new mxEventObject(mxEvent.CLEAR));
+		
+		if (this.ui.currentPage == null || this.ui.currentPage.needsUpdate)
+		{
+			// Updates the graph and background
+			if (math != graph.mathEnabled)
 			{
-				this.ui.selectPage(this.ui.pages[0], true);
+				this.ui.editor.updateGraphComponents();
+				graph.refresh();
 			}
-		
-			// Restores previous state
-			graph.cellRenderer.redraw = redraw;
-			this.changeListenerEnabled = prev;
-			graph.container.style.visibility = '';
-		
-			// Restores history state
-			undoMgr.history = history;
-			undoMgr.indexOfNextAdd = nextAdd;
-			undoMgr.fireEvent(new mxEventObject(mxEvent.CLEAR));
-			
-			if (this.ui.currentPage == null || this.ui.currentPage.needsUpdate)
+			else
 			{
-				// Updates the graph and background
-				if (math != graph.mathEnabled)
+				if (fold != graph.foldingEnabled)
 				{
-					this.ui.editor.updateGraphComponents();
-					graph.refresh();
+					graph.view.revalidate();
 				}
 				else
 				{
-					if (fold != graph.foldingEnabled)
-					{
-						graph.view.revalidate();
-					}
-					else
-					{
-						
-						graph.view.validate();
-					}
 					
-					graph.sizeDidChange();
+					graph.view.validate();
 				}
 				
-				// Updates view state in format panel if nothing is selected
-				if (this.ui.format != null && graph.isSelectionEmpty())
-				{
-					this.ui.format.refresh();
-				}
+				graph.sizeDidChange();
 			}
 			
-			this.ui.updateTabContainer();
+			// Updates view state in format panel if nothing is selected
+			if (this.ui.format != null && graph.isSelectionEmpty())
+			{
+				this.ui.format.refresh();
+			}
 		}
+		
+		this.ui.updateTabContainer();
 	}
 };
 
@@ -1093,15 +1154,11 @@ DrawioFile.prototype.addConflictStatus = function(fn, message)
 {
 	if (this.invalidChecksum)
 	{
-		this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) +
-			' (' + mxResources.get('checksum') + ')');
-	}
-	else
-	{
-		this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) +
-			((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : ''));
+		message = mxResources.get('checksum');
 	}
 
+	this.setConflictStatus(mxUtils.htmlEntities(mxResources.get('fileChangedSync')) +
+			((message != null && message != '') ? ' (' + mxUtils.htmlEntities(message) + ')' : ''));
 	this.ui.spinner.stop();
 	this.clearAutosave();
 
@@ -1457,10 +1514,9 @@ DrawioFile.prototype.fileSaved = function(savedData, lastDesc, success, error)
 	}
 	else
 	{
-		var savedPages = this.ui.getPagesForNode(
-			mxUtils.parseXml(savedData).documentElement);
-		this.sync.fileSaved(savedPages, lastDesc, success, error);
-		this.shadowPages = savedPages;
+		this.sync.fileSaved(this.ui.getPagesForNode(
+			mxUtils.parseXml(savedData).documentElement),
+			lastDesc, success, error);
 	}
 };
 

+ 68 - 43
src/main/webapp/js/diagramly/DrawioFileSync.js

@@ -841,42 +841,57 @@ DrawioFileSync.prototype.merge = function(patches, checksum, etag, success, erro
 			
 		// Creates a patch for backup if the checksum fails
 		this.file.backupPatch = (this.file.isModified()) ?
-			this.ui.diffPages(this.file.shadowPages, this.ui.pages) : null;
-		
-		// Patches the current document
-		this.file.patch(patches, (DrawioFile.LAST_WRITE_WINS &&
-			this.file.isEditable() && this.file.isModified()) ?
-			this.file.shadowPages : null);
-		
-		// Patches the shadow document
-		for (var i = 0; i < patches.length; i++)
-		{
-			this.file.shadowPages = this.ui.patchPages(this.file.shadowPages, patches[i]);
-		}
-		
-		var current = (checksum != null) ? this.ui.getHashValueForPages(this.file.shadowPages) : null;
-		EditorUi.debug('Sync.merge', [this], 'from', this.file.getCurrentEtag(), 'to', etag,
-			'attempt', this.catchupRetryCount, 'diffs', patches, 'checksum',
-			checksum == current, checksum);
+			this.ui.diffPages(this.file.shadowPages,
+			this.ui.pages) : null;
 
-		// Compares the checksum
-		if (checksum != null && checksum != current)
+		if (!this.file.ignorePatches(patches))
 		{
-			this.file.stats.mergeChecksumErrors++;
-			this.file.checksumError(error, patches);
-		}
-		else
-		{
-			this.file.invalidChecksum = false;
-			this.file.inConflictState = false;
-			this.file.backupPatch = null;
-			this.file.setCurrentEtag(etag);
+			// Patches the shadow document
+			for (var i = 0; i < patches.length; i++)
+			{
+				this.file.shadowPages = this.ui.patchPages(this.file.shadowPages, patches[i]);
+			}
+			
+			var current = (checksum != null) ? this.ui.getHashValueForPages(this.file.shadowPages) : null;
 			
-			if (success != null)
+			if (urlParams['test'] == '1')
 			{
-				success();
+				EditorUi.debug('Sync.merge', [this],
+					'from', this.file.getCurrentEtag(), 'to', etag,
+					'patches', patches, 'backup', this.file.backupPatch,
+					'attempt', this.catchupRetryCount,
+					'checksum', checksum == current, checksum);
+			}
+			
+			// Compares the checksum
+			if (checksum != null && checksum != current)
+			{
+				this.file.stats.mergeChecksumErrors++;
+				this.file.checksumError(error, patches,
+					'checksum: ' + checksum +
+					'\ncurrent: ' + current);
+				
+				// Abnormal termination
+				return;
+			}
+			else
+			{
+				// Patches the current document
+				this.file.patch(patches,
+					(DrawioFile.LAST_WRITE_WINS) ?
+					this.file.backupPatch : null);
 			}
 		}
+
+		this.file.invalidChecksum = false;
+		this.file.inConflictState = false;
+		this.file.setCurrentEtag(etag);
+		this.file.backupPatch = null;
+		
+		if (success != null)
+		{
+			success();
+		}
 	}
 	catch (e)
 	{
@@ -999,32 +1014,42 @@ DrawioFileSync.prototype.fileSaved = function(pages, lastDesc, success, error)
 			'&msg=' + encodeURIComponent(msg) + ((secret != null) ? '&secret=' + encodeURIComponent(secret) : '') +
 			((data.length < this.maxCacheEntrySize) ? '&data=' + encodeURIComponent(data) : ''),
 			mxUtils.bind(this, function(req)
+		{
+			this.file.shadowPages = pages;
+			
+			if (req.getStatus() >= 200 && req.getStatus() <= 299)
 			{
-				if (req.getStatus() >= 200 && req.getStatus() <= 299)
-				{
-					if (success != null)
-					{
-						success();
-					}
-				}
-				else if (error != null)
+				if (success != null)
 				{
-					error({message: req.getStatus()});
+					success();
 				}
-			}));
-		EditorUi.debug('Sync.fileSaved', [this], 'from', etag, 'to', current,
-			data.length, 'bytes', 'diff', diff, 'checksum', checksum);
+			}
+			else if (error != null)
+			{
+				error({message: req.getStatus()});
+			}
+		}));
+
+		if (urlParams['test'] == '1')
+		{
+			EditorUi.debug('Sync.fileSaved', [this],
+				'from', etag, 'to', current, data.length,
+				'bytes', 'diff', diff, 'checksum', checksum);
+		}
+		
 		this.file.stats.bytesSent += data.length;
 		this.file.stats.msgSent++;
 	}
 	else
 	{
+		this.file.shadowPages = pages;
+		
 		if (this.channelId == null)
 		{
 			// Checks channel ID and starts sync
 			this.start();
 		}
-		
+
 		if (success != null)
 		{
 			success();

+ 14 - 87
src/main/webapp/js/diagramly/DriveClient.js

@@ -711,7 +711,7 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
 			'revisionId': urlParams['rev'], 'supportsTeamDrives': true}),
 			mxUtils.bind(this, function(resp)
 			{
-   				this.getXmlFile(resp, null, success, error);
+   				this.getXmlFile(resp, success, error);
 			}), error);
 	}
 	else
@@ -735,54 +735,18 @@ DriveClient.prototype.getFile = function(id, success, error, readXml, readLibrar
 					if (readXml || readLibrary || resp.mimeType == this.libraryMimeType ||
 						resp.mimeType == this.xmlMimeType)
 					{
-						this.getXmlFile(resp, null, success, error, true, readLibrary);
+						this.getXmlFile(resp, success, error, true, readLibrary);
 					}
 					else
 					{
-						if (!App.GOOGLE_REALTIME)
+						if (this.isGoogleRealtimeMimeType(resp.mimeType))
 						{
-							if (this.isGoogleRealtimeMimeType(resp.mimeType))
-							{
-								this.convertRealtimeFile(resp, success, error);
-							}
-							else
-							{
-								this.getXmlFile(resp, null, success, error);
-							}
+							this.convertRealtimeFile(resp, success, error);
 						}
 						else
 						{
-							this.loadRealtime(resp, mxUtils.bind(this, function(doc)
-						    {
-								try
-								{
-									// Converts XML files to realtime including old realtime model
-									if (doc == null || doc.getModel() == null || doc.getModel().getRoot() == null ||
-										doc.getModel().getRoot().isEmpty() || (doc.getModel().getRoot().has('cells') &&
-										!doc.getModel().getRoot().has(DriveRealtime.prototype.diagramsKey)))
-						    		{
-										this.getXmlFile(resp, doc, success, error);
-						    		}
-						    		else
-						    		{
-						        		// 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)
-								{
-									error(e);
-								}
-						    }), error);
-					    }
+							this.getXmlFile(resp, success, error);
+						}
 					}
 				}
 			}
@@ -865,7 +829,7 @@ DriveClient.prototype.loadRealtime = function(resp, success, error)
  * used for import via getFile. Default is false. The optional
  * readLibrary argument is used for reading libraries. Default is false.
  */
-DriveClient.prototype.getXmlFile = function(resp, doc, success, error, ignoreMime, readLibrary)
+DriveClient.prototype.getXmlFile = function(resp, success, error, ignoreMime, readLibrary)
 {
 	var token = gapi.auth.getToken().access_token;
 	var url = resp.downloadUrl + '&access_token=' + token;
@@ -931,23 +895,7 @@ DriveClient.prototype.getXmlFile = function(resp, doc, success, error, ignoreMim
 				}
 			}
 			
-			var file = new DriveFile(this.ui, data, resp, doc);
-	
-			// 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.xmlMimeType)
-			{
-				// Overwrites mime-type (only mutable on update when uploading new content)
-				this.saveFile(file, true, mxUtils.bind(this, function(resp)
-				{
-					file.desc = resp;
-					success(file);
-				}), error, true);
-			}
-			else
-			{
-				success(file);
-			}
+			success(new DriveFile(this.ui, data, resp));
 		}
 	}), error, (resp.mimeType != null && resp.mimeType.substring(0, 6) == 'image/' &&
 		resp.mimeType.substring(0, 9) != 'image/svg') || /\.png$/i.test(resp.title) ||
@@ -1323,10 +1271,9 @@ DriveClient.prototype.redirectToNewApp = function(error, fileId)
  * @param {number} dx X-coordinate of the translation.
  * @param {number} dy Y-coordinate of the translation.
  */
-DriveClient.prototype.insertFile = function(title, data, folderId, success, error, mimeType, binary, allowRealtime)
+DriveClient.prototype.insertFile = function(title, data, folderId, success, error, mimeType, binary)
 {
-	mimeType = (mimeType != null) ? mimeType : ((App.GOOGLE_REALTIME) ? this.mimeType : this.xmlMimeType);
-	allowRealtime = (allowRealtime != null) ? allowRealtime : true;
+	mimeType = (mimeType != null) ? mimeType : this.xmlMimeType;
 	
 	var metadata =
 	{
@@ -1353,26 +1300,6 @@ DriveClient.prototype.insertFile = function(title, data, folderId, success, erro
 				error({message: mxResources.get('errorSavingFile')});
 			}
 		}
-		else if (App.GOOGLE_REALTIME && allowRealtime &&
-			this.isGoogleRealtimeMimeType(resp.mimeType))
-		{
-			this.loadRealtime(resp, mxUtils.bind(this, function(doc)
-		    {
-				if (this.user != null)
-				{
-					var file = new DriveFile(this.ui, data, resp, doc);
-				
-					// Avoids creating a new revision on first autosave of new files
-					file.lastAutosaveRevision = new Date().getTime();
-					
-		    		success(file);
-				}
-				else if (error != null)
-				{
-					error({message: mxResources.get('loggedOut')});
-				}
-		    }), error, false);
-		}
 		else
 		{
 			success(new DriveFile(this.ui, data, resp));
@@ -1818,7 +1745,7 @@ DriveClient.prototype.convertRealtimeFile = function(desc, success, error)
 			}
 			else
 			{
-				this.getXmlFile(desc, null, xmlSuccess, mxUtils.bind(this, function()
+				this.getXmlFile(desc, xmlSuccess, mxUtils.bind(this, function()
 				{
 					try
 					{
@@ -1828,18 +1755,18 @@ DriveClient.prototype.convertRealtimeFile = function(desc, success, error)
 					}
 					catch (e)
 					{
-						this.getXmlFile(desc, null, xmlSuccess, error);
+						this.getXmlFile(desc, xmlSuccess, error);
 					}
 				}));
 			}
 		}
 		catch (e)
 		{
-			this.getXmlFile(desc, null, xmlSuccess, error);
+			this.getXmlFile(desc, xmlSuccess, error);
 		}
 	}), mxUtils.bind(this, function()
 	{
-		this.getXmlFile(desc, null, xmlSuccess, error);
+		this.getXmlFile(desc, xmlSuccess, error);
 	}));
 };
 

+ 1 - 1
src/main/webapp/js/diagramly/DriveFile.js

@@ -446,7 +446,7 @@ DriveFile.prototype.getRevisions = function(success, error)
 						'revisionId': item.id
 					}), mxUtils.bind(this, function(resp)
 					{
-						this.ui.drive.getXmlFile(resp, null, mxUtils.bind(this, function(file)
+						this.ui.drive.getXmlFile(resp, mxUtils.bind(this, function(file)
 			   			{
 							itemSuccess(file.getData());
 			   			}), itemError);

+ 23 - 8
src/main/webapp/js/diagramly/EditorUi.js

@@ -2070,11 +2070,12 @@
 	 */
 	EditorUi.prototype.fileLoaded = function(file)
 	{
-		var result = false;
-		this.hideDialog();
 		var oldFile = this.getCurrentFile();
+		this.fileLoadedError = null;
 		this.setCurrentFile(null);
-	
+		var result = false;
+		this.hideDialog();
+		
 		if (oldFile != null)
 		{
 			oldFile.removeListener(this.descriptorChangedListener);
@@ -2143,6 +2144,17 @@
 					this.editor.setStatus('<span class="geStatusAlert" style="margin-left:8px;">' +
 						mxUtils.htmlEntities(mxResources.get('readOnly')) + '</span>');
 				}
+				// Handles modified state after error of loading new file
+				else if (file.isModified())
+				{
+					file.addUnsavedStatus();
+					
+					// Restores unsaved data
+					if (file.backupPatch != null)
+					{
+						file.patch([file.backupPatch]);
+					}
+				}
 				else
 				{
 					this.editor.setStatus('');
@@ -2204,6 +2216,8 @@
 			}
 			catch (e)
 			{
+				this.fileLoadedError = e;
+				
 				// Makes sure the file does not save the invalid UI model and overwrites anything important
 				if (window.console != null)
 				{
@@ -2243,7 +2257,7 @@
 					{
 						noFile();
 					}
-				}));
+				}), true);
 			}
 		}
 		else
@@ -3438,7 +3452,7 @@
 	 * @param {number} dx X-coordinate of the translation.
 	 * @param {number} dy Y-coordinate of the translation.
 	 */
-	EditorUi.prototype.handleError = function(resp, title, fn)
+	EditorUi.prototype.handleError = function(resp, title, fn, invokeFnOnClose)
 	{
 		var resume = (this.spinner != null && this.spinner.pause != null) ? this.spinner.pause() : function() {};
 		var e = (resp != null && resp.error != null) ? resp.error : resp;
@@ -3495,7 +3509,8 @@
 				}
 			}
 	
-			this.showError(title, msg, btn, fn, retry);
+			this.showError(title, msg, btn, fn, retry, null, null, null, null,
+				null, null, null, (invokeFnOnClose) ? fn : null);
 		}
 		else if (fn != null)
 		{
@@ -3509,10 +3524,10 @@
 	 * @param {number} dx X-coordinate of the translation.
 	 * @param {number} dy Y-coordinate of the translation.
 	 */
-	EditorUi.prototype.showError = function(title, msg, btn, fn, retry, btn2, fn2, btn3, fn3, w, h, hide)
+	EditorUi.prototype.showError = function(title, msg, btn, fn, retry, btn2, fn2, btn3, fn3, w, h, hide, onClose)
 	{
 		var dlg = new ErrorDialog(this, title, msg, btn || mxResources.get('ok'), fn, retry, btn2, fn2, hide, btn3, fn3);
-		this.showDialog(dlg.container, w || 340, h || 150, true, false);
+		this.showDialog(dlg.container, w || 340, h || 150, true, false, onClose);
 		dlg.init();
 	};
 	

+ 1 - 1
src/main/webapp/js/diagramly/Minimal.js

@@ -129,7 +129,7 @@ EditorUi.initMinimalTheme = function()
 	    {
 	        ui.formatWindow = new WrapperWindow(ui, mxResources.get('format'),
 	           Math.max(20, ui.diagramContainer.clientWidth - 240 - 12), 56,
-	           240, Math.min(560, graph.container.clientHeight - 10), function(container)
+	           240, Math.min(566, graph.container.clientHeight - 10), function(container)
 	        {
 	            var format = ui.createFormat(container);
 	            format.init();

Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
src/main/webapp/js/embed-static.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 1 - 1
src/main/webapp/js/reader.min.js


Rozdílová data souboru nebyla zobrazena, protože soubor je příliš velký
+ 708 - 707
src/main/webapp/js/viewer.min.js