diff --git a/editor/ext-connector.js b/editor/ext-connector.js
new file mode 100644
index 00000000..dff8f08e
--- /dev/null
+++ b/editor/ext-connector.js
@@ -0,0 +1,243 @@
+$(function() {
+ svgCanvas.addExtension("Connector", function(vars) {
+
+ var svgcontent = vars.content;
+ var getNextId = vars.getNextId;
+ var addElem = vars.addSvgElementFromJson;
+ var selManager = vars.selectorManager;
+ var started = false,
+ start_x,
+ start_y,
+ cur_line,
+ start_elem,
+ end_elem,
+ connections = [],
+ conn_class = "se_connect",
+ connect_str = "-SE_CONNECT-",
+ selElems;
+
+ // Init code
+// (function() {
+//
+//
+// }());
+
+
+ return {
+ name: "Connector",
+ svgicons: "images/conn.svg",
+ buttons: [{
+ id: "mode_connect",
+ type: "mode",
+ icon: "images/cut.png",
+ events: {
+ 'click': function() {
+ svgCanvas.setMode("connector");
+ }
+ }
+ }],
+ mouseDown: function(opts) {
+ var e = opts.event;
+
+ start_x = opts.start_x,
+ start_y = opts.start_y;
+ var mode = svgCanvas.getMode();
+
+ if(mode == "connector") {
+
+ if(started) return;
+
+ if(e.target.parentNode.parentNode == svgcontent) {
+ // Connectable element
+ start_elem = e.target;
+
+ // Get center of source element
+ var bb = svgCanvas.getStrokedBBox([start_elem]);
+ var x = bb.x + bb.width/2;
+ var y = bb.y + bb.height/2;
+
+ started = true;
+ cur_line = addElem({
+ "element": "line",
+ "attr": {
+ "x1": x,
+ "y1": y,
+ "x2": start_x,
+ "y2": start_y,
+ "id": getNextId(),
+ "stroke": '#000',
+ "stroke-width": 1,
+ "fill": "none",
+ "opacity": .5,
+ "style": "pointer-events:none"
+ }
+ });
+ }
+ return {
+ started: true
+ };
+ } else if(mode == "select") {
+
+ // Check if selected elements have connections
+ var elems = opts.selectedElements;
+ var i = elems.length;
+ var connectors = $(svgcontent).find("." + conn_class);
+ if(!connectors.length) return;
+
+ connections = [];
+
+ while(i--) {
+ var elem = elems[i];
+ if(!elem) continue;
+ if(elem.getAttribute('class') == conn_class) continue;
+ var elem_id = elem.id;
+ connectors.each(function() {
+ var con_id = this.id;
+ if(con_id.indexOf(elem_id) != -1) {
+ var is_start = true;
+ if(con_id.indexOf(connect_str + elem_id) != -1) {
+ // Found connector (selected is end elem)
+ is_start = false;
+ }
+
+ var bb = svgCanvas.getStrokedBBox([elem]);
+ var x = bb.x + bb.width/2;
+ var y = bb.y + bb.height/2;
+
+ connections.push({
+ elem: elem,
+ connector: this,
+ is_start: is_start,
+ start_x: x,
+ start_y: y
+ });
+ }
+ });
+ }
+ }
+ },
+ mouseMove: function(opts) {
+ var zoom = svgCanvas.getZoom();
+ var e = opts.event;
+ var x = opts.mouse_x/zoom;
+ var y = opts.mouse_y/zoom;
+
+ var diff_x = x - start_x,
+ diff_y = y - start_y;
+
+ var mode = svgCanvas.getMode();
+
+ if(mode == "connector" && started) {
+ cur_line.setAttributeNS(null, "x2", x);
+ cur_line.setAttributeNS(null, "y2", y);
+ } else if(mode == "select") {
+ var slen = selElems.length;
+
+ while(slen--) {
+ var elem = selElems[slen];
+ // Look for selected connector elements
+ if(elem && elem.getAttribute('class') == conn_class) {
+ // Remove the "translate" transform given to move
+ svgCanvas.getTransformList(elem).clear();
+ }
+ }
+
+ if(connections.length) {
+ // Update line with element
+ var i = connections.length;
+
+ while(i--) {
+ var conn = connections[i];
+ var line = conn.connector;
+ var elem = conn.elem;
+ var n = conn.is_start ? 1 : 2;
+ line.setAttributeNS(null, "x"+n, conn.start_x + diff_x);
+ line.setAttributeNS(null, "y"+n, conn.start_y + diff_y);
+ }
+ }
+ }
+ },
+ mouseUp: function(opts) {
+ var zoom = svgCanvas.getZoom();
+ var e = opts.event,
+ x = opts.mouse_x/zoom,
+ y = opts.mouse_y/zoom;
+
+ if(svgCanvas.getMode() == "connector") {
+ if(e.target.parentNode.parentNode != svgcontent) {
+ // Not a valid target element, so remove line
+ $(cur_line).remove();
+ started = false;
+ return {
+ keep: false,
+ element: null,
+ started: started
+ }
+ } else if(e.target == start_elem) {
+ // Start line through click
+ started = true;
+ return {
+ keep: true,
+ element: null,
+ started: started
+ }
+ } else {
+ // Valid end element
+ end_elem = e.target;
+ var line_id = start_elem.id + connect_str + end_elem.id;
+ var alt_line_id = end_elem.id + connect_str + start_elem.id;
+
+ // Don't create connector if one already exists
+ if($('#'+line_id + ', #' + alt_line_id).length) {
+ $(cur_line).remove();
+ return {
+ keep: false,
+ element: null,
+ started: false
+ }
+ }
+
+ var bb = svgCanvas.getStrokedBBox([end_elem]);
+ var x = bb.x + bb.width/2;
+ var y = bb.y + bb.height/2;
+ cur_line.setAttributeNS(null, "x2", x);
+ cur_line.setAttributeNS(null, "y2", y);
+ cur_line.id = line_id;
+ console.log('cur_line',cur_line.id);
+ cur_line.setAttribute("class", conn_class);
+ svgCanvas.addToSelection([cur_line]);
+ svgCanvas.moveToBottomSelectedElement();
+
+ started = false;
+ return {
+ keep: true,
+ element: cur_line,
+ started: started
+ }
+ }
+ }
+ },
+ selectedChanged: function(opts) {
+
+ // Use this to update the current selected elements
+ selElems = opts.elems;
+
+ var i = selElems.length;
+
+// var to_hide = $('#tool_clone, #tool_topath, div.toolset:has(#angle), #line_panel');
+
+ while(i--) {
+ var elem = selElems[i];
+ if(elem && elem.getAttribute('class') == conn_class) {
+ selManager.requestSelector(elem).showGrips(false);
+
+ if(opts.selectedElement && !opts.multiselected) {
+ // TODO: Set up context tools and hide most regular line tools
+
+ }
+ }
+ }
+ }
+ };
+ });
+});
\ No newline at end of file
diff --git a/editor/images/conn.svg b/editor/images/conn.svg
new file mode 100644
index 00000000..8d0f3af7
--- /dev/null
+++ b/editor/images/conn.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/editor/svg-editor.html b/editor/svg-editor.html
index 6bb2570b..32c5cadc 100644
--- a/editor/svg-editor.html
+++ b/editor/svg-editor.html
@@ -18,6 +18,7 @@
+