From 02c8721c2dc0ca18cba2c9422a3898f8e553b068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KB=20J=C3=B8rgensen?= Date: Thu, 18 Jan 2018 13:37:49 +0100 Subject: [PATCH 1/6] Implemented a clipboard that works across tabs and windows. --- editor/extensions/ext-connector.js | 34 ++++++-- editor/svg-editor.js | 12 ++- editor/svgcanvas.js | 120 ++++++++++++++++------------- editor/svgutils.js | 3 +- 4 files changed, 107 insertions(+), 62 deletions(-) diff --git a/editor/extensions/ext-connector.js b/editor/extensions/ext-connector.js index ed677db1..2386df53 100644 --- a/editor/extensions/ext-connector.js +++ b/editor/extensions/ext-connector.js @@ -160,6 +160,7 @@ svgEditor.addExtension("Connector", function(S) { // Loop through connectors to see if one is connected to the element connectors.each(function() { + var connector = this; var add_this; function add () { if ($.inArray(this, elems) !== -1) { @@ -167,12 +168,24 @@ svgEditor.addExtension("Connector", function(S) { add_this = true; } } - var start = elData(this, "c_start"); - var end = elData(this, "c_end"); - - var parts = [getElem(start), getElem(end)]; + + // Grab the ends + var parts = []; + ['start', 'end'].forEach(function (pos, i) { + var part = elData(this, 'c_'+pos); + if(part == null) { + part = document.getElementById( + connector.attributes['se:connector'].value.split(' ')[i] + ); + elData(connector, 'c_'+pos, part.id); + elData(connector, pos+'_bb', svgCanvas.getStrokedBBox([part])); + } + parts.push(part); + }); + for (i = 0; i < 2; i++) { var c_elem = parts[i]; + add_this = false; // The connected element might be part of a selected group $(c_elem).parents().each(add); @@ -261,7 +274,6 @@ svgEditor.addExtension("Connector", function(S) { var mse = svgCanvas.moveSelectedElements; svgCanvas.moveSelectedElements = function() { - svgCanvas.removeFromSelection($(conn_sel).toArray()); var cmd = mse.apply(this, arguments); updateConnectors(); return cmd; @@ -580,6 +592,18 @@ svgEditor.addExtension("Connector", function(S) { updateConnectors(); } }, + IDsUpdated: function(input) { + var remove = []; + input.elems.forEach(function(elem){ + if('se:connector' in elem.attr) { + elem.attr['se:connector'] = elem.attr['se:connector'].split(' ') + .map(function(oldID){ return input.changes[oldID] }).join(' '); + if(!/. ./.test(elem.attr['se:connector'])) + remove.push(elem.attr.id); + } + }); + return {remove: remove}; + }, toolButtonStateUpdate: function(opts) { if (opts.nostroke) { if ($('#mode_connect').hasClass('tool_button_current')) { diff --git a/editor/svg-editor.js b/editor/svg-editor.js index 95d1bbf2..0a051250 100644 --- a/editor/svg-editor.js +++ b/editor/svg-editor.js @@ -4818,9 +4818,6 @@ TODOS } break; } - if (svgCanvas.clipBoard.length) { - canv_menu.enableContextMenuItems('#paste,#paste_in_place'); - } } ); @@ -4865,6 +4862,15 @@ TODOS $('#cmenu_canvas li').disableContextMenu(); canv_menu.enableContextMenuItems('#delete,#cut,#copy'); + canv_menu[(localStorage.getItem('svgedit_clipboard') ? 'en' : 'dis') + 'ableContextMenuItems'] + ('#paste,#paste_in_place'); + window.addEventListener('storage', function(e) { + if(e.key != 'svgedit_clipboard') return; + + canv_menu[(localStorage.getItem('svgedit_clipboard') ? 'en' : 'dis') + 'ableContextMenuItems'] + ('#paste,#paste_in_place'); + }); + window.addEventListener('beforeunload', function(e) { // Suppress warning if page is empty if (undoMgr.getUndoStackSize() === 0) { diff --git a/editor/svgcanvas.js b/editor/svgcanvas.js index ec49107c..7071410d 100644 --- a/editor/svgcanvas.js +++ b/editor/svgcanvas.js @@ -177,6 +177,30 @@ var cur_shape = all_properties.shape; // default size of 1 until it needs to grow bigger var selectedElements = []; +var getJsonFromSvgElement = this.getJsonFromSvgElement = function(data) { + // Text node + if(data.nodeType == 3) return data.nodeValue; + + var retval = { + element: data.tagName, + //namespace: nsMap[data.namespaceURI], + attr: {}, + children: [], + }; + + // Itrate attributes + for(var i=0; i < data.attributes.length; i++) { + retval.attr[data.attributes[i].name] = data.attributes[i].value; + }; + + // Iterate children + for(var i=0; i < data.childNodes.length; i++) { + retval.children.push(getJsonFromSvgElement(data.childNodes[i])); + } + + return retval; +} + // Function: addSvgElementFromJson // Create a new SVG element based on the given object keys/values and add it to the current layer // The element will be ran through cleanupElement before being returned @@ -504,9 +528,6 @@ var encodableImages = {}, // Map of deleted reference elements removedElements = {}; -// Clipboard for cut, copy&pasted elements -canvas.clipBoard = []; - // Should this return an array by default, so extension results aren't overwritten? var runExtensions = this.runExtensions = function(action, vars, returnArray) { var result = returnArray ? [] : false; @@ -872,17 +893,13 @@ var root_sctm = null; // Parameters: // noCall - Optional boolean that when true does not call the "selected" handler var clearSelection = this.clearSelection = function(noCall) { - if (selectedElements[0] != null) { - var i, elem, - len = selectedElements.length; - for (i = 0; i < len; ++i) { - elem = selectedElements[i]; - if (elem == null) {break;} - selectorManager.releaseSelector(elem); - selectedElements[i] = null; - } -// selectedBBoxes[0] = null; - } + selectedElements.map(function(elem){ + if(elem == null) return; + + selectorManager.releaseSelector(elem); + }); + selectedElements = []; + if (!noCall) {call('selected', selectedElements);} }; @@ -6310,6 +6327,7 @@ this.deleteSelectedElements = function() { var batchCmd = new svgedit.history.BatchCommand('Delete Elements'); var len = selectedElements.length; var selectedCopy = []; //selectedElements is being deleted + for (i = 0; i < len; ++i) { var selected = selectedElements[i]; if (selected == null) {break;} @@ -6332,9 +6350,10 @@ this.deleteSelectedElements = function() { var nextSibling = t.nextSibling; var elem = parent.removeChild(t); selectedCopy.push(selected); //for the copy - selectedElements[i] = null; batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); } + selectedElements = []; + if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);} call('changed', selectedCopy); clearSelection(); @@ -6343,65 +6362,60 @@ this.deleteSelectedElements = function() { // Function: cutSelectedElements // Removes all selected elements from the DOM and adds the change to the // history stack. Remembers removed elements on the clipboard - -// TODO: Combine similar code with deleteSelectedElements this.cutSelectedElements = function() { - var i; - var batchCmd = new svgedit.history.BatchCommand('Cut Elements'); - var len = selectedElements.length; - var selectedCopy = []; //selectedElements is being deleted - for (i = 0; i < len; ++i) { - var selected = selectedElements[i]; - if (selected == null) {break;} - - var parent = selected.parentNode; - var t = selected; - - // this will unselect the element and remove the selectedOutline - selectorManager.releaseSelector(t); - - // Remove the path if present. - svgedit.path.removePath_(t.id); - - var nextSibling = t.nextSibling; - var elem = parent.removeChild(t); - selectedCopy.push(selected); //for the copy - selectedElements[i] = null; - batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); - } - if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);} - call('changed', selectedCopy); - clearSelection(); - - canvas.clipBoard = selectedCopy; + svgCanvas.copySelectedElements(); + svgCanvas.deleteSelectedElements(); }; // Function: copySelectedElements // Remembers the current selected elements on the clipboard this.copySelectedElements = function() { - canvas.clipBoard = $.merge([], selectedElements); + localStorage.setItem('svgedit_clipboard', JSON.stringify( + selectedElements.map(function(x){ return getJsonFromSvgElement(x) }) + )); + + $('#cmenu_canvas').enableContextMenuItems('#paste,#paste_in_place'); }; this.pasteElements = function(type, x, y) { - var cb = canvas.clipBoard; + var cb = JSON.parse(localStorage.getItem('svgedit_clipboard')); var len = cb.length; if (!len) {return;} var pasted = []; var batchCmd = new svgedit.history.BatchCommand('Paste elements'); var drawing = getCurrentDrawing(); + var changedIDs = {}; + + // Recursively replace IDs and record the changes + function checkIDs(elem) { + if(elem.attr && elem.attr.id) { + changedIDs[elem.attr.id] = getNextId(); + elem.attr.id = changedIDs[elem.attr.id]; + } + if(elem.children) elem.children.forEach(checkIDs); + } + cb.forEach(checkIDs); + + // Give extensions like the connector extension a chance to reflect new IDs and remove invalid elements + runExtensions('IDsUpdated', {elems: cb, changes: changedIDs}, true).forEach(function(extChanges){ + if(!extChanges || !('remove' in extChanges)) return; + + extChanges.remove.forEach(function(removeID){ + cb = cb.filter(function(cbItem){ + return cbItem.attr.id != removeID; + + }); + }); + }); // Move elements to lastClickPoint while (len--) { var elem = cb[len]; if (!elem) {continue;} - var copy = drawing.copyElem(elem); - // See if elem with elem ID is in the DOM already - if (!svgedit.utilities.getElem(elem.id)) {copy.id = elem.id;} - + var copy = addSvgElementFromJson(elem); pasted.push(copy); - (current_group || drawing.getCurrentLayer()).appendChild(copy); batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(copy)); restoreRefElems(copy); @@ -6433,7 +6447,7 @@ this.pasteElements = function(type, x, y) { }); var cmd = canvas.moveSelectedElements(dx, dy, false); - batchCmd.addSubCommand(cmd); + if(cmd) batchCmd.addSubCommand(cmd); } addCommandToHistory(batchCmd); diff --git a/editor/svgutils.js b/editor/svgutils.js index 1a2a93e4..e5eb7d3b 100644 --- a/editor/svgutils.js +++ b/editor/svgutils.js @@ -1191,8 +1191,9 @@ svgedit.utilities.copyElem = function(el, getNextId) { var ref = $(el).data('symbol'); $(new_el).data('ref', ref).data('symbol', ref); } else if (new_el.tagName == 'image') { - preventClickDefault(new_el); + svgedit.utilities.preventClickDefault(new_el); } + return new_el; }; From 524f47cab966f879950069afed9ebbff9e2672c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KB=20J=C3=B8rgensen?= Date: Fri, 19 Jan 2018 16:08:35 +0100 Subject: [PATCH 2/6] Let the 'this' keyword propagate in to the for each loop. --- editor/extensions/ext-connector.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/editor/extensions/ext-connector.js b/editor/extensions/ext-connector.js index 2386df53..6d9d7ab7 100644 --- a/editor/extensions/ext-connector.js +++ b/editor/extensions/ext-connector.js @@ -160,7 +160,6 @@ svgEditor.addExtension("Connector", function(S) { // Loop through connectors to see if one is connected to the element connectors.each(function() { - var connector = this; var add_this; function add () { if ($.inArray(this, elems) !== -1) { @@ -175,13 +174,13 @@ svgEditor.addExtension("Connector", function(S) { var part = elData(this, 'c_'+pos); if(part == null) { part = document.getElementById( - connector.attributes['se:connector'].value.split(' ')[i] + this.attributes['se:connector'].value.split(' ')[i] ); - elData(connector, 'c_'+pos, part.id); - elData(connector, pos+'_bb', svgCanvas.getStrokedBBox([part])); + elData(this, 'c_'+pos, part.id); + elData(this, pos+'_bb', svgCanvas.getStrokedBBox([part])); } parts.push(part); - }); + }.bind(this)); for (i = 0; i < 2; i++) { var c_elem = parts[i]; From 19170116ba8154b0089c16521e6ef0adfdb6b970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KB=20J=C3=B8rgensen?= Date: Mon, 22 Jan 2018 10:01:59 +0100 Subject: [PATCH 3/6] Fixed handling of connectors after the new clipboard changes. --- editor/extensions/ext-connector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/extensions/ext-connector.js b/editor/extensions/ext-connector.js index 6d9d7ab7..2512dd52 100644 --- a/editor/extensions/ext-connector.js +++ b/editor/extensions/ext-connector.js @@ -178,7 +178,7 @@ svgEditor.addExtension("Connector", function(S) { ); elData(this, 'c_'+pos, part.id); elData(this, pos+'_bb', svgCanvas.getStrokedBBox([part])); - } + } else part = document.getElementById(part); parts.push(part); }.bind(this)); From 055198e28d70180493fca2a38b543d5cee63a398 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KB=20J=C3=B8rgensen?= Date: Mon, 22 Jan 2018 10:08:40 +0100 Subject: [PATCH 4/6] Use a variable for the key, as requested by codedread. --- editor/extensions/ext-connector.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/editor/extensions/ext-connector.js b/editor/extensions/ext-connector.js index 2512dd52..d895e98f 100644 --- a/editor/extensions/ext-connector.js +++ b/editor/extensions/ext-connector.js @@ -171,7 +171,8 @@ svgEditor.addExtension("Connector", function(S) { // Grab the ends var parts = []; ['start', 'end'].forEach(function (pos, i) { - var part = elData(this, 'c_'+pos); + var key = 'c_' + pos; + var part = elData(this, key); if(part == null) { part = document.getElementById( this.attributes['se:connector'].value.split(' ')[i] From e055468f77acfb5d5e070dd682d1317aeef79fbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KB=20J=C3=B8rgensen?= Date: Mon, 22 Jan 2018 10:13:43 +0100 Subject: [PATCH 5/6] Added comment to explain meaning of a regex. --- editor/extensions/ext-connector.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/editor/extensions/ext-connector.js b/editor/extensions/ext-connector.js index d895e98f..9c6e631c 100644 --- a/editor/extensions/ext-connector.js +++ b/editor/extensions/ext-connector.js @@ -598,6 +598,9 @@ svgEditor.addExtension("Connector", function(S) { if('se:connector' in elem.attr) { elem.attr['se:connector'] = elem.attr['se:connector'].split(' ') .map(function(oldID){ return input.changes[oldID] }).join(' '); + + // Check validity - the field would be something like 'svg_21 svg_22', but + // if one end is missing, it would be 'svg_21' and therefore fail this test if(!/. ./.test(elem.attr['se:connector'])) remove.push(elem.attr.id); } From 42bcd5bcf880129ab218505129484b87cedf9adb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?KB=20J=C3=B8rgensen?= Date: Mon, 22 Jan 2018 10:14:42 +0100 Subject: [PATCH 6/6] Fixed typo in comment. --- editor/svgcanvas.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor/svgcanvas.js b/editor/svgcanvas.js index 7071410d..a41050b6 100644 --- a/editor/svgcanvas.js +++ b/editor/svgcanvas.js @@ -188,7 +188,7 @@ var getJsonFromSvgElement = this.getJsonFromSvgElement = function(data) { children: [], }; - // Itrate attributes + // Iterate attributes for(var i=0; i < data.attributes.length; i++) { retval.attr[data.attributes[i].name] = data.attributes[i].value; };