Przeglądaj źródła

5.7.2-0 release

David Benson 8 lat temu
rodzic
commit
4bd72dfe75

+ 6 - 0
ChangeLog

@@ -1,3 +1,9 @@
+18-OCT-2016: 5.7.2-0
+
+- Switches to semantic versioning
+- Uses mxGraph 3.7.0.0 beta 6
+- Adds experimental sql plugin
+
 13-OCT-2016: 5.7.1.3
 
 - Fixes mxFile import with page disabled

+ 1 - 1
VERSION

@@ -1 +1 @@
-5.7.1.3
+5.7.2-0

Plik diff jest za duży
+ 4 - 4
etc/mxgraph/mxClient.js


+ 407 - 0
src/com/mxgraph/online/EmbedServlet.java

@@ -0,0 +1,407 @@
+/**
+ * $Id: EmbedServlet.java,v 1.18 2014/01/31 22:27:07 gaudenz Exp $
+ * Copyright (c) 2011-2012, JGraph Ltd
+ * 
+ * TODO
+ * 
+ * We could split the static part and the stencils into two separate requests
+ * in order for multiple graphs in the pages to not load the static part
+ * multiple times. This is only relevant if the embed arguments are different,
+ * in which case there is a problem with parsin the graph model too soon, ie.
+ * before certain stencils become available.
+ * 
+ * Easier solution is for the user to move the embed script to after the last
+ * graph in the page and merge the stencil arguments.
+ * 
+ * Note: The static part is roundly 105K, the stencils are much smaller in size.
+ * This means if the embed function is widely used, it will make sense to factor
+ * out the static part because only stencils will change between pages.
+ */
+package com.mxgraph.online;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.zip.GZIPOutputStream;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import com.google.appengine.api.utils.SystemProperty;
+
+/**
+ * Servlet implementation class OpenServlet
+ */
+public class EmbedServlet extends HttpServlet
+{
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 
+	 */
+	protected static String reader = null;
+
+	/**
+	 * 
+	 */
+	protected static String embed = null;
+	
+	/**
+	 * 
+	 */
+	protected static String embedDev = null;
+	
+	/**
+	 * 
+	 */
+	protected static String stylesheet = null;
+
+	/**
+	 * 
+	 */
+	protected static String lastModified = null;
+
+	/**
+	 * 
+	 */
+	protected HashMap<String, String> stencils = new HashMap<String, String>();
+
+	/**
+	 * 
+	 */
+	protected HashMap<String, String[]> libraries = new HashMap<String, String[]>();
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public EmbedServlet()
+	{
+		if (lastModified == null)
+		{
+			// Uses deployment date as lastModified header
+			String applicationVersion = SystemProperty.applicationVersion.get();
+			Date uploadDate = new Date(Long.parseLong(applicationVersion
+					.substring(applicationVersion.lastIndexOf(".") + 1))
+					/ (2 << 27) * 1000);
+
+			DateFormat httpDateFormat = new SimpleDateFormat(
+					"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
+			lastModified = httpDateFormat.format(uploadDate);
+		}
+
+		initLibraries(libraries);
+	}
+
+	/**
+	 * @see HttpServlet#HttpServlet()
+	 */
+	public static void initLibraries(HashMap<String, String[]> libraries)
+	{
+		libraries.put("bpmn", new String[] { "/shapes/bpmn/mxBpmnShape2.js",
+				"/stencils/bpmn.xml" });
+		libraries.put("er", new String[] { "/shapes/er/mxER.js" });
+		libraries.put("ios", new String[] { "/shapes/mockup/mxMockupiOS.js" });
+		libraries.put("ios7", new String[] { "/stencils/ios7.xml" });
+		libraries.put("android", new String[] { "/shapes/mxAndroid.js",
+				"/stencils/android/android.xml" });
+		libraries.put("lean_mapping", new String[] { "/shapes/mxLeanMap.js",
+				"/stencils/lean_mapping.xml" });
+		// Required for anchor shape which follows non-standard naming scheme (see Sidebar.js)
+		libraries.put("mockup",
+				new String[] { "/shapes/mockup/mxMockupButtons.js" });
+		libraries.put("mockup/buttons",
+				new String[] { "/shapes/mockup/mxMockupButtons.js" });
+		libraries.put("mockup/containers",
+				new String[] { "/shapes/mockup/mxMockupContainers.js" });
+		libraries.put("mockup/forms",
+				new String[] { "/shapes/mockup/mxMockupForms.js" });
+		libraries.put("mockup/graphics", new String[] {
+				"/shapes/mockup/mxMockupGraphics.js",
+				"/stencils/mockup/misc.xml" });
+		libraries.put("mockup/markup",
+				new String[] { "/shapes/mockup/mxMockupMarkup.js" });
+		libraries
+				.put("mockup/misc", new String[] {
+						"/shapes/mockup/mxMockupMisc.js",
+						"/stencils/mockup/misc.xml" });
+		libraries.put("mockup/navigation", new String[] {
+				"/shapes/mockup/mxMockupNavigation.js",
+				"/stencils/mockup/misc.xml" });
+		libraries.put("mockup/text",
+				new String[] { "/shapes/mockup/mxMockupText.js" });
+		libraries.put("pid2inst",
+				new String[] { "/shapes/pid2/mxPidInstruments.js" });
+		libraries.put("pid2misc", new String[] { "/shapes/pid2/mxPidMisc.js",
+				"/stencils/pid/misc.xml" });
+		libraries.put("pid2valves",
+				new String[] { "/shapes/pid2/mxPidValves.js" });
+		libraries.put("floorplan", new String[] { "/shapes/mxFloorplan.js",
+				"/stencils/floorplan.xml" });
+		libraries.put("archimate", new String[] { "/shapes/mxArchiMate.js" });
+		libraries.put("azure", new String[] { "/stencils/azure.xml" });
+	}
+
+	/**
+	 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
+	 */
+	protected void doGet(HttpServletRequest request,
+			HttpServletResponse response) throws ServletException, IOException
+	{
+		try
+		{
+			String qs = request.getQueryString();
+
+			if (qs != null && qs.equals("stats"))
+			{
+				writeStats(response);
+			}
+			else
+			{
+				if (reader == null)
+				{
+					reader = readFile("/js/reader.min.js");
+				}
+
+				if (embed == null)
+				{
+					embed = readFile("/js/embed.min.js");
+				}
+				
+				if (embedDev == null)
+				{
+					embedDev = readFile("/js/embed.dev.js");
+				}
+
+				if (stylesheet == null)
+				{
+					stylesheet = readXmlFile("/styles/default.xml", true);
+				}
+
+				// Checks or sets last modified date of delivered content.
+				// Date comparison not needed. Only return 304 if
+				// delivered by this servlet instance.
+				String modSince = request.getHeader("If-Modified-Since");
+
+				if (modSince != null && modSince.equals(lastModified))
+				{
+					response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+				}
+				else
+				{
+					writeEmbedResponse(request, response);
+				}
+			}
+		}
+		catch (Exception e)
+		{
+			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+		}
+	}
+
+	public void writeEmbedResponse(HttpServletRequest request,
+			HttpServletResponse response) throws IOException
+	{
+		response.setCharacterEncoding("UTF-8");
+		response.setContentType("application/javascript; charset=UTF-8");
+		response.setHeader("Last-Modified", lastModified);
+
+		OutputStream out = response.getOutputStream();
+
+		// FIXME: Accept-encoding header is missing
+		String encoding = request.getHeader("Accept-Encoding");
+
+		// Supports GZIP content encoding
+		if (encoding != null && encoding.indexOf("gzip") >= 0)
+		{
+			response.setHeader("Content-Encoding", "gzip");
+			out = new GZIPOutputStream(out);
+		}
+
+		// Creates XML for stencils
+		PrintWriter writer = new PrintWriter(out);
+
+		// Writes JavaScript and adds function call with
+		// stylesheet and stencils as arguments 
+		writer.println(createEmbedJavaScript(request.getParameter("s"), request.getParameter("dev")));
+		response.setStatus(HttpServletResponse.SC_OK);
+
+		writer.flush();
+		writer.close();
+	}
+
+	public String createEmbedJavaScript(String sparam, String dev) throws IOException
+	{
+		StringBuffer result = new StringBuffer("[");
+		StringBuffer js = new StringBuffer("");
+
+		// Processes each stencil only once
+		HashSet<String> done = new HashSet<String>();
+		
+		// Processes each lib only once
+		HashSet<String> libsLoaded = new HashSet<String>();
+
+		if (sparam != null)
+		{
+			String[] names = sparam.split(";");
+
+			for (int i = 0; i < names.length; i++)
+			{
+				if (names[i].indexOf("..") < 0 && !done.contains(names[i]))
+				{
+					if (names[i].equals("*"))
+					{
+						js.append(readXmlFile("/js/shapes.min.js", false));
+						result.append("'" + readXmlFile("/stencils.xml", true)
+								+ "'");
+					}
+					else
+					{
+						// Checks if any JS files are associated with the library
+						// name and injects the JS into the page
+						String[] libs = libraries.get(names[i]);
+
+						if (libs != null)
+						{
+							for (int j = 0; j < libs.length; j++)
+							{
+								if (!libsLoaded.contains(libs[j]))
+								{
+									String tmp = stencils.get(libs[j]);
+									libsLoaded.add(libs[j]);
+									
+									if (tmp == null)
+									{
+										try
+										{
+											tmp = readXmlFile(libs[j], !libs[j]
+													.toLowerCase().endsWith(".js"));
+	
+											// Cache for later use
+											if (tmp != null)
+											{
+												stencils.put(libs[j], tmp);
+											}
+										}
+										catch (NullPointerException e)
+										{
+											// This seems possible according to access log so ignore stencil
+										}
+									}
+	
+									if (tmp != null)
+									{
+										// TODO: Add JS to Javascript code inline. This had to be done to quickly
+										// add JS-based dynamic loading to the existing embed setup where everything
+										// dynamic is passed via function call, so an indirection via eval must be
+										// used even though the JS could be parsed directly by adding it to JS.
+										if (libs[j].toLowerCase().endsWith(".js"))
+										{
+											js.append(tmp);
+										}
+										else
+										{
+											if (result.length() > 1)
+											{
+												result.append(",");
+											}
+											
+											result.append("'" + tmp + "'");
+										}
+									}
+								}
+							}
+						}
+						else
+						{
+							String tmp = stencils.get(names[i]);
+
+							if (tmp == null)
+							{
+								try
+								{
+									tmp = readXmlFile("/stencils/" + names[i]
+											+ ".xml", true);
+
+									// Cache for later use
+									if (tmp != null)
+									{
+										stencils.put(names[i], tmp);
+									}
+								}
+								catch (NullPointerException e)
+								{
+									// This seems possible according to access log so ignore stencil
+								}
+							}
+
+							if (tmp != null)
+							{
+								if (result.length() > 1)
+								{
+									result.append(",");
+								}
+								
+								result.append("'" + tmp + "'");
+							}
+						}
+					}
+
+					done.add(names[i]);
+				}
+			}
+		}
+
+		result.append("]");
+		
+		String tmp = (dev != null && dev.equals("1")) ? embedDev : embed;
+
+		// JS must be executed after core but before embed function
+		return reader + "\n" + js + tmp + "})('" + stylesheet + "',"
+				+ result.toString() + ");";
+	}
+
+	public void writeStats(HttpServletResponse response) throws IOException
+	{
+		PrintWriter writer = new PrintWriter(response.getOutputStream());
+		writer.println("<html>");
+		writer.println("<body>");
+		writer.println("Deployed: " + lastModified);
+		writer.println("</body>");
+		writer.println("</html>");
+		writer.flush();
+	}
+
+	public String readXmlFile(String filename, boolean xmlContent)
+			throws IOException
+	{
+		String result = readFile(filename);
+
+		if (xmlContent)
+		{
+			result = result.replaceAll("'", "\\\\'").replaceAll("\t", "")
+					.replaceAll("\n", "");
+		}
+
+		return result;
+	}
+
+	public String readFile(String filename) throws IOException
+	{
+		InputStream is = getServletContext().getResourceAsStream(filename);
+
+		return Utils.readInputStream(is);
+	}
+
+}

+ 1 - 1
war/WEB-INF/appengine-web.xml

@@ -2,7 +2,7 @@
 <appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
 	<application>drawdotio</application>
 	<!-- IMPORTANT! DO NOT CHANGE THIS VALUE IN SOURCE CONTROL! -->
-	<version>5-7-1-3</version>
+	<version>test</version>
 	
 	<!-- Configure java.util.logging -->
 	<system-properties>

+ 1 - 1
war/cache.manifest

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

Plik diff jest za duży
+ 46 - 43
war/js/app.min.js


Plik diff jest za duży
+ 13 - 10
war/js/atlas-viewer.min.js


Plik diff jest za duży
+ 41 - 37
war/js/atlas.min.js


+ 1 - 1
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'};
+	'tags': '/plugins/tags.js', 'sql': '/plugins/sql.js'};
 
 /**
  * Function: authorize

+ 3 - 2
war/js/diagramly/Dialogs.js

@@ -2953,10 +2953,11 @@ var ParseDialog = function(editorUi, title)
 			var bds = editorUi.editor.graph.getGraphBounds();
 			
 			// Computes unscaled, untranslated graph bounds
-			var x = Math.max(0, bds.x / view.scale - view.translate.x) + graph.gridSize;
-			var y = Math.max(0, (bds.y + bds.height) / view.scale - view.translate.y) + 4 * graph.gridSize;
+			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);

+ 1 - 17
war/js/diagramly/DriveRealtime.js

@@ -830,24 +830,8 @@ DriveRealtime.prototype.installPageSelectListener = function()
 		// Applies view state from realtime model without firing events
 		if (page.viewState == null)
 		{
-			// Resets part of the view that is in view state but not in realtime model
-			// via activate and not in setViewState (if state is null)
-			var graph = this.ui.editor.graph;
-			graph.view.scale = 1;
-			graph.gridEnabled = !this.ui.editor.chromeless || urlParams['grid'] == '1';
-			graph.gridSize = mxGraph.prototype.gridSize;
-			graph.pageScale = mxGraph.prototype.pageScale;
-			graph.pageVisible = this.ui.editor.graph.defaultPageVisible;
-			graph.scrollbars = this.ui.editor.graph.defaultScrollbars;
-			graph.graphHandler.guidesEnabled = true;
-			graph.defaultParent = null;
-			graph.setTooltips(true);
-			graph.setConnectable(true);
-			graph.setTooltips(true);
-			
-			// Actives from realtime without calling event listeners
+			// Activates from realtime without calling event listeners
 			page.mapping.activate(true);
-			this.ui.resetScrollbars();
 		}
 	});
 	

+ 30 - 6
war/js/diagramly/Pages.js

@@ -356,6 +356,12 @@ EditorUi.prototype.initPages = function()
 			}
 		}
 	}));
+	
+	// Updates zoom in toolbar
+	if (this.toolbar != null)
+	{
+		this.editor.addListener('pageSelected', this.toolbar.updateZoom);
+	}
 };
 
 /**
@@ -386,6 +392,7 @@ Graph.prototype.createViewState = function(node)
 				parseFloat(pw), parseFloat(ph)) : this.pageFormat,
 		tooltips: node.getAttribute('tooltips') != '0',
 		connect: node.getAttribute('connect') != '0',
+		arrows: node.getAttribute('arrows') != '0',
 		mathEnabled: node.getAttribute('math') != '0',
 		selectionCells: null,
 		defaultParent: null,
@@ -414,8 +421,9 @@ Graph.prototype.getViewState = function()
 		backgroundImage: this.backgroundImage,
 		pageScale: this.pageScale,
 		pageFormat: this.pageFormat,
-		tooltips: this.tooltipHandler.isEnabled() ? '1' : '0',
-		connect: this.connectionHandler.isEnabled() ? '1' : '0',	
+		tooltips: this.tooltipHandler.isEnabled(),
+		connect: this.connectionHandler.isEnabled(),
+		arrows: this.connectionArrowsEnabled,
 		scale: this.view.scale,
 		scrollLeft: this.container.scrollLeft,
 		scrollTop: this.container.scrollTop,
@@ -451,6 +459,7 @@ Graph.prototype.setViewState = function(state)
 		this.view.scale = state.scale;
 		this.view.currentRoot = state.currentRoot;
 		this.defaultParent = state.defaultParent;
+		this.connectionArrowsEnabled = state.arrows;
 		this.setTooltips(state.tooltips);
 		this.setConnectable(state.connect);
 		
@@ -458,18 +467,33 @@ Graph.prototype.setViewState = function(state)
 		{
 			this.view.translate = state.translate;
 		}
-
-		// Implicit settings
-		this.pageBreaksVisible = this.pageVisible; 
-		this.preferPageSize = this.pageVisible;
 	}
 	else
 	{
 		this.view.currentRoot = null;
+		this.view.scale = 1;
+		this.gridEnabled = true;
+		this.gridSize = mxGraph.prototype.gridSize;
+		this.pageScale = mxGraph.prototype.pageScale;
+		this.pageFormat = mxSettings.getPageFormat();
+		this.pageVisible = this.defaultPageVisible;
+		this.background = this.defaultGraphBackground;
+		this.backgroundImage = null;
+		this.scrollbars = this.defaultScrollbars;
+		this.graphHandler.guidesEnabled = true;
+		this.foldingEnabled = true;
+		this.defaultParent = null;
+		this.setTooltips(true);
+		this.setConnectable(true);
 		this.lastPasteXml = null;
 		this.pasteCounter = 0;
 		this.mathEnabled = false;
+		this.connectionArrowsEnabled = true;
 	}
+	
+	// Implicit settings
+	this.pageBreaksVisible = this.pageVisible; 
+	this.preferPageSize = this.pageVisible;
 };
 
 /**

Plik diff jest za duży
+ 4 - 3
war/js/embed-static.min.js


+ 19 - 14
war/js/mxgraph/Format.js

@@ -1057,21 +1057,26 @@ BaseFormatPanel.prototype.createColorOption = function(label, getColorFn, setCol
 
 	mxEvent.addListener(div, 'click', function(evt)
 	{
-		// Toggles checkbox state for click on label
-		if (mxEvent.getSource(evt) != cb)
-		{
-			cb.checked = !cb.checked;
-		}
-
-		// Overrides default value with current value to make it easier
-		// to restore previous value if the checkbox is clicked twice
-		if (!cb.checked && value != null && value != mxConstants.NONE &&
-			defaultColor != mxConstants.NONE)
-		{
-			defaultColor = value;
-		}
+		var source = mxEvent.getSource(evt);
 		
-		apply((cb.checked) ? defaultColor : mxConstants.NONE);
+		if (source == cb || source.nodeName != 'INPUT')
+		{		
+			// Toggles checkbox state for click on label
+			if (source != cb)
+			{
+				cb.checked = !cb.checked;
+			}
+	
+			// Overrides default value with current value to make it easier
+			// to restore previous value if the checkbox is clicked twice
+			if (!cb.checked && value != null && value != mxConstants.NONE &&
+				defaultColor != mxConstants.NONE)
+			{
+				defaultColor = value;
+			}
+			
+			apply((cb.checked) ? defaultColor : mxConstants.NONE);
+		}
 	});
 	
 	apply(value, true);

+ 37 - 0
war/js/mxgraph/Graph.js

@@ -1636,6 +1636,43 @@ Graph.prototype.connectVertex = function(source, direction, length, evt, forceCl
 	return result;
 };
 
+/**
+ * Returns all labels in the diagram as a string.
+ */
+Graph.prototype.getIndexableText = function()
+{
+	var tmp = document.createElement('div');
+	var labels = [];
+	var label = '';
+	
+	for (var key in this.model.cells)
+	{
+		var cell = this.model.cells[key];
+		
+		if (this.model.isVertex(cell) || this.model.isEdge(cell))
+		{
+			if (this.isHtmlLabel(cell))
+			{
+				tmp.innerHTML = this.getLabel(cell);
+				label = mxUtils.extractTextWithWhitespace([tmp]);
+			}
+			else
+			{					
+				label = this.getLabel(cell);
+			}
+
+			label = mxUtils.trim(label.replace(/[\x00-\x1F\x7F-\x9F]|\s+/g, ' '));
+			
+			if (label.length > 0)
+			{
+				labels.push(label);
+			}
+		}
+	}
+	
+	return labels.join(' ');
+};
+
 /**
  * Returns the label for the given cell.
  */

+ 7 - 4
war/js/mxgraph/Toolbar.js

@@ -90,7 +90,7 @@ Toolbar.prototype.init = function()
 	}
 	
 	// Updates the label if the scale changes
-	var updateZoom = mxUtils.bind(this, function()
+	this.updateZoom = mxUtils.bind(this, function()
 	{
 		viewMenu.innerHTML = Math.round(this.editorUi.editor.graph.view.scale * 100) + '%' +
 			this.dropdownImageHtml;
@@ -102,8 +102,8 @@ Toolbar.prototype.init = function()
 		}
 	});
 
-	this.editorUi.editor.graph.view.addListener(mxEvent.EVENT_SCALE, updateZoom);
-	this.editorUi.editor.addListener('resetGraphView', updateZoom);
+	this.editorUi.editor.graph.view.addListener(mxEvent.EVENT_SCALE, this.updateZoom);
+	this.editorUi.editor.addListener('resetGraphView', this.updateZoom);
 
 	var elts = this.addItems(['-', 'undo', 'redo']);
 	elts[1].setAttribute('title', mxResources.get('undo') + ' (' + this.editorUi.actions.get('undo').shortcut + ')');
@@ -645,7 +645,10 @@ Toolbar.prototype.hideMenu = function()
 Toolbar.prototype.addMenu = function(label, tooltip, showLabels, name, c, showAll)
 {
 	var menu = this.editorUi.menus.get(name);
-	var elt = this.addMenuFunction(label, tooltip, showLabels, menu.funct, c, showAll);
+	var elt = this.addMenuFunction(label, tooltip, showLabels, function()
+	{
+		menu.funct.apply(menu, arguments);
+	}, c, showAll);
 	
 	menu.addListener('stateChanged', function()
 	{

Plik diff jest za duży
+ 4 - 3
war/js/reader.min.js


Plik diff jest za duży
+ 13 - 10
war/js/viewer.min.js


+ 176 - 0
war/plugins/sql.js

@@ -0,0 +1,176 @@
+/**
+ * Parse SQL CREATE TABLE. Simple initial version for community to improve.
+ */
+Draw.loadPlugin(function(ui)
+{
+	// LATER: REFERENCES and PRIMARY KEY
+	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 sqlInput = document.createElement('textarea');
+	sqlInput.style.height = '200px';
+	sqlInput.style.width = '100%';
+  	sqlInput.value = 'CREATE TABLE Persons\n(\nPersonID int,\nLastName varchar(255),\n' +
+  		'FirstName varchar(255),\nAddress varchar(255),\nCity varchar(255)\n);';
+	mxUtils.br(div);
+	div.appendChild(sqlInput);
+	
+	var graph = ui.editor.graph;
+	
+	var wnd = new mxWindow(mxResources.get('insertTable'), div, document.body.offsetWidth - 480, 140,
+		320, 300, true, true);
+	wnd.destroyOnClose = false;
+	wnd.setMaximizable(false);
+	wnd.setResizable(false);
+	wnd.setClosable(true);
+
+	function parseSql(text)
+	{
+		var lines = text.split('\n');
+		var tableCell = null;
+		var rows = null;
+		var cells = [];
+		var dx = 0;
+
+		for (var i = 0; i < lines.length; i++)
+		{
+			var tmp = mxUtils.trim(lines[i]);
+			
+			if (tmp.substring(0, 12).toLowerCase() == 'create table')
+			{
+				var name = mxUtils.trim(tmp.substring(12));
+				
+				if (name.charAt(name.length - 1) == '(')
+				{
+					name = name.substring(0, name.lastIndexOf(' '));
+				}
+				
+				tableCell = new mxCell(name, new mxGeometry(dx, 0, 160, 26),
+			    	'swimlane;fontStyle=0;childLayout=stackLayout;horizontal=1;startSize=26;fillColor=#e0e0e0;horizontalStack=0;resizeParent=1;resizeLast=0;collapsible=1;marginBottom=0;swimlaneFillColor=#ffffff;align=center;');
+				tableCell.vertex = true;
+				cells.push(tableCell);
+				
+				var size = ui.editor.graph.getPreferredSizeForCell(rowCell);
+	   			
+	   			if (size != null)
+	   			{
+	   				tableCell.geometry.width = size.width + 10;
+	   			}
+	   			
+	   			// For primary key lookups
+	   			rows = {};
+			}
+			else if (tableCell != null && tmp.charAt(0) == ')')
+			{
+				dx += tableCell.geometry.width + 40;
+				tableCell = null;
+			}
+			else if (tmp != '(' && tableCell != null)
+			{
+				var name = tmp.substring(0, (tmp.charAt(tmp.length - 1) == ',') ? tmp.length - 1 : tmp.length);
+				
+				if (name.substring(0, 11).toLowerCase() != 'primary key')
+				{
+					var rowCell = new mxCell(name, new mxGeometry(0, 0, 90, 26),
+						'shape=partialRectangle;top=0;left=0;right=0;bottom=0;align=left;verticalAlign=top;spacingTop=-2;fillColor=none;spacingLeft=34;spacingRight=4;overflow=hidden;rotatable=0;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;dropTarget=0;');
+		   			rowCell.vertex = true;
+	
+					var left = sb.cloneCell(rowCell, '' /* eg. PK */);
+		   			left.connectable = false;
+		   			left.style = 'shape=partialRectangle;top=0;left=0;bottom=0;fillColor=none;align=left;verticalAlign=middle;spacingLeft=4;spacingRight=4;overflow=hidden;rotatable=0;points=[];portConstraint=eastwest;part=1;'
+		   			left.geometry.width = 30;
+		   			left.geometry.height = 26;
+		   			rowCell.insert(left);
+		   			
+		   			var size = ui.editor.graph.getPreferredSizeForCell(rowCell);
+		   			
+		   			if (size != null && tableCell.geometry.width < size.width + 10)
+		   			{
+		   				tableCell.geometry.width = size.width + 10;
+		   			}
+		   			
+		   			tableCell.insert(rowCell);
+		   			tableCell.geometry.height += 26;
+		   			
+		   			rows[rowCell.value] = rowCell;
+				}
+			}
+		}
+		
+		if (cells.length > 0)
+		{
+			var graph = ui.editor.graph;
+			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(cells, x, y));
+			graph.scrollCellToVisible(graph.getSelectionCell());
+		}
+		
+		wnd.setVisible(false);
+	};
+
+	mxUtils.br(div);
+
+	var resetBtn = mxUtils.button(mxResources.get('reset'), function()
+	{
+		sqlInput.value = '';
+	});
+	
+	resetBtn.style.marginTop = '8px';
+	resetBtn.style.marginRight = '4px';
+	resetBtn.style.padding = '4px';
+	div.appendChild(resetBtn);
+
+	var btn = mxUtils.button(mxResources.get('close'), function()
+	{
+		wnd.setVisible(false);
+	});
+	
+	btn.style.marginTop = '8px';
+	btn.style.marginRight = '4px';
+	btn.style.padding = '4px';
+	div.appendChild(btn);
+
+	var btn = mxUtils.button(mxResources.get('insert'), function()
+	{
+		parseSql(sqlInput.value);
+	});
+	
+	btn.style.marginTop = '8px';
+	btn.style.padding = '4px';
+	div.appendChild(btn);
+
+	// Extends Extras menu
+	mxResources.parse('fromSql=From SQL');
+
+    // Adds action
+    ui.actions.addAction('fromSql', function()
+    {
+		wnd.setVisible(!wnd.isVisible());
+		
+		if (wnd.isVisible())
+		{
+			sqlInput.focus();	
+		}
+    });
+	
+	var theMenu = ui.menus.get('insert');
+	var oldMenu = theMenu.funct;
+	
+	theMenu.funct = function(menu, parent)
+	{
+		oldMenu.apply(this, arguments);
+		
+		ui.menus.addMenuItems(menu, ['fromSql'], parent);
+	};
+});