Browse Source

5.7.2.2 release

Former-commit-id: 85b106c6686d92ead7c3d8447bb4f7f728f88b1b
David Benson 8 years ago
parent
commit
4c4d1ca664

+ 15 - 2
ChangeLog

@@ -1,6 +1,19 @@
-18-OCT-2016: 5.7.2-0
+27-OCT-2016: 5.7.2.2
+
+- Adds page view state to realtime
+- Uses mxGraph 3.7.0.0 beta 8
+- Adds diagram option in From Text dialog
+- Adds incremental find plugin
+
+26-OCT-2016: 5.7.2.1
+
+- Uses mxGraph 3.7.0.0 beta 7
+- Adds page format option in print dialog
+- Shift+alt+rubberband removes selection cells
+- Shift+click adds edges for delete in toolbar
+
+18-OCT-2016: 5.7.2.0
 
-- Switches to semantic versioning
 - Uses mxGraph 3.7.0.0 beta 6
 - Adds experimental sql plugin
 

+ 1 - 1
VERSION

@@ -1 +1 @@
-5.7.2-0
+5.7.2.2

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


+ 4 - 7
etc/sandstorm/Makefile

@@ -5,14 +5,14 @@ SANDSTORM_CAPNP_DIR=/opt/sandstorm/latest/usr/include
 
 .PHONEY: all clean dev
 
-package.spk: server sandstorm-pkgdef.capnp empty
+package.spk: server sandstorm-pkgdef.capnp
 	spk pack --keyring="drawio.key" package.spk
 
-dev: server sandstorm-pkgdef.capnp empty
+dev: server sandstorm-pkgdef.capnp
 	spk dev
 
 clean:
-	rm -rf tmp server package.spk empty
+	rm -rf tmp server package.spk
 
 tmp/genfiles:
 	@mkdir -p tmp
@@ -20,7 +20,4 @@ tmp/genfiles:
 	@touch tmp/genfiles
 
 server: tmp/genfiles server.c++
-	$(CXX) -static server.c++ tmp/sandstorm/*.capnp.c++ -o server $(CXXFLAGS2) `pkg-config capnp-rpc --cflags --libs`
-
-empty:
-	mkdir -p empty
+	$(CXX) -static server.c++ tmp/sandstorm/*.capnp.c++ -o server $(CXXFLAGS2) `pkg-config capnp-rpc --cflags --libs`

+ 0 - 2
etc/sandstorm/sandstorm-pkgdef.capnp

@@ -86,8 +86,6 @@ const pkgdef :Spk.PackageDefinition = (
       
       ( packagePath = "client", sourcePath = "client" ),
       # Map client directory at "/client".
-
-      ( sourcePath = "." )
     ]
   ),
 

+ 7 - 11
src/com/mxgraph/io/gliffy/importer/GliffyDiagramConverter.java

@@ -133,9 +133,7 @@ public class GliffyDiagramConverter {
 					sortObjectsByOrder(obj.children);
 
 				for (Object child : obj.children) {
-					//do not import text as a child object, use inline text
-					if(!child.isText())
-						importObject(child, obj);
+					importObject(child, obj);
 				}
 			}
 		} else {
@@ -244,7 +242,7 @@ public class GliffyDiagramConverter {
 				vertices.put(object.id, object);
 			
 			// don't collect for swimlanes and mindmaps, their children are treated differently
-			if (object.hasChildren() && !object.isSwimlane() && !object.isMindmap())
+			if (object.isGroup())
 				collectVerticesAndConvert(vertices, object.children, object);
 		}
 	}
@@ -284,6 +282,10 @@ public class GliffyDiagramConverter {
 
 		mxGeometry geometry = new mxGeometry((int) gliffyObject.x, (int) gliffyObject.y, (int) gliffyObject.width, (int) gliffyObject.height);
 		cell.setGeometry(geometry);
+		
+		String text;
+		Object textObject = null;
+		String link = null;
 
 		Graphic graphic = null;
 		if (gliffyObject.isGroup()) {
@@ -292,17 +294,11 @@ public class GliffyDiagramConverter {
 		} else {
 			// groups don't have graphic
 			graphic = gliffyObject.getGraphic();
+			textObject = gliffyObject.getTextObject();
 		}
 
-		String text;
-		Object textObject = gliffyObject.getTextObject();
-		
-		String link = null;
-
 		if (graphic != null) {
 			link = gliffyObject.getLink();
-			
-			
 
 			if (gliffyObject.isShape()) {
 				GliffyShape shape = graphic.Shape;

+ 13 - 1
src/com/mxgraph/io/vsdx/mxVsdxUtils.java

@@ -8,6 +8,8 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 import org.w3c.dom.Element;
 import org.w3c.dom.Node;
@@ -23,6 +25,8 @@ public class mxVsdxUtils
 	private static final double CENTIMETERS_PER_INCHES = 2.54;
 	
 	public static final double conversionFactor = screenCoordinatesPerCm * CENTIMETERS_PER_INCHES;
+	
+	private static final Logger log = Logger.getLogger(mxVsdxUtils.class.getName());
 
 	/**
 	 * Checks if the NodeList has a Node with name = tag.
@@ -163,7 +167,15 @@ public class mxVsdxUtils
 
 			if(!key.equals(mxConstants.STYLE_SHAPE) || (!styleMap.get(key).startsWith("image") && !styleMap.get(key).startsWith("rounded=")))
 			{
-				style = style + key + asig;
+				try
+				{
+					style = style + key + asig;
+				}
+				catch (Exception e)
+				{
+					log.log(Level.SEVERE, "mxVsdxUtils.getStyleString," + e.toString() + ",style.length=" + style.length() +
+							",key.length=" + key.length() + ",asig.length=" + asig.length());
+				}
 			}
 
 			style = style + value + ";";

+ 1 - 1
src/com/mxgraph/online/EmbedServlet2.java

@@ -85,7 +85,7 @@ public class EmbedServlet2 extends HttpServlet
 	}
 
 	/**
-	 * @see HttpServlet#HttpServlet()
+	 * Sets up collection of stencils
 	 */
 	public static void initLibraries(HashMap<String, String[]> libraries)
 	{

+ 1 - 1
war/WEB-INF/logging.properties

@@ -10,4 +10,4 @@
 #
 
 # Set the default logging level for all loggers to WARNING
-.level = CONFIG
+.level = SEVERE

+ 1 - 1
war/cache.manifest

@@ -1,7 +1,7 @@
 CACHE MANIFEST
 
 # THIS FILE WAS GENERATED. DO NOT MODIFY!
-# 10/18/2016 03:18 PM
+# 10/27/2016 03:59 PM
 
 /app.html
 /index.html?offline=1

File diff suppressed because it is too large
+ 1097 - 1083
war/js/app.min.js


File diff suppressed because it is too large
+ 796 - 793
war/js/atlas-viewer.min.js


File diff suppressed because it is too large
+ 770 - 760
war/js/atlas.min.js


+ 14 - 4
war/js/diagramly/App.js

@@ -148,7 +148,7 @@ App.pluginRegistry = {'4xAKTrabTpTzahoLthkwPNUn': '/plugins/explore.js',
 	'acj': '/plugins/connectJira.js', 'voice': '/plugins/voice.js',
 	'tips': '/plugins/tooltips.js', 'svgdata': '/plugins/svgdata.js',
 	'doors': '/plugins/doors.js', 'electron': 'plugins/electron.js',
-	'tags': '/plugins/tags.js', 'sql': '/plugins/sql.js'};
+	'tags': '/plugins/tags.js', 'sql': '/plugins/sql.js', 'find': '/plugins/find.js'};
 
 /**
  * Function: authorize
@@ -391,7 +391,8 @@ App.main = function(callback)
 				var severity = (message.indexOf('NetworkError') >= 0 || message.indexOf('SecurityError') >= 0 ||
 					message.indexOf('NS_ERROR_FAILURE') >= 0 || message.indexOf('out of memory') >= 0) ?
 					'CONFIG' : 'SEVERE';
-	    		img.src = 'https://log.draw.io/log?severity=' + severity + '&v=' + encodeURIComponent(EditorUi.VERSION) +
+				var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+	    		img.src = logDomain + '/log?severity=' + severity + '&v=' + encodeURIComponent(EditorUi.VERSION) +
 	    			'&msg=clientError:' + encodeURIComponent(message) + ':url:' + encodeURIComponent(window.location.href) +
 	    			':lnum:' + encodeURIComponent(linenumber) + 
 	    			((colno != null) ? ':colno:' + encodeURIComponent(colno) : '') +
@@ -680,6 +681,9 @@ App.prototype.init = function()
 {
 	EditorUi.prototype.init.apply(this, arguments);
 	
+	var host = window.location.host;
+	
+	
 	/**
 	 * Overrides export dialog for using cloud storage save.
 	 */
@@ -2284,7 +2288,8 @@ App.prototype.start = function()
 			try
 			{
 				var img = new Image();
-	    		img.src = 'https://log.draw.io/log?v=' + encodeURIComponent(EditorUi.VERSION) +
+				var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+	    		img.src = logDomain + '/log?v=' + encodeURIComponent(EditorUi.VERSION) +
 	    			'&msg=errorLoadingFile:url:' + encodeURIComponent(window.location.href) +
     				((e != null && e.message != null) ? ':err:' + encodeURIComponent(e.message) : '') +
     				((e != null && e.stack != null) ? '&stack=' + encodeURIComponent(e.stack) : '');
@@ -3858,6 +3863,10 @@ App.prototype.libraryLoaded = function(file, images)
 				
 				addCells(cells, bounds);
 			}
+			else if (graph.getRubberband().isActive())
+			{
+				graph.getRubberband().execute(evt);
+			}
 			else
 			{
 				this.showError(mxResources.get('error'), mxResources.get('nothingIsSelected'), mxResources.get('ok'));
@@ -4228,7 +4237,8 @@ App.prototype.fileLoaded = function(file)
 //		        	if (!this.isOffline())
 //		        	{
 //	        			var img = new Image();
-//	        			img.src = 'https://log.draw.io/log?msg=storageMode:' + encodeURIComponent(file.getMode()) +
+						var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+//	        			img.src = logDomain + '/log?msg=storageMode:' + encodeURIComponent(file.getMode()) +
 //        				'&v=' + encodeURIComponent(EditorUi.VERSION);
 //		        	}
 //	        	}

+ 420 - 116
war/js/diagramly/Dialogs.js

@@ -2873,94 +2873,156 @@ var BackgroundImageDialog = function(editorUi, applyFn)
  */
 var ParseDialog = function(editorUi, title)
 {
-	function parse(text)
+	function parse(text, asList)
 	{
 		var lines = text.split('\n');
-		var vertices = new Object();
-		var cells = [];
 		
-		function getOrCreateVertex(id)
+		if (asList)
 		{
-			var vertex = vertices[id];
-
-			if (vertex == null)
+			if (lines.length > 0)
 			{
-				vertex = new mxCell(id, new mxGeometry(0, 0, 80, 30));
-				vertex.vertex = true;
-				vertices[id] = vertex;
-				cells.push(vertex);
+				var graph = editorUi.editor.graph;
+				
+				var listCell = new mxCell(lines[0], new mxGeometry(0, 0, 160, 26 + 4),
+				    'swimlane;fontStyle=1;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=none;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;');
+				listCell.vertex = true;
+				
+				var size = graph.getPreferredSizeForCell(listCell);
+		
+	   			if (size != null && listCell.geometry.width < size.width + 10)
+	   			{
+	   				listCell.geometry.width = size.width + 10;
+	   			}
+				
+				if (lines.length > 1)
+				{
+					for (var i = 1; i < lines.length; i++)
+					{
+						if (lines[i] == '--')
+						{
+							var divider = new mxCell('', new mxGeometry(0, 0, 40, 8), 'line;strokeWidth=1;fillColor=none;align=left;verticalAlign=middle;spacingTop=-1;spacingLeft=3;spacingRight=3;rotatable=0;labelPosition=right;points=[];portConstraint=eastwest;');
+							divider.vertex = true;
+							listCell.geometry.height += divider.geometry.height;
+							listCell.insert(divider);
+						}
+						else if (lines[i].length > 0 && lines[i].charAt(0) != ';')
+						{
+							var field = new mxCell(lines[i], new mxGeometry(0, 0, 60, 26), 'text;strokeColor=none;fillColor=none;align=left;verticalAlign=top;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;');
+							field.vertex = true;
+							
+							var size = graph.getPreferredSizeForCell(field);
+		   			
+				   			if (size != null && field.geometry.width < size.width)
+				   			{
+				   				field.geometry.width = size.width;
+				   			}
+							
+				   			listCell.geometry.width = Math.max(listCell.geometry.width, field.geometry.width);
+							listCell.geometry.height += field.geometry.height;
+							listCell.insert(field);
+						}
+					}
+				}
+				
+				var view = graph.view;
+				var bds = graph.getGraphBounds();
+				
+				// Computes unscaled, untranslated graph bounds
+				var x = Math.ceil(Math.max(0, bds.x / view.scale - view.translate.x) + 4 * graph.gridSize);
+				var y = Math.ceil(Math.max(0, (bds.y + bds.height) / view.scale - view.translate.y) + 4 * graph.gridSize);
+	
+				graph.setSelectionCells(graph.importCells([listCell], x, y));
+				graph.scrollCellToVisible(graph.getSelectionCell());
 			}
+		}
+		else
+		{		
+			var vertices = new Object();
+			var cells = [];
 			
-			return vertex;
-		};
-		
-		for (var i = 0; i < lines.length; i++)
-		{
-			if (lines[i].charAt(0) != ';')
+			function getOrCreateVertex(id)
 			{
-				var values = lines[i].split('->');
+				var vertex = vertices[id];
+	
+				if (vertex == null)
+				{
+					vertex = new mxCell(id, new mxGeometry(0, 0, 80, 30));
+					vertex.vertex = true;
+					vertices[id] = vertex;
+					cells.push(vertex);
+				}
 				
-				if (values.length == 2)
+				return vertex;
+			};
+			
+			for (var i = 0; i < lines.length; i++)
+			{
+				if (lines[i].charAt(0) != ';')
 				{
-					var source = getOrCreateVertex(values[0]);
-					var target = getOrCreateVertex(values[1]);
+					var values = lines[i].split('->');
 					
-					var edge = new mxCell('', new mxGeometry());
-					edge.edge = true;
-					source.insertEdge(edge, true);
-					target.insertEdge(edge, false);
-					cells.push(edge);
+					if (values.length == 2)
+					{
+						var source = getOrCreateVertex(values[0]);
+						var target = getOrCreateVertex(values[1]);
+						
+						var edge = new mxCell('', new mxGeometry());
+						edge.edge = true;
+						source.insertEdge(edge, true);
+						target.insertEdge(edge, false);
+						cells.push(edge);
+					}
 				}
 			}
-		}
-		
-		if (cells.length > 0)
-		{
-			var container = document.createElement('div');
-			container.style.visibility = 'hidden';
-			document.body.appendChild(container);
-			var graph = new Graph(container);
 			
-			graph.getModel().beginUpdate();
-			try
+			if (cells.length > 0)
 			{
-				cells = graph.importCells(cells);
+				var container = document.createElement('div');
+				container.style.visibility = 'hidden';
+				document.body.appendChild(container);
+				var graph = new Graph(container);
 				
-				for (var i = 0; i < cells.length; i++)
+				graph.getModel().beginUpdate();
+				try
 				{
-					if (graph.getModel().isVertex(cells[i]))
+					cells = graph.importCells(cells);
+					
+					for (var i = 0; i < cells.length; i++)
 					{
-						var size = graph.getPreferredSizeForCell(cells[i]);
-						cells[i].geometry.width = Math.max(cells[i].geometry.width, size.width);
-						cells[i].geometry.height = Math.max(cells[i].geometry.height, size.height);
+						if (graph.getModel().isVertex(cells[i]))
+						{
+							var size = graph.getPreferredSizeForCell(cells[i]);
+							cells[i].geometry.width = Math.max(cells[i].geometry.width, size.width);
+							cells[i].geometry.height = Math.max(cells[i].geometry.height, size.height);
+						}
 					}
+	
+					var layout = new mxFastOrganicLayout(graph);
+					layout.disableEdgeStyle = false;
+					layout.forceConstant = 120;
+					layout.execute(graph.getDefaultParent());
+					
+					graph.moveCells(cells, 20, 20);
+				}
+				finally
+				{
+					graph.getModel().endUpdate();
 				}
-
-				var layout = new mxFastOrganicLayout(graph);
-				layout.disableEdgeStyle = false;
-				layout.forceConstant = 120;
-				layout.execute(graph.getDefaultParent());
 				
-				graph.moveCells(cells, 20, 20);
-			}
-			finally
-			{
-				graph.getModel().endUpdate();
+				graph.clearCellOverlays();
+				var view = editorUi.editor.graph.view;
+				var bds = editorUi.editor.graph.getGraphBounds();
+				
+				// Computes unscaled, untranslated graph bounds
+				var x = Math.ceil(Math.max(0, bds.x / view.scale - view.translate.x) + graph.gridSize);
+				var y = Math.ceil(Math.max(0, (bds.y + bds.height) / view.scale - view.translate.y) + 4 * graph.gridSize);
+				editorUi.editor.graph.setSelectionCells(editorUi.editor.graph.importCells(
+						graph.getModel().getChildren(graph.getDefaultParent()), x, y));
+				editorUi.editor.graph.scrollCellToVisible(editorUi.editor.graph.getSelectionCell());
+				
+				graph.destroy();
+				container.parentNode.removeChild(container);
 			}
-			
-			graph.clearCellOverlays();
-			var view = editorUi.editor.graph.view;
-			var bds = editorUi.editor.graph.getGraphBounds();
-			
-			// Computes unscaled, untranslated graph bounds
-			var x = Math.ceil(Math.max(0, bds.x / view.scale - view.translate.x) + graph.gridSize);
-			var y = Math.ceil(Math.max(0, (bds.y + bds.height) / view.scale - view.translate.y) + 4 * graph.gridSize);
-			editorUi.editor.graph.setSelectionCells(editorUi.editor.graph.importCells(
-					graph.getModel().getChildren(graph.getDefaultParent()), x, y));
-			editorUi.editor.graph.scrollCellToVisible(editorUi.editor.graph.getSelectionCell());
-			
-			graph.destroy();
-			container.parentNode.removeChild(container);
 		}
 	};
 	
@@ -2972,8 +3034,20 @@ var ParseDialog = function(editorUi, title)
 	textarea.style.width = '100%';
 	textarea.style.height = '354px';
 	textarea.style.marginBottom = '16px';
+		
+	var diagramCheckBox = document.createElement('input');
+	diagramCheckBox.setAttribute('type', 'checkbox');
+	
+	function getDefaultValue()
+	{
+		return  (!diagramCheckBox.checked) ?
+			'Person\n-name: String\n-birthDate: Date\n--\n+getName(): String\n+setName(String): void\n+isBirthday(): boolean' :
+			';Example:\na->b\nb->c\nc->a\n';
+	};
 	
-	textarea.value = ';example\na->b\nb->c\nc->a\n';
+	var defaultValue = getDefaultValue();
+
+	textarea.value = defaultValue;
 	div.appendChild(textarea);
 	
 	this.init = function()
@@ -3009,13 +3083,38 @@ var ParseDialog = function(editorUi, title)
 		textarea.addEventListener('dragover', handleDragOver, false);
 		textarea.addEventListener('drop', handleDrop, false);
 	}
+
+	div.appendChild(diagramCheckBox);
+	
+	mxEvent.addListener(diagramCheckBox, 'change', function()
+	{
+		var newDefaultValue = getDefaultValue();
+		
+		if (textarea.value.length == 0 || textarea.value == defaultValue)
+		{
+			defaultValue = newDefaultValue;
+			textarea.value = defaultValue;
+		}
+	});
+	
+	var span = document.createElement('span');
+	mxUtils.write(span, ' ' + mxResources.get('diagram'));
+	span.style.marginRight = '10px';
+	div.appendChild(span);
 	
 	var cancelBtn = mxUtils.button(mxResources.get('close'), function()
 	{
-		editorUi.confirm(mxResources.get('areYouSure'), function()
+		if (textarea.value == defaultValue)
 		{
 			editorUi.hideDialog();
-		});
+		}
+		else
+		{
+			editorUi.confirm(mxResources.get('areYouSure'), function()
+			{
+				editorUi.hideDialog();
+			});
+		}
 	});
 	
 	cancelBtn.className = 'geBtn';
@@ -3028,7 +3127,7 @@ var ParseDialog = function(editorUi, title)
 	var okBtn = mxUtils.button(mxResources.get('insert'), function()
 	{
 		editorUi.hideDialog();
-		parse(textarea.value);
+		parse(textarea.value, !diagramCheckBox.checked);
 	});
 	div.appendChild(okBtn);
 	
@@ -4358,22 +4457,23 @@ PrintDialog.prototype.create = function(editorUi)
 	
 	var allPagesRadio = document.createElement('input');
 	allPagesRadio.style.cssText = 'margin-right:8px;margin-bottom:8px;';
-	
 	allPagesRadio.setAttribute('value', 'all');
 	allPagesRadio.setAttribute('type', 'radio');
-	allPagesRadio.setAttribute('name', 'pages');
+	allPagesRadio.setAttribute('name', 'pages-printdialog');
+	
 	pagesSection.appendChild(allPagesRadio);
 
 	var span = document.createElement('span');
 	mxUtils.write(span, mxResources.get('printAllPages'));
 	pagesSection.appendChild(span);
-	
+
 	mxUtils.br(pagesSection);
 
 	// Pages ... to ...
 	var pagesRadio = allPagesRadio.cloneNode(true);
-	pagesRadio.setAttribute('value', 'range');pagesSection.appendChild(pagesRadio);
 	allPagesRadio.setAttribute('checked', 'checked');
+	pagesRadio.setAttribute('value', 'range');
+	pagesSection.appendChild(pagesRadio);
 	
 	var span = document.createElement('span');
 	mxUtils.write(span, mxResources.get('pages') + ':');
@@ -4393,7 +4493,7 @@ PrintDialog.prototype.create = function(editorUi)
 	
 	var pagesToInput = pagesFromInput.cloneNode(true);
 	pagesSection.appendChild(pagesToInput);
-	
+
 	mxEvent.addListener(pagesFromInput, 'focus', function()
 	{
 		pagesRadio.checked = true;
@@ -4406,14 +4506,14 @@ PrintDialog.prototype.create = function(editorUi)
 	
 	function validatePageRange()
 	{
-		pagesToInput.value = Math.min(pageCount, Math.max(parseInt(pagesToInput.value), parseInt(pagesFromInput.value)));
-		pagesFromInput.value = Math.min(pageCount, Math.min(parseInt(pagesToInput.value), parseInt(pagesFromInput.value)));
+		pagesToInput.value = Math.max(1, Math.min(pageCount, Math.max(parseInt(pagesToInput.value), parseInt(pagesFromInput.value))));
+		pagesFromInput.value = Math.max(1, Math.min(pageCount, Math.min(parseInt(pagesToInput.value), parseInt(pagesFromInput.value))));
 	};
 	
 	mxEvent.addListener(pagesFromInput, 'change', validatePageRange);
 	mxEvent.addListener(pagesToInput, 'change', validatePageRange);
 	
-	if (false && editorUi.pages != null)
+	if (editorUi.pages != null)
 	{
 		pageCount = editorUi.pages.length;
 
@@ -4550,10 +4650,23 @@ PrintDialog.prototype.create = function(editorUi)
 	// Page scale ...
 	var pageScaleSection = document.createElement('div');
 
-	var span = document.createElement('span');
+	var span = document.createElement('div');
+	span.style.fontWeight = 'bold';
+	span.style.marginBottom = '12px';
 	mxUtils.write(span, mxResources.get('paperSize'));
 	pageScaleSection.appendChild(span);
 	
+	var span = document.createElement('div');
+	span.style.marginBottom = '12px';
+
+	var accessor = PageSetupDialog.addPageFormatPanel(span, 'printdialog',
+		editorUi.editor.graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT);
+	pageScaleSection.appendChild(span);
+	
+	var span = document.createElement('span');
+	mxUtils.write(span, mxResources.get('pageScale'));
+	pageScaleSection.appendChild(span);
+	
 	var pageScaleInput = document.createElement('input');
 	pageScaleInput.style.cssText = 'margin:0 8px 0 8px;';
 	pageScaleInput.setAttribute('value', '100 %');
@@ -4564,7 +4677,7 @@ PrintDialog.prototype.create = function(editorUi)
 	
 	// Buttons
 	var buttons = document.createElement('div');
-	buttons.style.cssText = 'text-align:right;margin:36px 0 0 0;';
+	buttons.style.cssText = 'text-align:right;margin:42px 0 0 0;';
 	
 	// Overall scale for print-out to account for print borders in dialogs etc
 	function preview(print)
@@ -4579,55 +4692,246 @@ PrintDialog.prototype.create = function(editorUi)
 		
 		// Workaround to match available paper size in actual print output
 		printScale *= 0.75;
-
-		// Negative coordinates are cropped or shifted if page visible
-		var gb = graph.getGraphBounds();
-		var border = 0;
-		var x0 = 0;
-		var y0 = 0;
-
-		var pf = graph.pageFormat || mxConstants.PAGE_FORMAT_A4_PORTRAIT;
-		var scale = 1 / graph.pageScale;
-		var autoOrigin = fitRadio.checked;
-
-		if (autoOrigin)
+		
+		function printGraph(thisGraph, pv, forcePageBreaks)
 		{
-			var h = parseInt(sheetsAcrossInput.value);
-			var v = parseInt(sheetsDownInput.value);
+			// Negative coordinates are cropped or shifted if page visible
+			var gb = thisGraph.getGraphBounds();
+			var border = 0;
+			var x0 = 0;
+			var y0 = 0;
+	
+			var pf = accessor.get();
+			var scale = 1 / thisGraph.pageScale;
+			var autoOrigin = fitRadio.checked;
+	
+			if (autoOrigin)
+			{
+				var h = parseInt(sheetsAcrossInput.value);
+				var v = parseInt(sheetsDownInput.value);
+				
+				scale = Math.min((pf.height * v) / (gb.height / thisGraph.view.scale),
+					(pf.width * h) / (gb.width / thisGraph.view.scale));
+			}
+			else
+			{
+				scale = parseInt(zoomInput.value) / (100 * thisGraph.pageScale);
+				
+				if (isNaN(scale))
+				{
+					printScale = 1 / thisGraph.pageScale;
+					zoomInput.value = '100 %';
+				}
+			}
+	
+			// Applies print scale
+			pf = mxRectangle.fromRectangle(pf);
+			pf.width = Math.ceil(pf.width * printScale);
+			pf.height = Math.ceil(pf.height * printScale);
+			scale *= printScale;
+			
+			// Starts at first visible page
+			if (!autoOrigin && thisGraph.pageVisible)
+			{
+				var layout = thisGraph.getPageLayout();
+				x0 -= layout.x * pf.width;
+				y0 -= layout.y * pf.height;
+			}
+			else
+			{
+				autoOrigin = true;
+			}
+
+			if (pv == null)
+			{
+				pv = PrintDialog.createPrintPreview(thisGraph, scale, pf, border, x0, y0, autoOrigin);
+				pv.pageSelector = false;
+				pv.mathEnabled = false;
+				
+				if (typeof(MathJax) !== 'undefined')
+				{
+					// Adds class to ignore if math is disabled
+					var printPreviewRenderPage = pv.renderPage;
+					
+					pv.renderPage = function(w, h, dx, dy, content, pageNumber)
+					{
+						var result = printPreviewRenderPage.apply(this, arguments);
+						
+						if (this.graph.mathEnabled)
+						{
+							this.mathEnabled = true;
+						}
+						else
+						{
+							result.className = 'geDisableMathJax';
+						}
+						
+						return result;
+					};
+				}
+				
+				pv.open(null, null, forcePageBreaks, true);
+			}
+			else
+			{				
+				var bg = thisGraph.background;
+				
+				if (bg == null || bg == '' || bg == mxConstants.NONE)
+				{
+					bg = '#ffffff';
+				}
+				
+				pv.backgroundColor = bg;
+				pv.autoOrigin = autoOrigin;
+				pv.appendGraph(thisGraph, scale, x0, y0, forcePageBreaks, true);
+			}
 			
-			scale = Math.min((pf.height * v) / (gb.height / graph.view.scale),
-				(pf.width * h) / (gb.width / graph.view.scale));
+			return pv;
+		};
+		
+		var pagesFrom = pagesFromInput.value;
+		var pagesTo = pagesToInput.value;
+		var ignorePages = !allPagesRadio.checked;
+		var pv = null;
+					
+		if (ignorePages)
+		{
+			ignorePages = pagesFrom == currentPage && pagesTo == currentPage;
 		}
-		else
+		
+		if (!ignorePages && editorUi.pages != null && editorUi.pages.length)
 		{
-			scale = parseInt(zoomInput.value) / (100 * graph.pageScale);
+			var i0 = 0;
+			var imax = editorUi.pages.length - 1;
 			
-			if (isNaN(scale))
+			if (!allPagesRadio.checked)
 			{
-				printScale = 1 / graph.pageScale;
-				zoomInput.value = '100 %';
+				i0 = parseInt(pagesFrom) - 1;
+				imax = parseInt(pagesTo) - 1;
 			}
-		}
+			
+			for (var i = i0; i <= imax; i++)
+			{
+				var page = editorUi.pages[i];
+				var tempGraph = (page == editorUi.currentPage) ? graph : null;
+				
+				if (tempGraph == null)
+				{
+					tempGraph = editorUi.createTemporaryGraph(graph.getStylesheet());
 
-		// Applies print scale
-		pf = mxRectangle.fromRectangle(pf);
-		pf.width = Math.ceil(pf.width * printScale);
-		pf.height = Math.ceil(pf.height * printScale);
-		scale *= printScale;
-		
-		// Starts at first visible page
-		if (!autoOrigin && graph.pageVisible)
-		{
-			var layout = graph.getPageLayout();
-			x0 -= layout.x * pf.width;
-			y0 -= layout.y * pf.height;
+					// Restores graph settings that are relevant for printing
+					var pageVisible = true;
+					var mathEnabled = false;
+					var bg = null;
+					var bgImage = null;
+					
+					if (page.viewState == null && page.mapping == null)
+					{
+						// Workaround to extract view state from XML node
+						// This changes the state of the page and parses
+						// the XML for the graph model even if not needed.
+						if (page.root == null)
+						{
+							editorUi.updatePageRoot(page);
+						}
+					}
+					
+					if (page.viewState != null)
+					{
+						pageVisible = page.viewState.pageVisible;
+						mathEnabled = page.viewState.mathEnabled;
+						bg = page.viewState.background;
+						bgImage = page.viewState.backgroundImage;
+					}
+					else if (page.mapping != null && page.mapping.diagramMap != null)
+					{
+						// Default pageVisible in realtime is true
+						mathEnabled = page.mapping.diagramMap.get('mathEnabled') != '0';
+						bg = page.mapping.diagramMap.get('background');
+						
+						var temp = page.mapping.diagramMap.get('backgroundImage');
+						bgImage = (temp != null && temp.length > 0) ? JSON.parse(temp) : null;
+					}
+				
+					tempGraph.background = bg;
+					tempGraph.backgroundImage = (bgImage != null) ? new mxImage(bgImage.src, bgImage.width, bgImage.height) : null;
+					tempGraph.pageVisible = pageVisible;
+					tempGraph.mathEnabled = mathEnabled;
+					
+					// Redirects placeholders to current page
+					var graphGetGlobalVariable = tempGraph.getGlobalVariable;
+	
+					tempGraph.getGlobalVariable = function(name)
+					{
+						if (name == 'page')
+						{
+							return page.getName();
+						}
+						else if (name == 'pagenumber')
+						{
+							return i + 1;
+						}
+						
+						return graphGetGlobalVariable.apply(this, arguments);
+					};
+					
+					document.body.appendChild(tempGraph.container);
+					editorUi.updatePageRoot(page);
+					tempGraph.model.setRoot(page.root);
+				}
+
+				pv = printGraph(tempGraph, pv, i != imax);
+
+				if (tempGraph != graph)
+				{
+					tempGraph.container.parentNode.removeChild(tempGraph.container);
+				}
+			}
 		}
 		else
 		{
-			autoOrigin = true;
+			pv = printGraph(graph);
+		}
+		
+		if (pv.mathEnabled)
+		{
+			var doc = pv.wnd.document;
+	
+			doc.writeln('<script type="text/x-mathjax-config">');
+			doc.writeln('MathJax.Hub.Config({');
+			doc.writeln('messageStyle: "none",');
+			doc.writeln('jax: ["input/TeX", "input/MathML", "input/AsciiMath", "output/HTML-CSS"],');
+			doc.writeln('extensions: ["tex2jax.js", "mml2jax.js", "asciimath2jax.js"],');
+			doc.writeln('TeX: {');
+			doc.writeln('extensions: ["AMSmath.js", "AMSsymbols.js", "noErrors.js", "noUndefined.js"]');
+			doc.writeln('},');
+						// Ignores math in in-place editor
+			doc.writeln('tex2jax: {');
+			doc.writeln('	ignoreClass: "geDisableMathJax"');
+		  	doc.writeln('},');
+		  	doc.writeln('asciimath2jax: {');
+			doc.writeln('	ignoreClass: "geDisableMathJax"');
+		  	doc.writeln('}');
+			doc.writeln('});');
+			
+			// Adds asynchronous printing when MathJax finished rendering
+			if (print)
+			{
+				doc.writeln('MathJax.Hub.Queue(function () {');
+				doc.writeln('window.print();');
+				doc.writeln('});');
+			}
+			
+			doc.writeln('</script>');
+			doc.writeln('<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js"></script>');
 		}
 		
-		return PrintDialog.showPreview(PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin, print), print);
+		pv.closeDocument();
+		
+		if (!pv.mathEnabled && print)
+		{
+			PrintDialog.printPreview(pv);
+		}
 	};
 	
 	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()

+ 32 - 0
war/js/diagramly/DriveRealtime.js

@@ -636,6 +636,26 @@ DriveRealtime.prototype.installUiChangeListeners = function()
 	});
 	
 	this.ui.addListener('foldingEnabledChanged', this.foldingEnabledListener);
+		
+	this.graph.addListener('shadowVisibleChanged', this.shadowVisibleListener);
+	
+	this.pageVisibleListener = mxUtils.bind(this, function(sender, evt)
+	{
+		if (!this.ignorePageVisibleChanged)
+		{
+			try
+			{
+				this.setFileModified();
+				this.getDiagramMap().set('pageVisible', (this.graph.pageVisible) ? '1' : '0');
+			}
+			catch (e)
+			{
+				this.ui.handleError(e);
+			}
+		}
+	});
+	
+	this.ui.addListener('pageViewChanged', this.pageVisibleListener);
 	
 	this.backgroundImageListener = mxUtils.bind(this, function(sender, evt)
 	{
@@ -1676,6 +1696,12 @@ DriveRealtime.prototype.destroy = function(unloading)
 		this.foldingEnabledListener = null;
 	}
 
+	if (this.pageVisibleListener != null)
+	{
+		this.ui.removeListener(this.pageVisibleListener);
+		this.pageVisibleListener = null;
+	}
+
 	if (this.backgroundImageListener != null)
 	{
 		this.ui.removeListener(this.backgroundImageListener);
@@ -1717,6 +1743,12 @@ DriveRealtime.prototype.destroy = function(unloading)
 		this.model.removeListener(this.graphModelChangeListener);
 		this.graphModelChangeListener = null;
 	}
+	
+	if (this.pageChangeListener != null)
+	{
+		this.ui.editor.removeListener(this.pageChangeListener);
+		this.pageChangeListener = null;
+	}
 
 	if (this.viewStateListener != null)
 	{

+ 3 - 59
war/js/diagramly/EditorUi.js

@@ -79,7 +79,7 @@
 		Editor.doMathJaxRender = function(container)
 		{
 			MathJax.Hub.Queue(['Typeset', MathJax.Hub, container]);
-		}
+		};
 
 		// Disables global typesetting and messages on startup, adds queue for
 		// asynchronous rendering while MathJax is loading
@@ -319,63 +319,6 @@
 			}
 		}
 	};
-	
-	// Overrides to call print asynchronously after (disables immediate print here)
-	if (window.PrintDialog)
-	{
-		var printDialogShowPrintPreview = PrintDialog.showPreview;
-		PrintDialog.showPreview = function(preview, print)
-		{
-			if (typeof(MathJax) !== 'undefined' && preview.graph.mathEnabled)
-			{
-				print = false;
-			}
-			
-			return printDialogShowPrintPreview.apply(this, arguments);
-		};
-	
-		// Adds MathJax to print preview
-		var printDialogCreatePrintPreview = PrintDialog.createPrintPreview;
-		PrintDialog.createPrintPreview = function(graph, scale, pf, border, x0, y0, autoOrigin, print)
-		{
-			var preview = printDialogCreatePrintPreview.apply(this, arguments);
-			
-			if (typeof(MathJax) !== 'undefined' && graph.mathEnabled)
-			{
-				// Using writePostfix is workaround for blocking of DOM processing
-				// in Chrome if using writeHead for injecting async script tag
-				var writePostfix = preview.writePostfix;
-				preview.writePostfix = function(doc, css)
-				{
-					writePostfix.apply(this, arguments);
-					
-					// Disables status message in print output
-					doc.writeln('<script type="text/x-mathjax-config">');
-					doc.writeln('MathJax.Hub.Config({');
-					doc.writeln('messageStyle: "none",');
-					doc.writeln('jax: ["input/TeX", "input/MathML", "input/AsciiMath", "output/HTML-CSS"],');
-					doc.writeln('extensions: ["tex2jax.js", "mml2jax.js", "asciimath2jax.js"],');
-					doc.writeln('TeX: {');
-					doc.writeln('	extensions: ["AMSmath.js", "AMSsymbols.js", "noErrors.js", "noUndefined.js"]');
-					doc.writeln('}');
-					doc.writeln('});');
-					
-					// Adds asynchronous printing when MathJax finished rendering
-					if (print)
-					{
-						doc.writeln('MathJax.Hub.Queue(function () {');
-						doc.writeln('window.print();');
-						doc.writeln('});');
-					}
-					
-					doc.writeln('</script>');
-					doc.writeln('<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/2.6-latest/MathJax.js"></script>');
-				};
-			}
-	
-			return preview;
-		};
-	}
 
 	/**
 	 * Global switches for the export dialog.
@@ -1768,7 +1711,8 @@
 								var img = new Image();
 								
 								// Timestamp is added to bypass client-side cache
-								img.src = 'https://log.draw.io/log?severity=CONFIG&msg=imgur-published:' + res.data.id + '&v=' +
+								var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+								img.src = logDomain + '/log?severity=CONFIG&msg=imgur-published:' + res.data.id + '&v=' +
 									encodeURIComponent(EditorUi.VERSION) + '&ts=' + new Date().getTime();
 					    	}
 					    	catch (e)

+ 15 - 0
war/js/diagramly/Init.js

@@ -21,6 +21,9 @@ window.TEMPLATE_PATH = window.TEMPLATE_PATH || '/templates';
 window.RESOURCES_PATH = window.RESOURCES_PATH || 'resources';
 window.RESOURCE_BASE = window.RESOURCE_BASE || RESOURCES_PATH + '/dia';
 
+// URL for logging
+window.DRAWIO_LOG_URL = window.DRAWIO_LOG_URL || '';
+
 // Sets the base path, the UI language via URL param and configures the
 // supported languages to avoid 404s. The loading of all core language
 // resources is disabled as all required resources are in grapheditor.
@@ -231,3 +234,15 @@ if (urlParams['offline'] == '1' || urlParams['local'] == '1')
 {
 	urlParams['math'] = '0';
 }
+
+// Adds hard-coded logging domain for draw.io domains
+var host = window.location.host;
+var searchString = 'draw.io';
+var position = host.length - searchString.length;
+var lastIndex = host.lastIndexOf(searchString, position);
+
+if (lastIndex !== -1 && lastIndex === position && host != 'test.draw.io')
+{
+	// endsWith polyfill
+	window.DRAWIO_LOG_URL = 'https://log.draw.io';
+}

+ 4 - 3
war/js/diagramly/Menus.js

@@ -84,8 +84,8 @@
 		editorUi.actions.get('print').funct = function()
 		{
 			editorUi.showDialog(new PrintDialog(editorUi).container, 320,
-				(false && editorUi.pages != null && editorUi.pages.length > 1) ?
-				300 : 220, true, true);
+				(editorUi.pages != null && editorUi.pages.length > 1) ?
+				400 : 340, true, true);
 		};
 		
 		editorUi.actions.addAction('open...', function()
@@ -560,7 +560,8 @@
 						if (editorUi.enableLogging)
 						{
 							var img = new Image();
-							img.src = 'https://log.draw.io/log?severity=CONFIG&msg=helpsearch:' + encodeURIComponent(input.value) + '&v=' + encodeURIComponent(EditorUi.VERSION);
+							var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+							img.src = logDomain + '/log?severity=CONFIG&msg=helpsearch:' + encodeURIComponent(input.value) + '&v=' + encodeURIComponent(EditorUi.VERSION);
 						}
 						
 						// Workaround for blocked submit on iOS/IE11

+ 15 - 13
war/js/diagramly/Pages.js

@@ -228,8 +228,8 @@ EditorUi.prototype.initPages = function()
 		this.selectNextPage(true);
 	}));
 	
-	this.keyHandler.bindAction(33, true, 'previousPage'); // Ctrl+PageUp
-	this.keyHandler.bindAction(34, true, 'nextPage'); // Ctrl+PageDown
+	this.keyHandler.bindAction(33, true, 'previousPage', true); // Ctrl+Shift+PageUp
+	this.keyHandler.bindAction(34, true, 'nextPage', true); // Ctrl+Shift+PageDown
 	
 	// Updates the tabs after loading the diagram
 	var graph = this.editor.graph;
@@ -276,8 +276,8 @@ EditorUi.prototype.initPages = function()
 			if (p.viewState == null || p.viewState.scrollLeft == null)
 			{
 				this.resetScrollbars();
-				
-				if (this.editor.graph.lightbox)
+
+				if (graph.lightbox)
 				{
 					this.lightboxFit();
 				}
@@ -291,12 +291,8 @@ EditorUi.prototype.initPages = function()
 			}
 			else
 			{
-				// Takes into account resized window
-				var dx = (p.viewState.translate != null) ? p.viewState.translate.x - graph.view.translate.x : 0;
-				var dy = (p.viewState.translate != null) ? p.viewState.translate.y - graph.view.translate.y : 0;
-				
-				graph.container.scrollLeft = p.viewState.scrollLeft - dx * graph.view.scale;
-				graph.container.scrollTop = p.viewState.scrollTop - dy * graph.view.scale;
+				graph.container.scrollLeft = graph.view.translate.x * graph.view.scale + p.viewState.scrollLeft;
+				graph.container.scrollTop = graph.view.translate.y * graph.view.scale + p.viewState.scrollTop;
 			}
 			
 			lastPage = p;
@@ -425,8 +421,8 @@ Graph.prototype.getViewState = function()
 		connect: this.connectionHandler.isEnabled(),
 		arrows: this.connectionArrowsEnabled,
 		scale: this.view.scale,
-		scrollLeft: this.container.scrollLeft,
-		scrollTop: this.container.scrollTop,
+		scrollLeft: this.container.scrollLeft - this.view.translate.x * this.view.scale,
+		scrollTop: this.container.scrollTop - this.view.translate.y * this.view.scale,
 		translate: this.view.translate.clone(),
 		lastPasteXml: this.lastPasteXml,
 		pasteCounter: this.pasteCounter,
@@ -688,9 +684,15 @@ EditorUi.prototype.duplicatePage = function(page, name)
 	
 	if (graph.isEnabled())
 	{
-		// Clones the current page and takes a snapshot of the graph model
+		// Clones the current page and takes a snapshot of the graph model and view state
 		var newPage = new DiagramPage(page.node.cloneNode(false));
 		newPage.root = graph.cloneCells([graph.model.root])[0];
+		newPage.viewState = graph.getViewState();
+		
+		// Resets zoom and scrollbar positions
+		newPage.viewState.scale = 1;
+		newPage.viewState.scrollLeft = null;
+		newPage.viewState.scrollRight = null;
 		newPage.setName(name);
 		
 		newPage = this.insertPage(newPage, mxUtils.indexOf(this.pages, page) + 1);

+ 46 - 7
war/js/diagramly/RealtimeMapping.js

@@ -99,6 +99,10 @@ RealtimeMapping.prototype.init = function()
 				{
 					this.realtimeFoldingEnabledChanged(evt.newValue);
 				}
+				else if (evt.property == 'pageVisible')
+				{
+					this.realtimePageVisibleChanged(evt.newValue);
+				}
 				else if (evt.property == 'backgroundImage')
 				{
 					this.realtimeBackgroundImageChanged(evt.newValue);
@@ -113,7 +117,8 @@ RealtimeMapping.prototype.init = function()
 			if (evt.newValue != null && (evt.property == 'pageFormat' ||
 				evt.property == 'pageScale' || evt.property == 'shadowVisible' ||
 				evt.property == 'backgroundColor' || evt.property == 'foldingEnabled' ||
-				evt.property == 'backgroundImage' || evt.property == 'mathEnabled'))
+				evt.property == 'backgroundImage' || evt.property == 'mathEnabled' ||
+				evt.property == 'pageVisible'))
 			{
 				this.needsUpdate = true;
 			}
@@ -280,6 +285,7 @@ RealtimeMapping.prototype.writeNodeToRealtime = function(node)
 	this.diagramMap.set('foldingEnabled', node.getAttribute('fold'));
 	this.diagramMap.set('mathEnabled', node.getAttribute('math'));
 	this.diagramMap.set('pageScale', node.getAttribute('pageScale'));
+	this.diagramMap.set('pageVisible', node.getAttribute('pageVisible'));
 	
 	var img = node.getAttribute('backgroundImage');
 	
@@ -310,6 +316,7 @@ RealtimeMapping.prototype.activate = function(quiet)
 	this.realtimeBackgroundColorChanged(this.diagramMap.get('backgroundColor'), quiet);
 	this.realtimeShadowVisibleChanged(this.diagramMap.get('shadowVisible'), quiet);
 	this.realtimeFoldingEnabledChanged(this.diagramMap.get('foldingEnabled'), quiet);
+	this.realtimePageVisibleChanged(this.diagramMap.get('pageVisible'), quiet);
 	this.realtimeBackgroundImageChanged(this.diagramMap.get('backgroundImage'), quiet);
 };
 
@@ -332,13 +339,19 @@ RealtimeMapping.prototype.initRealtime = function()
 		}
 		else
 		{
-			this.diagramMap.set('shadowVisible', (this.graph.shadowVisible) ? '1' : '0');
-			this.diagramMap.set('foldingEnabled', (this.graph.foldingEnabled) ? '1' : '0');
-			this.diagramMap.set('mathEnabled', (this.graph.mathEnabled) ? '1' : '0');			
+			var vs = this.page.viewState;
+			var pf = (vs != null) ? vs.pageFormat : mxSettings.getPageFormat();
+			
+			this.diagramMap.set('shadowVisible', (vs != null && vs.shadowVisible) ? '1' : '0');
+			this.diagramMap.set('foldingEnabled', (vs != null && !vs.foldingEnabled) ? '0' : '1');
+			this.diagramMap.set('mathEnabled', (vs != null && vs.mathEnabled) ? '1' : '0');
 			this.diagramMap.set('pageScale', this.graph.pageScale);
-			this.diagramMap.set('backgroundImage', (this.graph.backgroundImage != null) ? JSON.stringify(this.graph.backgroundImage) : '');
-			this.diagramMap.set('backgroundColor', (this.graph.background != null) ? this.graph.background : '');
-			this.diagramMap.set('pageFormat', this.graph.pageFormat.width + ',' + this.graph.pageFormat.height);
+			this.diagramMap.set('pageVisible', (vs != null && !vs.pageVisible) ? '0' : '1');
+			this.diagramMap.set('pageFormat', pf.width + ',' + pf.height);
+			this.diagramMap.set('backgroundImage', (vs != null && vs.backgroundImage != null) ?
+				JSON.stringify(vs.backgroundImage) : '');
+			this.diagramMap.set('backgroundColor', (vs != null && vs.background != null) ?
+				this.graph.background : '');
 		}
 		
 		this.root.set('modifiedDate', new Date().getTime());
@@ -968,6 +981,32 @@ RealtimeMapping.prototype.realtimeFoldingEnabledChanged = function(value, quiet)
 	}
 };
 
+/**
+ * Syncs initial state from collab model to graph model.
+ */
+RealtimeMapping.prototype.realtimePageVisibleChanged = function(value, quiet)
+{
+	if (!this.isActive())
+	{
+		if (this.page.viewState != null)
+		{
+			this.page.viewState.pageVisible = value != '0';
+		}
+	}
+	else if (quiet)
+	{
+		this.graph.pageVisible = value != '0';
+		this.graph.pageBreaksVisible = this.graph.pageVisible; 
+		this.graph.preferPageSize = this.graph.pageVisible;
+	}
+	else
+	{
+		this.driveRealtime.ignorePageVisibleChanged = true;
+		this.ui.setPageVisible(value != '0');
+		this.driveRealtime.ignorePageVisibleChanged = false;
+	}
+};
+
 /**
  * Syncs initial state from collab model to graph model.
  */

+ 4 - 0
war/js/diagramly/sidebar/Sidebar-Basic.js

@@ -12,6 +12,10 @@
 		
 		this.addPaletteFunctions('basic', mxResources.get('basic'), false,
 		[
+			this.createVertexTemplateEntry('whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Square', null, null, 'square'),
+			this.createVertexTemplateEntry('ellipse;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Circle', null, null, 'circle'),
+			this.createVertexTemplateEntry('shape=ext;double=1;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Double Square', null, null, 'double square'),
+			this.createVertexTemplateEntry('ellipse;shape=doubleEllipse;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Double Circle', null, null, 'double circle'),
 			this.createVertexTemplateEntry(s2 + '4_point_star', w, h, '', '4 Point Star', null, null, this.getTagsForStencil(gn, '4_point_star', dt).join(' ')),
 			this.createVertexTemplateEntry(s2 + '6_point_star', w, h * 0.9, '', '6 Point Star', null, null, this.getTagsForStencil(gn, '6_point_star', dt).join(' ')),
 			this.createVertexTemplateEntry(s2 + '8_point_star', w, h, '', '8 Point Star', null, null, this.getTagsForStencil(gn, '8_point_star', dt).join(' ')),

+ 2 - 1
war/js/diagramly/sidebar/Sidebar.js

@@ -983,7 +983,8 @@
 		if (this.editorUi.enableLogging && !this.editorUi.isOffline() && page == 0)
 		{
 			var img = new Image();
-			img.src = 'https://log.draw.io/log?severity=CONFIG&msg=shapesearch:' + encodeURIComponent(searchTerms) + '&v=' + encodeURIComponent(EditorUi.VERSION);
+			var logDomain = window.DRAWIO_LOG_URL != null ? window.DRAWIO_LOG_URL : '';
+			img.src = logDomain + '/log?severity=CONFIG&msg=shapesearch:' + encodeURIComponent(searchTerms) + '&v=' + encodeURIComponent(EditorUi.VERSION);
 		}
 		
 		success = mxUtils.bind(this, function(results, len, more, terms)

File diff suppressed because it is too large
+ 300 - 297
war/js/embed-static.min.js


+ 3 - 35
war/js/mxgraph/Actions.js

@@ -156,9 +156,9 @@ Actions.prototype.init = function()
 		}
 	};
 	
-	this.addAction('delete', function()
+	this.addAction('delete', function(evt)
 	{
-		deleteCells(false);
+		deleteCells(evt != null && mxEvent.isShiftDown(evt));
 	}, null, null, 'Delete');
 	this.addAction('deleteAll', function()
 	{
@@ -680,39 +680,7 @@ Actions.prototype.init = function()
 	action.setSelectedCallback(function() { return graph.scrollbars; });
 	action = this.addAction('pageView', mxUtils.bind(this, function()
 	{
-		var hasScrollbars = mxUtils.hasScrollbars(graph.container);
-		var tx = 0;
-		var ty = 0;
-		
-		if (hasScrollbars)
-		{
-			tx = graph.view.translate.x * graph.view.scale - graph.container.scrollLeft;
-			ty = graph.view.translate.y * graph.view.scale - graph.container.scrollTop;
-		}
-		
-		graph.pageVisible = !graph.pageVisible;
-		graph.pageBreaksVisible = graph.pageVisible; 
-		graph.preferPageSize = graph.pageBreaksVisible;
-		graph.view.validateBackground();
-
-		// Workaround for possible handle offset
-		if (hasScrollbars)
-		{
-			var cells = graph.getSelectionCells();
-			graph.clearSelection();
-			graph.setSelectionCells(cells);
-		}
-		
-		// Calls updatePageBreaks
-		graph.sizeDidChange();
-		
-		if (hasScrollbars)
-		{
-			graph.container.scrollLeft = graph.view.translate.x * graph.view.scale - tx;
-			graph.container.scrollTop = graph.view.translate.y * graph.view.scale - ty;
-		}
-		
-		ui.fireEvent(new mxEventObject('pageViewChanged'));
+		ui.setPageVisible(!graph.pageVisible);
 	}));
 	action.setToggleAction(true);
 	action.setSelectedCallback(function() { return graph.pageVisible; });

+ 249 - 169
war/js/mxgraph/Dialogs.js

@@ -475,156 +475,20 @@ var PageSetupDialog = function(editorUi)
 	row = document.createElement('tr');
 	
 	td = document.createElement('td');
+	td.style.verticalAlign = 'top';
 	td.style.fontSize = '10pt';
 	mxUtils.write(td, mxResources.get('paperSize') + ':');
 	
 	row.appendChild(td);
-
-	var portraitCheckBox = document.createElement('input');
-	portraitCheckBox.setAttribute('name', 'format');
-	portraitCheckBox.setAttribute('type', 'radio');
-	portraitCheckBox.setAttribute('value', 'portrait');
-	
-	var landscapeCheckBox = document.createElement('input');
-	landscapeCheckBox.setAttribute('name', 'format');
-	landscapeCheckBox.setAttribute('type', 'radio');
-	landscapeCheckBox.setAttribute('value', 'landscape');
-	
-	var formatRow = document.createElement('tr');
-	formatRow.style.display = 'none';
-	
-	var customRow = document.createElement('tr');
-	customRow.style.display = 'none';
-	
-	// Adds all papersize options
-	var paperSizeSelect = document.createElement('select');
-	var detected = false;
-	var pf = new Object();
-	var formats = PageSetupDialog.getFormats();
-
-	for (var i = 0; i < formats.length; i++)
-	{
-		var f = formats[i];
-		pf[f.key] = f;
-
-		var paperSizeOption = document.createElement('option');
-		paperSizeOption.setAttribute('value', f.key);
-		mxUtils.write(paperSizeOption, f.title);
-		paperSizeSelect.appendChild(paperSizeOption);
-		
-		if (f.format != null)
-		{
-			if (graph.pageFormat.width == f.format.width && graph.pageFormat.height == f.format.height)
-			{
-				paperSizeOption.setAttribute('selected', 'selected');
-				portraitCheckBox.setAttribute('checked', 'checked');
-				portraitCheckBox.defaultChecked = true;
-				formatRow.style.display = '';
-				detected = true;
-			}
-			else if (graph.pageFormat.width == f.format.height && graph.pageFormat.height == f.format.width)
-			{
-				paperSizeOption.setAttribute('selected', 'selected');
-				landscapeCheckBox.setAttribute('checked', 'checked');
-				portraitCheckBox.defaultChecked = true;
-				formatRow.style.display = '';
-				detected = true;
-			}
-		}
-		// Selects custom format which is last in list
-		else if (!detected)
-		{
-			paperSizeOption.setAttribute('selected', 'selected');
-			customRow.style.display = '';
-		}
-	}
 	
 	td = document.createElement('td');
+	td.style.verticalAlign = 'top';
 	td.style.fontSize = '10pt';
-	td.appendChild(paperSizeSelect);
-	row.appendChild(td);
-	
-	tbody.appendChild(row);
-	
-	formatRow = document.createElement('tr');
-	formatRow.style.height = '40px';
-	td = document.createElement('td');
-	formatRow.appendChild(td);
-
-	td = document.createElement('td');
-	td.style.fontSize = '10pt';
-	
-	td.appendChild(portraitCheckBox);
-	var span = document.createElement('span');
-	mxUtils.write(span, ' ' + mxResources.get('portrait'));
-	td.appendChild(span);
-	
-	mxEvent.addListener(span, 'click', function(evt)
-	{
-		portraitCheckBox.checked = true;
-		mxEvent.consume(evt);
-	});
-	
-	landscapeCheckBox.style.marginLeft = '10px';
-	td.appendChild(landscapeCheckBox);
-	
-	var span = document.createElement('span');
-	mxUtils.write(span, ' ' + mxResources.get('landscape'));
-	td.appendChild(span);
 	
-	mxEvent.addListener(span, 'click', function(evt)
-	{
-		landscapeCheckBox.checked = true;
-		mxEvent.consume(evt);
-	});
+	var accessor = PageSetupDialog.addPageFormatPanel(td, 'pagesetupdialog', graph.pageFormat);
 
-	formatRow.appendChild(td);
-	
-	tbody.appendChild(formatRow);
-	row = document.createElement('tr');
-	
-	td = document.createElement('td');
-	customRow.appendChild(td);
-
-	td = document.createElement('td');
-	td.style.fontSize = '10pt';
-	
-	var widthInput = document.createElement('input');
-	widthInput.setAttribute('size', '6');
-	widthInput.setAttribute('value', graph.pageFormat.width);
-	td.appendChild(widthInput);
-	mxUtils.write(td, ' x ');
-	
-	var heightInput = document.createElement('input');
-	heightInput.setAttribute('size', '6');
-	heightInput.setAttribute('value', graph.pageFormat.height);
-	td.appendChild(heightInput);
-	mxUtils.write(td, ' pt');
-	
-	customRow.appendChild(td);
-	customRow.style.height = formatRow.style.height;
-	tbody.appendChild(customRow);
-	
-	var updateInputs = function()
-	{
-		var f = pf[paperSizeSelect.value];
-		
-		if (f.format != null)
-		{
-			widthInput.value = f.format.width;
-			heightInput.value = f.format.height;
-			customRow.style.display = 'none';
-			formatRow.style.display = '';
-		}
-		else
-		{
-			formatRow.style.display = 'none';
-			customRow.style.display = '';
-		}
-	};
-	
-	mxEvent.addListener(paperSizeSelect, 'change', updateInputs);
-	updateInputs();
+	row.appendChild(td);
+	tbody.appendChild(row);
 	
 	row = document.createElement('tr');
 	
@@ -765,21 +629,7 @@ var PageSetupDialog = function(editorUi)
 	var applyBtn = mxUtils.button(mxResources.get('apply'), function()
 	{
 		editorUi.hideDialog();
-		var ls = landscapeCheckBox.checked;
-		var f = pf[paperSizeSelect.value];
-		var size = f.format;
-		
-		if (size == null)
-		{
-			size = new mxRectangle(0, 0, parseInt(widthInput.value), parseInt(heightInput.value));
-		}
-		
-		if (ls)
-		{
-			size = new mxRectangle(0, 0, size.height, size.width);
-		}
-
-		editorUi.setPageFormat(size);
+		editorUi.setPageFormat(accessor.get());
 		
 		if (graph.background != newBackgroundColor)
 		{
@@ -811,6 +661,234 @@ var PageSetupDialog = function(editorUi)
 	this.container = table;
 };
 
+/**
+ * 
+ */
+PageSetupDialog.addPageFormatPanel = function(div, namePostfix, pageFormat, pageFormatListener)
+{
+	var formatName = 'format-' + namePostfix;
+	
+	var portraitCheckBox = document.createElement('input');
+	portraitCheckBox.setAttribute('name', formatName);
+	portraitCheckBox.setAttribute('type', 'radio');
+	portraitCheckBox.setAttribute('value', 'portrait');
+	
+	var landscapeCheckBox = document.createElement('input');
+	landscapeCheckBox.setAttribute('name', formatName);
+	landscapeCheckBox.setAttribute('type', 'radio');
+	landscapeCheckBox.setAttribute('value', 'landscape');
+	
+	var paperSizeSelect = document.createElement('select');
+	paperSizeSelect.style.marginBottom = '8px';
+	paperSizeSelect.style.width = '202px';
+
+	var formatDiv = document.createElement('div');
+	formatDiv.style.marginLeft = '4px';
+	formatDiv.style.width = '210px';
+	formatDiv.style.height = '24px';
+
+	portraitCheckBox.style.marginRight = '6px';
+	formatDiv.appendChild(portraitCheckBox);
+	
+	var portraitSpan = document.createElement('span');
+	portraitSpan.style.maxWidth = '100px';
+	mxUtils.write(portraitSpan, mxResources.get('portrait'));
+	formatDiv.appendChild(portraitSpan);
+
+	landscapeCheckBox.style.marginLeft = '10px';
+	landscapeCheckBox.style.marginRight = '6px';
+	formatDiv.appendChild(landscapeCheckBox);
+	
+	var landscapeSpan = document.createElement('span');
+	landscapeSpan.style.width = '100px';
+	mxUtils.write(landscapeSpan, mxResources.get('landscape'));
+	formatDiv.appendChild(landscapeSpan)
+
+	var customDiv = document.createElement('div');
+	customDiv.style.marginLeft = '4px';
+	customDiv.style.width = '210px';
+	customDiv.style.height = '24px';
+	
+	var widthInput = document.createElement('input');
+	widthInput.setAttribute('size', '6');
+	widthInput.setAttribute('value', pageFormat.width);
+	customDiv.appendChild(widthInput);
+	mxUtils.write(customDiv, ' x ');
+	
+	var heightInput = document.createElement('input');
+	heightInput.setAttribute('size', '6');
+	heightInput.setAttribute('value', pageFormat.height);
+	customDiv.appendChild(heightInput);
+	mxUtils.write(customDiv, ' pt');
+
+	formatDiv.style.display = 'none';
+	customDiv.style.display = 'none';
+	
+	var pf = new Object();
+	var formats = PageSetupDialog.getFormats();
+	
+	for (var i = 0; i < formats.length; i++)
+	{
+		var f = formats[i];
+		pf[f.key] = f;
+
+		var paperSizeOption = document.createElement('option');
+		paperSizeOption.setAttribute('value', f.key);
+		mxUtils.write(paperSizeOption, f.title);
+		paperSizeSelect.appendChild(paperSizeOption);
+	}
+	
+	var customSize = false;
+	
+	function listener(sender, evt, force)
+	{
+		if (force || (widthInput != document.activeElement && heightInput != document.activeElement))
+		{
+			var detected = false;
+			
+			for (var i = 0; i < formats.length; i++)
+			{
+				var f = formats[i];
+	
+				// Special case where custom was chosen
+				if (customSize)
+				{
+					if (f.key == 'custom')
+					{
+						paperSizeSelect.value = f.key;
+						customSize = false;
+					}
+				}
+				else if (f.format != null)
+				{
+					if (pageFormat.width == f.format.width && pageFormat.height == f.format.height)
+					{
+						paperSizeSelect.value = f.key;
+						portraitCheckBox.setAttribute('checked', 'checked');
+						portraitCheckBox.defaultChecked = true;
+						portraitCheckBox.checked = true;
+						landscapeCheckBox.removeAttribute('checked');
+						landscapeCheckBox.defaultChecked = false;
+						landscapeCheckBox.checked = false;
+						detected = true;
+					}
+					else if (pageFormat.width == f.format.height && pageFormat.height == f.format.width)
+					{
+						paperSizeSelect.value = f.key;
+						portraitCheckBox.removeAttribute('checked');
+						portraitCheckBox.defaultChecked = false;
+						portraitCheckBox.checked = false;
+						landscapeCheckBox.setAttribute('checked', 'checked');
+						landscapeCheckBox.defaultChecked = true;
+						landscapeCheckBox.checked = true;
+						detected = true;
+					}
+				}
+			}
+			
+			// Selects custom format which is last in list
+			if (!detected)
+			{
+				widthInput.value = pageFormat.width;
+				heightInput.value = pageFormat.height;
+				paperSizeOption.setAttribute('selected', 'selected');
+				portraitCheckBox.setAttribute('checked', 'checked');
+				portraitCheckBox.defaultChecked = true;
+				formatDiv.style.display = 'none';
+				customDiv.style.display = '';
+			}
+			else
+			{
+				formatDiv.style.display = '';
+				customDiv.style.display = 'none';
+			}
+		}
+	};
+	listener();
+
+	div.appendChild(paperSizeSelect);
+	mxUtils.br(div);
+
+	div.appendChild(formatDiv);
+	div.appendChild(customDiv);
+	
+	var currentPageFormat = pageFormat;
+	
+	var update = function()
+	{
+		var f = pf[paperSizeSelect.value];
+		
+		if (f.format != null)
+		{
+			widthInput.value = f.format.width;
+			heightInput.value = f.format.height;
+			customDiv.style.display = 'none';
+			formatDiv.style.display = '';
+		}
+		else
+		{
+			formatDiv.style.display = 'none';
+			customDiv.style.display = '';
+		}
+		
+		var newPageFormat = new mxRectangle(0, 0, parseInt(widthInput.value), parseInt(heightInput.value));
+		
+		if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked)
+		{
+			newPageFormat = new mxRectangle(0, 0, newPageFormat.height, newPageFormat.width);
+		}
+		
+		if (newPageFormat.width != currentPageFormat.width || newPageFormat.height != currentPageFormat.height)
+		{
+			currentPageFormat = newPageFormat;
+			
+			if (pageFormatListener != null)
+			{
+				pageFormatListener(currentPageFormat);
+			}
+		}
+	};
+
+	mxEvent.addListener(portraitSpan, 'click', function(evt)
+	{
+		portraitCheckBox.checked = true;
+		update();
+		mxEvent.consume(evt);
+	});
+	
+	mxEvent.addListener(landscapeSpan, 'click', function(evt)
+	{
+		landscapeCheckBox.checked = true;
+		update();
+		mxEvent.consume(evt);
+	});
+	
+	mxEvent.addListener(widthInput, 'blur', update);
+	mxEvent.addListener(widthInput, 'click', update);
+	mxEvent.addListener(heightInput, 'blur', update);
+	mxEvent.addListener(heightInput, 'click', update);
+	mxEvent.addListener(landscapeCheckBox, 'change', update);
+	mxEvent.addListener(portraitCheckBox, 'change', update);
+	mxEvent.addListener(paperSizeSelect, 'change', function()
+	{
+		// Handles special case where custom was chosen
+		customSize = paperSizeSelect.value == 'custom';
+		update();
+	});
+	
+	update();
+	
+	return {set: function(value)
+	{
+		pageFormat = value;
+		listener(null, null, true);
+	},get: function()
+	{
+		return currentPageFormat;
+	}, widthInput: widthInput,
+	heightInput: heightInput};
+};
+
 /**
  * 
  */
@@ -998,7 +1076,13 @@ PrintDialog.prototype.create = function(editorUi)
 			autoOrigin = true;
 		}
 		
-		return PrintDialog.showPreview(PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin, print), print);
+		var preview = PrintDialog.createPrintPreview(graph, scale, pf, border, x0, y0, autoOrigin);
+		preview.open();
+	
+		if (print)
+		{
+			PrintDialog.printPreview(preview);
+		}
 	};
 	
 	var cancelBtn = mxUtils.button(mxResources.get('cancel'), function()
@@ -1046,17 +1130,15 @@ PrintDialog.prototype.create = function(editorUi)
 /**
  * Constructs a new print dialog.
  */
-PrintDialog.showPreview = function(preview, print)
+PrintDialog.printPreview = function(preview)
 {
-	var result = preview.open();
-	
-	if (print && result != null)
+	if (preview.wnd != null)
 	{
-		var print = function()
+		var printFn = function()
 		{
-			result.focus();
-			result.print();
-			result.close();
+			preview.wnd.focus();
+			preview.wnd.print();
+			preview.wnd.close();
 		};
 		
 		// Workaround for Google Chrome which needs a bit of a
@@ -1064,15 +1146,13 @@ PrintDialog.showPreview = function(preview, print)
 		// Needs testing in production
 		if (mxClient.IS_GC)
 		{
-			window.setTimeout(print, 500);
+			window.setTimeout(printFn, 500);
 		}
 		else
 		{
-			print();
+			printFn();
 		}
 	}
-	
-	return result;
 };
 
 /**

+ 58 - 51
war/js/mxgraph/EditorUi.js

@@ -1278,16 +1278,7 @@ EditorUi.prototype.initCanvas = function()
 	// Initial page layout view, scrollBuffer and timer-based scrolling
 	var graph = this.editor.graph;
 	graph.timerAutoScroll = true;
-	
-	/**
-	 * Specifies the size of the size for "tiles" to be used for a graph with
-	 * scrollbars but no visible background page. A good value is large
-	 * enough to reduce the number of repaints that is caused for auto-
-	 * translation, which depends on this value, and small enough to give
-	 * a small empty buffer around the graph. Default is 400x400.
-	 */
-	graph.scrollTileSize = new mxRectangle(0, 0, 400, 400);
-	
+
 	/**
 	 * Returns the padding for pages in page view with scrollbars.
 	 */
@@ -1296,47 +1287,6 @@ EditorUi.prototype.initCanvas = function()
 		return new mxPoint(Math.max(0, Math.round((graph.container.offsetWidth - 34) / graph.view.scale)),
 				Math.max(0, Math.round((graph.container.offsetHeight - 34) / graph.view.scale)));
 	};
-	
-	/**
-	 * Returns the size of the page format scaled with the page size.
-	 */
-	graph.getPageSize = function()
-	{
-		return (this.pageVisible) ? new mxRectangle(0, 0, this.pageFormat.width * this.pageScale,
-				this.pageFormat.height * this.pageScale) : this.scrollTileSize;
-	};
-	
-	/**
-	 * Returns a rectangle describing the position and count of the
-	 * background pages, where x and y are the position of the top,
-	 * left page and width and height are the vertical and horizontal
-	 * page count.
-	 */
-	graph.getPageLayout = function()
-	{
-		var size = (this.pageVisible) ? this.getPageSize() : this.scrollTileSize;
-		var bounds = this.getGraphBounds();
-
-		if (bounds.width == 0 || bounds.height == 0)
-		{
-			return new mxRectangle(0, 0, 1, 1);
-		}
-		else
-		{
-			// Computes untransformed graph bounds
-			var x = Math.ceil(bounds.x / this.view.scale - this.view.translate.x);
-			var y = Math.ceil(bounds.y / this.view.scale - this.view.translate.y);
-			var w = Math.floor(bounds.width / this.view.scale);
-			var h = Math.floor(bounds.height / this.view.scale);
-			
-			var x0 = Math.floor(x / size.width);
-			var y0 = Math.floor(y / size.height);
-			var w0 = Math.ceil((x + w) / size.width) - x0;
-			var h0 = Math.ceil((y + h) / size.height) - y0;
-			
-			return new mxRectangle(x0, y0, w0, h0);
-		}
-	};
 
 	// Fits the number of background pages to the graph
 	graph.view.getBackgroundPageBounds = function()
@@ -2356,6 +2306,22 @@ EditorUi.prototype.resetScrollbars = function()
 				var pad = graph.getPagePadding();
 				graph.container.scrollTop = Math.floor(pad.y - this.editor.initialTopSpacing);
 				graph.container.scrollLeft = Math.floor(Math.min(pad.x, (graph.container.scrollWidth - graph.container.clientWidth) / 2));
+
+				// Scrolls graph to visible area
+				var bounds = graph.getGraphBounds();
+				
+				if (bounds.width > 0 && bounds.height > 0)
+				{
+					if (bounds.x > graph.container.scrollLeft + graph.container.clientWidth * 0.9)
+					{
+						graph.container.scrollLeft = Math.min(bounds.x + bounds.width - graph.container.clientWidth, bounds.x - 10);
+					}
+					
+					if (bounds.y > graph.container.scrollTop + graph.container.clientHeight * 0.9)
+					{
+						graph.container.scrollTop = Math.min(bounds.y + bounds.height - graph.container.clientHeight, bounds.y - 10);
+					}
+				}
 			}
 			else
 			{
@@ -2385,6 +2351,47 @@ EditorUi.prototype.resetScrollbars = function()
 	}
 };
 
+/**
+ * Loads the stylesheet for this graph.
+ */
+EditorUi.prototype.setPageVisible = function(value)
+{
+	var graph = this.editor.graph;
+	var hasScrollbars = mxUtils.hasScrollbars(graph.container);
+	var tx = 0;
+	var ty = 0;
+	
+	if (hasScrollbars)
+	{
+		tx = graph.view.translate.x * graph.view.scale - graph.container.scrollLeft;
+		ty = graph.view.translate.y * graph.view.scale - graph.container.scrollTop;
+	}
+	
+	graph.pageVisible = value;
+	graph.pageBreaksVisible = value; 
+	graph.preferPageSize = value;
+	graph.view.validateBackground();
+
+	// Workaround for possible handle offset
+	if (hasScrollbars)
+	{
+		var cells = graph.getSelectionCells();
+		graph.clearSelection();
+		graph.setSelectionCells(cells);
+	}
+	
+	// Calls updatePageBreaks
+	graph.sizeDidChange();
+	
+	if (hasScrollbars)
+	{
+		graph.container.scrollLeft = graph.view.translate.x * graph.view.scale - tx;
+		graph.container.scrollTop = graph.view.translate.y * graph.view.scale - ty;
+	}
+	
+	this.fireEvent(new mxEventObject('pageViewChanged'));
+};
+
 /**
  * Loads the stylesheet for this graph.
  */

+ 13 - 196
war/js/mxgraph/Format.js

@@ -4623,210 +4623,29 @@ DiagramFormatPanel.prototype.addPaperSize = function(div)
 	var graph = editor.graph;
 	
 	div.appendChild(this.createTitle(mxResources.get('paperSize')));
-	
-	var portraitCheckBox = document.createElement('input');
-	portraitCheckBox.setAttribute('name', 'format');
-	portraitCheckBox.setAttribute('type', 'radio');
-	portraitCheckBox.setAttribute('value', 'portrait');
-	
-	var landscapeCheckBox = document.createElement('input');
-	landscapeCheckBox.setAttribute('name', 'format');
-	landscapeCheckBox.setAttribute('type', 'radio');
-	landscapeCheckBox.setAttribute('value', 'landscape');
-	
-	var paperSizeSelect = document.createElement('select');
-	paperSizeSelect.style.marginBottom = '8px';
-	paperSizeSelect.style.width = '202px';
-
-	var formatDiv = document.createElement('div');
-	formatDiv.style.marginLeft = '4px';
-	formatDiv.style.width = '210px';
-	formatDiv.style.height = '24px';
-
-	portraitCheckBox.style.marginRight = '6px';
-	formatDiv.appendChild(portraitCheckBox);
-	
-	var portraitSpan = document.createElement('span');
-	portraitSpan.style.maxWidth = '100px';
-	mxUtils.write(portraitSpan, mxResources.get('portrait'));
-	formatDiv.appendChild(portraitSpan);
-
-	landscapeCheckBox.style.marginLeft = '10px';
-	landscapeCheckBox.style.marginRight = '6px';
-	formatDiv.appendChild(landscapeCheckBox);
-	
-	var landscapeSpan = document.createElement('span');
-	landscapeSpan.style.width = '100px';
-	mxUtils.write(landscapeSpan, mxResources.get('landscape'));
-	formatDiv.appendChild(landscapeSpan)
-
-	var customDiv = document.createElement('div');
-	customDiv.style.marginLeft = '4px';
-	customDiv.style.width = '210px';
-	customDiv.style.height = '24px';
-	
-	var widthInput = document.createElement('input');
-	widthInput.setAttribute('size', '6');
-	widthInput.setAttribute('value', graph.pageFormat.width);
-	customDiv.appendChild(widthInput);
-	mxUtils.write(customDiv, ' x ');
-	
-	var heightInput = document.createElement('input');
-	heightInput.setAttribute('size', '6');
-	heightInput.setAttribute('value', graph.pageFormat.height);
-	customDiv.appendChild(heightInput);
-	mxUtils.write(customDiv, ' pt');
-
-	formatDiv.style.display = 'none';
-	customDiv.style.display = 'none';
-	
-	var pf = new Object();
-	var formats = PageSetupDialog.getFormats();
-	
-	for (var i = 0; i < formats.length; i++)
-	{
-		var f = formats[i];
-		pf[f.key] = f;
-
-		var paperSizeOption = document.createElement('option');
-		paperSizeOption.setAttribute('value', f.key);
-		mxUtils.write(paperSizeOption, f.title);
-		paperSizeSelect.appendChild(paperSizeOption);
-	}
-	
-	var customSize = false;
-	
-	function listener(sender, evt, force)
-	{
-		if (force || (widthInput != document.activeElement && heightInput != document.activeElement))
-		{
-			var detected = false;
-			
-			for (var i = 0; i < formats.length; i++)
-			{
-				var f = formats[i];
-	
-				// Special case where custom was chosen
-				if (customSize)
-				{
-					if (f.key == 'custom')
-					{
-						paperSizeSelect.value = f.key;
-						customSize = false;
-					}
-				}
-				else if (f.format != null)
-				{
-					if (graph.pageFormat.width == f.format.width && graph.pageFormat.height == f.format.height)
-					{
-						paperSizeSelect.value = f.key;
-						portraitCheckBox.setAttribute('checked', 'checked');
-						portraitCheckBox.defaultChecked = true;
-						portraitCheckBox.checked = true;
-						landscapeCheckBox.removeAttribute('checked');
-						landscapeCheckBox.defaultChecked = false;
-						landscapeCheckBox.checked = false;
-						detected = true;
-					}
-					else if (graph.pageFormat.width == f.format.height && graph.pageFormat.height == f.format.width)
-					{
-						paperSizeSelect.value = f.key;
-						portraitCheckBox.removeAttribute('checked');
-						portraitCheckBox.defaultChecked = false;
-						portraitCheckBox.checked = false;
-						landscapeCheckBox.setAttribute('checked', 'checked');
-						landscapeCheckBox.defaultChecked = true;
-						landscapeCheckBox.checked = true;
-						detected = true;
-					}
-				}
-			}
-			
-			// Selects custom format which is last in list
-			if (!detected)
-			{
-				widthInput.value = graph.pageFormat.width;
-				heightInput.value = graph.pageFormat.height;
-				paperSizeOption.setAttribute('selected', 'selected');
-				portraitCheckBox.setAttribute('checked', 'checked');
-				portraitCheckBox.defaultChecked = true;
-				formatDiv.style.display = 'none';
-				customDiv.style.display = '';
-			}
-			else
-			{
-				formatDiv.style.display = '';
-				customDiv.style.display = 'none';
-			}
-		}
-	};
-	listener();
-
-	div.appendChild(paperSizeSelect);
-	mxUtils.br(div);
 
-	div.appendChild(formatDiv);
-	div.appendChild(customDiv);
-	
-	var update = function()
+	var accessor = PageSetupDialog.addPageFormatPanel(div, 'formatpanel', graph.pageFormat, function(pageFormat)
 	{
-		var f = pf[paperSizeSelect.value];
-		
-		if (f.format != null)
-		{
-			widthInput.value = f.format.width;
-			heightInput.value = f.format.height;
-			customDiv.style.display = 'none';
-			formatDiv.style.display = '';
-		}
-		else
-		{
-			formatDiv.style.display = 'none';
-			customDiv.style.display = '';
-		}
-		
-		var size = new mxRectangle(0, 0, parseInt(widthInput.value), parseInt(heightInput.value));
-		
-		if (paperSizeSelect.value != 'custom' && landscapeCheckBox.checked)
+		if (graph.pageFormat == null || graph.pageFormat.width != pageFormat.width || graph.pageFormat.height != pageFormat.height)
 		{
-			size = new mxRectangle(0, 0, size.height, size.width);
+			ui.setPageFormat(pageFormat);
 		}
-		
-		if (graph.pageFormat == null || graph.pageFormat.width != size.width || graph.pageFormat.height != size.height)
-		{
-			ui.setPageFormat(size);
-		}
-	};
-
-	this.addKeyHandler(widthInput, listener);
-	this.addKeyHandler(heightInput, listener);
+	});
 	
-	mxEvent.addListener(portraitSpan, 'click', function(evt)
+	this.addKeyHandler(accessor.widthInput, function()
 	{
-		portraitCheckBox.checked = true;
-		update();
-		mxEvent.consume(evt);
+		console.log('here', graph.pageFormat);
+		accessor.set(graph.pageFormat);
 	});
-	
-	mxEvent.addListener(landscapeSpan, 'click', function(evt)
+-	this.addKeyHandler(accessor.heightInput, function()
 	{
-		landscapeCheckBox.checked = true;
-		update();
-		mxEvent.consume(evt);
+		accessor.set(graph.pageFormat);	
 	});
 	
-	mxEvent.addListener(widthInput, 'blur', update);
-	mxEvent.addListener(widthInput, 'click', update);
-	mxEvent.addListener(heightInput, 'blur', update);
-	mxEvent.addListener(heightInput, 'click', update);
-	mxEvent.addListener(landscapeCheckBox, 'change', update);
-	mxEvent.addListener(portraitCheckBox, 'change', update);
-	mxEvent.addListener(paperSizeSelect, 'change', function()
-	{
-		// Handles special case where custom was chosen
-		customSize = paperSizeSelect.value == 'custom';
-		update();
-	});
+	var listener = function()
+	{
+		accessor.set(graph.pageFormat);
+	};
 	
 	ui.addListener('pageFormatChanged', listener);
 	this.listeners.push({destroy: function() { ui.removeListener(listener); }});
@@ -4834,8 +4653,6 @@ DiagramFormatPanel.prototype.addPaperSize = function(div)
 	graph.getModel().addListener(mxEvent.CHANGE, listener);
 	this.listeners.push({destroy: function() { graph.getModel().removeListener(listener); }});
 	
-	update();
-	
 	return div;
 };
 

File diff suppressed because it is too large
+ 78 - 20
war/js/mxgraph/Graph.js


+ 37 - 13
war/js/mxgraph/Sidebar.js

@@ -94,8 +94,7 @@ Sidebar.prototype.init = function()
 	this.addGeneralPalette(true);
 	this.addMiscPalette(false);
 	this.addAdvancedPalette(false);
-	this.addStencilPalette('basic', mxResources.get('basic'), dir + '/basic.xml',
-		';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2');
+	this.addBasicPalette(dir);
 	this.addStencilPalette('arrows', mxResources.get('arrows'), dir + '/arrows.xml',
 		';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2');
 	this.addUmlPalette(false);
@@ -358,7 +357,7 @@ Sidebar.prototype.showTooltip = function(elt, cells, w, h, title, showLabel)
 				
 				var b = document.body;
 				var d = document.documentElement;
-				var bottom = b.clientHeight || d.clientHeight;
+				var bottom = Math.max(b.clientHeight || 0, d.clientHeight);
 
 				var left = this.container.clientWidth + this.editorUi.splitSize + 3 + this.editorUi.container.offsetLeft;
 				var top = Math.min(bottom - height - 20 /*status bar*/, Math.max(0, (this.editorUi.container.offsetTop +
@@ -380,7 +379,7 @@ Sidebar.prototype.showTooltip = function(elt, cells, w, h, title, showLabel)
 					this.graph2.view.drawPane.style.left = x0 + 'px';
 					this.graph2.view.drawPane.style.top = y0 + 'px';
 				}
-		
+				
 				// Workaround for ignored position CSS style in IE9
 				// (changes to relative without the following line)
 				this.tooltip.style.position = 'absolute';
@@ -898,6 +897,21 @@ Sidebar.prototype.addGeneralPalette = function(expand)
 	this.addPaletteFunctions('general', mxResources.get('general'), (expand != null) ? expand : true, fns);
 };
 
+/**
+ * Adds the general palette to the sidebar.
+ */
+Sidebar.prototype.addBasicPalette = function(dir)
+{
+	this.addStencilPalette('basic', mxResources.get('basic'), dir + '/basic.xml',
+		';whiteSpace=wrap;html=1;fillColor=#ffffff;strokeColor=#000000;strokeWidth=2',
+		null, null, null, null, [
+		this.createVertexTemplateEntry('whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Square', null, null, 'square'),
+		this.createVertexTemplateEntry('ellipse;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Circle', null, null, 'circle'),
+		this.createVertexTemplateEntry('shape=ext;double=1;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Double Square', null, null, 'double square'),
+		this.createVertexTemplateEntry('ellipse;shape=doubleEllipse;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Double Circle', null, null, 'double circle')
+	]);
+};
+
 /**
  * Adds the general palette to the sidebar.
  */
@@ -960,12 +974,6 @@ Sidebar.prototype.addMiscPalette = function(expand)
 	 		return this.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, 'Variable');
 	 	})),
 	 	this.createVertexTemplateEntry('shape=umlActor;verticalLabelPosition=bottom;labelBackgroundColor=#ffffff;verticalAlign=top;html=1;', 30, 60, 'Actor', 'Actor', false, null, 'user person human stickman'),
-		// Entries for top searches
-		this.createVertexTemplateEntry('whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Square', null, null, 'square'),
-		this.createVertexTemplateEntry('ellipse;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Circle', null, null, 'circle'),
-		this.createVertexTemplateEntry('shape=ext;double=1;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Double Square', null, null, 'double square'),
-		this.createVertexTemplateEntry('ellipse;shape=doubleEllipse;whiteSpace=wrap;html=1;aspect=fixed;', 80, 80, '', 'Double Circle', null, null, 'double circle'),
-		// End of entries for top searches
 	 	this.createVertexTemplateEntry('html=1;whiteSpace=wrap;comic=1;strokeWidth=2;fontFamily=Comic Sans MS;fontStyle=1;', 120, 60, 'RECTANGLE', 'Comic Rectangle', true, null, 'comic rectangle rect box text retro'),
 	 	this.createVertexTemplateEntry('rhombus;html=1;align=center;whiteSpace=wrap;comic=1;strokeWidth=2;fontFamily=Comic Sans MS;fontStyle=1;', 100, 100, 'DIAMOND', 'Comic Diamond', true, null, 'comic diamond rhombus if condition decision conditional question test retro'),
 	 	this.createEdgeTemplateEntry('edgeStyle=segmentEdgeStyle;rounded=0;comic=1;strokeWidth=2;endArrow=blockThin;html=1;fontFamily=Comic Sans MS;fontStyle=1;', 50, 50, '', 'Comic Arrow 1'),
@@ -3347,14 +3355,22 @@ Sidebar.prototype.getTagsForStencil = function(packageName, stencilName, moreTag
 /**
  * Adds the given stencil palette.
  */
-Sidebar.prototype.addStencilPalette = function(id, title, stencilFile, style, ignore, onInit, scale, tags)
+Sidebar.prototype.addStencilPalette = function(id, title, stencilFile, style, ignore, onInit, scale, tags, customFns)
 {
 	scale = (scale != null) ? scale : 1;
-
+	
 	if (this.addStencilsToIndex)
 	{
 		// LATER: Handle asynchronous loading dependency
 		var fns = [];
+		
+		if (customFns != null)
+		{
+			for (var i = 0; i < customFns.length; i++)
+			{
+				fns.push(customFns[i]);
+			}
+		}
 
 		mxStencilRegistry.loadStencilSet(stencilFile, mxUtils.bind(this, function(packageName, stencilName, displayName, w, h)
 		{
@@ -3389,7 +3405,15 @@ Sidebar.prototype.addStencilPalette = function(id, title, stencilFile, style, ig
 			{
 				onInit.call(this, content);
 			}
-	
+			
+			if (customFns != null)
+			{
+				for (var i = 0; i < customFns.length; i++)
+				{
+					customFns[i](content);
+				}
+			}
+
 			mxStencilRegistry.loadStencilSet(stencilFile, mxUtils.bind(this, function(packageName, stencilName, displayName, w, h)
 			{
 				if (ignore == null || mxUtils.indexOf(ignore, stencilName) < 0)

File diff suppressed because it is too large
+ 300 - 297
war/js/reader.min.js


File diff suppressed because it is too large
+ 802 - 800
war/js/viewer.min.js


+ 175 - 0
war/plugins/find.js

@@ -0,0 +1,175 @@
+/**
+ * Explore plugin.
+ */
+Draw.loadPlugin(function(ui)
+{
+	var div = document.createElement('div');
+	div.style.userSelect = 'none';
+	div.style.overflow = 'hidden';
+	div.style.padding = '10px';
+	div.style.height = '100%';
+
+	var graph = ui.editor.graph;
+	var lastFound = null;
+
+	mxUtils.write(div, 'Find:');
+	mxUtils.br(div);
+	
+	var searchInput = document.createElement('input');
+	searchInput.setAttribute('type', 'text');
+	searchInput.style.marginTop = '4px';
+	searchInput.style.width = '170px';
+	div.appendChild(searchInput);
+
+	var tmp = document.createElement('div');
+	
+	function search(next)
+	{
+		var cells = graph.model.getDescendants(graph.model.getRoot());
+		var search = searchInput.value.toLowerCase();
+		var active = !next || lastFound == null;
+		var firstMatch = null;
+		
+		if (search.length > 0)
+		{
+			for (var i = 0; i < cells.length; i++)
+			{
+				var state = graph.view.getState(cells[i]);
+				
+				if (state != null && (active || firstMatch == null) &&
+					graph.model.isVertex(state.cell) || graph.model.isEdge(state.cell))
+				{
+					if (graph.isHtmlLabel(state.cell))
+					{
+						tmp.innerHTML = graph.getLabel(state.cell);
+						label = mxUtils.extractTextWithWhitespace([tmp]);
+					}
+					else
+					{					
+						label = graph.getLabel(state.cell);
+					}
+		
+					label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' ')).toLowerCase();
+					
+					if (label.substring(0, search.length) == search)
+					{
+						if (active)
+						{
+							firstMatch = state;
+						
+							break;
+						}
+						else if (firstMatch == null)
+						{
+							firstMatch = state;
+						}
+					}
+				}
+	
+				active = active || state == lastFound;
+			}
+		}
+					
+		if (firstMatch != null)
+		{
+			lastFound = firstMatch;
+			graph.setSelectionCell(lastFound.cell);
+			graph.scrollCellToVisible(lastFound.cell);
+		}
+		else
+		{
+			graph.clearSelection();
+		}
+		
+		return search.length == 0 || firstMatch != null;
+	};
+
+	mxUtils.br(div);
+
+	var resetBtn = mxUtils.button(mxResources.get('reset'), function()
+	{
+		searchInput.value = '';
+		searchInput.style.backgroundColor = '';
+		lastFound = null;
+		searchInput.focus();
+	});
+	
+	resetBtn.style.marginTop = '8px';
+	resetBtn.style.marginRight = '4px';
+	resetBtn.style.padding = '4px';
+	div.appendChild(resetBtn);
+
+	var btn = mxUtils.button('Find Again', function()
+	{
+		searchInput.style.backgroundColor = search(true) ? '' : '#ffcfcf';
+	});
+	
+	btn.style.marginTop = '8px';
+	btn.style.padding = '4px';
+	div.appendChild(btn);
+	
+	mxEvent.addListener(searchInput, 'keyup', function(evt)
+	{
+		searchInput.style.backgroundColor = search(evt.keyCode == 13) ? '' : '#ffcfcf';
+	});
+
+	var wnd = new mxWindow('Find', div, document.body.offsetWidth - 300, 140, 200, 120, true, true);
+	wnd.destroyOnClose = false;
+	wnd.setMaximizable(false);
+	wnd.setResizable(false);
+	wnd.setClosable(true);
+	
+	// Extends Extras menu
+	mxResources.parse('find=Find');
+
+    // Adds action
+    ui.actions.addAction('find...', function()
+    {
+		wnd.setVisible(!wnd.isVisible());
+		
+		if (wnd.isVisible())
+		{
+			searchInput.focus();
+			
+			if (mxClient.IS_FF || document.documentMode >= 5 || mxClient.IS_QUIRKS)
+			{
+				searchInput.select();
+			}
+			else
+			{
+				document.execCommand('selectAll', false, null);
+			}
+		}
+		else
+		{
+			graph.container.focus();
+		}
+    }, null, null, 'Ctrl+Space');
+	
+	var menu = ui.menus.get('edit');
+	var oldFunct = menu.funct;
+	
+	menu.funct = function(menu, parent)
+	{
+		oldFunct.apply(this, arguments);
+		
+		ui.menus.addMenuItems(menu, ['-', 'find'], parent);
+	};
+	
+	var findAction = ui.actions.get('find');
+	
+	var keyHandlerKeyDown = ui.keyHandler.keyDown;
+
+	ui.keyHandler.keyDown = function(evt)
+	{
+		if (evt.keyCode == 32 && mxEvent.isControlDown(evt))
+		{
+			findAction.funct();
+			mxEvent.consume(evt);
+		}
+		else
+		{
+			return keyHandlerKeyDown.apply(this, arguments);
+		}
+	};
+});

+ 5 - 5
war/plugins/sql.js

@@ -22,7 +22,10 @@ Draw.loadPlugin(function(ui)
 	
 	var graph = ui.editor.graph;
 	
-	var wnd = new mxWindow(mxResources.get('insertTable'), div, document.body.offsetWidth - 480, 140,
+	// Extends Extras menu
+	mxResources.parse('fromSql=From SQL');
+
+	var wnd = new mxWindow(mxResources.get('fromSql'), div, document.body.offsetWidth - 480, 140,
 		320, 300, true, true);
 	wnd.destroyOnClose = false;
 	wnd.setMaximizable(false);
@@ -131,7 +134,7 @@ Draw.loadPlugin(function(ui)
 	resetBtn.style.padding = '4px';
 	div.appendChild(resetBtn);
 
-	var btn = mxUtils.button(mxResources.get('close'), function()
+	var btn = mxUtils.button(mxResources.get('cancel'), function()
 	{
 		wnd.setVisible(false);
 	});
@@ -150,9 +153,6 @@ Draw.loadPlugin(function(ui)
 	btn.style.padding = '4px';
 	div.appendChild(btn);
 
-	// Extends Extras menu
-	mxResources.parse('fromSql=From SQL');
-
     // Adds action
     ui.actions.addAction('fromSql', function()
     {

+ 6 - 6
war/resources/dia_ru.txt

@@ -15,7 +15,7 @@ advanced=Расширенные
 align=Выровнять
 alignment=Выравнивание
 allChangesLost=Все изменения будут потеряны!
-allPages=All Pages
+allPages=Все страницы
 android=Android
 angle=Угол
 areYouSure=Вы уверены?
@@ -461,7 +461,7 @@ position=Положение
 posterPrint=Печать постера
 preferences=Предпочтения
 preview=Просмотр
-previousPage=Previous Page
+previousPage=Предыдущая страница
 print=Печать
 printAllPages=Пачать всех страниц
 procEng=Техн. процессы
@@ -536,7 +536,7 @@ sendMessage=Отправить
 sendYourFeedbackToDrawIo=Отправьте ваш отзыв о draw.io
 serviceUnavailableOrBlocked=Служба недоступна или заблокирована
 sessionExpired=Время вашей сессии истекло. Пожалуйста, обновите окно браузера.
-sessionTimeoutOnSave=Ваша сессия истекла и вы были отключены от Google Drive. Нажмите ОК для того чтобы войти снова и сохранить изменения.
+sessionTimeoutOnSave=Время вашей сессии истекло и вы были отключены от Google Drive. Нажмите ОК для того чтобы войти снова и сохранить изменения.
 setAsDefaultStyle=Установить как стиль по умолчанию
 shadow=Тень
 shape=Фигура
@@ -544,9 +544,9 @@ shapes=Фигуры
 share=Общий доступ
 shareLink=Ссылка для совместного редактирования
 sharp=Без скругления
-show=Show
-showStartScreen=Show Start Screen
-sidebarTooltip=Click to expand. Drag and drop shapes into the diagram. Shift+click to change selection. Alt+click to insert and connect.
+show=Показать
+showStartScreen=Начальный экран
+sidebarTooltip=Нажмите чтобы развернуть. Переместите фигуры на диаграмму. Нажмите, удерживая нажатым Shift, чтобы изменить выделение. Нажмите, удерживая нажатым Alt, чтобы вставить фигуру и нарисовать связь
 signs=Знаки и символы
 signOut=Выйти
 simple=Simple

File diff suppressed because it is too large
+ 1 - 1
war/shortcuts.svg