Implemented a clipboard that works across tabs and windows.

master
KB Jørgensen 2018-01-18 13:37:49 +01:00
parent 6f7aa650f8
commit 02c8721c2d
4 changed files with 107 additions and 62 deletions

View File

@ -160,6 +160,7 @@ svgEditor.addExtension("Connector", function(S) {
// Loop through connectors to see if one is connected to the element // Loop through connectors to see if one is connected to the element
connectors.each(function() { connectors.each(function() {
var connector = this;
var add_this; var add_this;
function add () { function add () {
if ($.inArray(this, elems) !== -1) { if ($.inArray(this, elems) !== -1) {
@ -167,12 +168,24 @@ svgEditor.addExtension("Connector", function(S) {
add_this = true; 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++) { for (i = 0; i < 2; i++) {
var c_elem = parts[i]; var c_elem = parts[i];
add_this = false; add_this = false;
// The connected element might be part of a selected group // The connected element might be part of a selected group
$(c_elem).parents().each(add); $(c_elem).parents().each(add);
@ -261,7 +274,6 @@ svgEditor.addExtension("Connector", function(S) {
var mse = svgCanvas.moveSelectedElements; var mse = svgCanvas.moveSelectedElements;
svgCanvas.moveSelectedElements = function() { svgCanvas.moveSelectedElements = function() {
svgCanvas.removeFromSelection($(conn_sel).toArray());
var cmd = mse.apply(this, arguments); var cmd = mse.apply(this, arguments);
updateConnectors(); updateConnectors();
return cmd; return cmd;
@ -580,6 +592,18 @@ svgEditor.addExtension("Connector", function(S) {
updateConnectors(); 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) { toolButtonStateUpdate: function(opts) {
if (opts.nostroke) { if (opts.nostroke) {
if ($('#mode_connect').hasClass('tool_button_current')) { if ($('#mode_connect').hasClass('tool_button_current')) {

View File

@ -4818,9 +4818,6 @@ TODOS
} }
break; break;
} }
if (svgCanvas.clipBoard.length) {
canv_menu.enableContextMenuItems('#paste,#paste_in_place');
}
} }
); );
@ -4865,6 +4862,15 @@ TODOS
$('#cmenu_canvas li').disableContextMenu(); $('#cmenu_canvas li').disableContextMenu();
canv_menu.enableContextMenuItems('#delete,#cut,#copy'); 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) { window.addEventListener('beforeunload', function(e) {
// Suppress warning if page is empty // Suppress warning if page is empty
if (undoMgr.getUndoStackSize() === 0) { if (undoMgr.getUndoStackSize() === 0) {

View File

@ -177,6 +177,30 @@ var cur_shape = all_properties.shape;
// default size of 1 until it needs to grow bigger // default size of 1 until it needs to grow bigger
var selectedElements = []; 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 // Function: addSvgElementFromJson
// Create a new SVG element based on the given object keys/values and add it to the current layer // 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 // The element will be ran through cleanupElement before being returned
@ -504,9 +528,6 @@ var encodableImages = {},
// Map of deleted reference elements // Map of deleted reference elements
removedElements = {}; removedElements = {};
// Clipboard for cut, copy&pasted elements
canvas.clipBoard = [];
// Should this return an array by default, so extension results aren't overwritten? // Should this return an array by default, so extension results aren't overwritten?
var runExtensions = this.runExtensions = function(action, vars, returnArray) { var runExtensions = this.runExtensions = function(action, vars, returnArray) {
var result = returnArray ? [] : false; var result = returnArray ? [] : false;
@ -872,17 +893,13 @@ var root_sctm = null;
// Parameters: // Parameters:
// noCall - Optional boolean that when true does not call the "selected" handler // noCall - Optional boolean that when true does not call the "selected" handler
var clearSelection = this.clearSelection = function(noCall) { var clearSelection = this.clearSelection = function(noCall) {
if (selectedElements[0] != null) { selectedElements.map(function(elem){
var i, elem, if(elem == null) return;
len = selectedElements.length;
for (i = 0; i < len; ++i) {
elem = selectedElements[i];
if (elem == null) {break;}
selectorManager.releaseSelector(elem); selectorManager.releaseSelector(elem);
selectedElements[i] = null; });
} selectedElements = [];
// selectedBBoxes[0] = null;
}
if (!noCall) {call('selected', selectedElements);} if (!noCall) {call('selected', selectedElements);}
}; };
@ -6310,6 +6327,7 @@ this.deleteSelectedElements = function() {
var batchCmd = new svgedit.history.BatchCommand('Delete Elements'); var batchCmd = new svgedit.history.BatchCommand('Delete Elements');
var len = selectedElements.length; var len = selectedElements.length;
var selectedCopy = []; //selectedElements is being deleted var selectedCopy = []; //selectedElements is being deleted
for (i = 0; i < len; ++i) { for (i = 0; i < len; ++i) {
var selected = selectedElements[i]; var selected = selectedElements[i];
if (selected == null) {break;} if (selected == null) {break;}
@ -6332,9 +6350,10 @@ this.deleteSelectedElements = function() {
var nextSibling = t.nextSibling; var nextSibling = t.nextSibling;
var elem = parent.removeChild(t); var elem = parent.removeChild(t);
selectedCopy.push(selected); //for the copy selectedCopy.push(selected); //for the copy
selectedElements[i] = null;
batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent)); batchCmd.addSubCommand(new RemoveElementCommand(elem, nextSibling, parent));
} }
selectedElements = [];
if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);} if (!batchCmd.isEmpty()) {addCommandToHistory(batchCmd);}
call('changed', selectedCopy); call('changed', selectedCopy);
clearSelection(); clearSelection();
@ -6343,65 +6362,60 @@ this.deleteSelectedElements = function() {
// Function: cutSelectedElements // Function: cutSelectedElements
// Removes all selected elements from the DOM and adds the change to the // Removes all selected elements from the DOM and adds the change to the
// history stack. Remembers removed elements on the clipboard // history stack. Remembers removed elements on the clipboard
// TODO: Combine similar code with deleteSelectedElements
this.cutSelectedElements = function() { this.cutSelectedElements = function() {
var i; svgCanvas.copySelectedElements();
var batchCmd = new svgedit.history.BatchCommand('Cut Elements'); svgCanvas.deleteSelectedElements();
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;
}; };
// Function: copySelectedElements // Function: copySelectedElements
// Remembers the current selected elements on the clipboard // Remembers the current selected elements on the clipboard
this.copySelectedElements = function() { 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) { this.pasteElements = function(type, x, y) {
var cb = canvas.clipBoard; var cb = JSON.parse(localStorage.getItem('svgedit_clipboard'));
var len = cb.length; var len = cb.length;
if (!len) {return;} if (!len) {return;}
var pasted = []; var pasted = [];
var batchCmd = new svgedit.history.BatchCommand('Paste elements'); var batchCmd = new svgedit.history.BatchCommand('Paste elements');
var drawing = getCurrentDrawing(); 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 // Move elements to lastClickPoint
while (len--) { while (len--) {
var elem = cb[len]; var elem = cb[len];
if (!elem) {continue;} 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); pasted.push(copy);
(current_group || drawing.getCurrentLayer()).appendChild(copy);
batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(copy)); batchCmd.addSubCommand(new svgedit.history.InsertElementCommand(copy));
restoreRefElems(copy); restoreRefElems(copy);
@ -6433,7 +6447,7 @@ this.pasteElements = function(type, x, y) {
}); });
var cmd = canvas.moveSelectedElements(dx, dy, false); var cmd = canvas.moveSelectedElements(dx, dy, false);
batchCmd.addSubCommand(cmd); if(cmd) batchCmd.addSubCommand(cmd);
} }
addCommandToHistory(batchCmd); addCommandToHistory(batchCmd);

View File

@ -1191,8 +1191,9 @@ svgedit.utilities.copyElem = function(el, getNextId) {
var ref = $(el).data('symbol'); var ref = $(el).data('symbol');
$(new_el).data('ref', ref).data('symbol', ref); $(new_el).data('ref', ref).data('symbol', ref);
} else if (new_el.tagName == 'image') { } else if (new_el.tagName == 'image') {
preventClickDefault(new_el); svgedit.utilities.preventClickDefault(new_el);
} }
return new_el; return new_el;
}; };