From 7bd0ca221267d4cce77323b76a164a09ebd217ef Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 3 Sep 2020 18:31:33 +0100 Subject: [PATCH] Handle nodes with invalid z property Closes #2170 --- .../editor-client/locales/en-US/editor.json | 3 ++ .../@node-red/editor-client/src/js/nodes.js | 45 ++++++++++++++++-- .../editor-client/src/js/ui/clipboard.js | 46 +++++++++++++------ .../@node-red/editor-client/src/js/ui/view.js | 21 +++++++-- 4 files changed, 96 insertions(+), 19 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index f12d56f00..83ca0c151 100755 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -213,6 +213,9 @@ "groupCopied_plural": "__count__ groups copied", "groupStyleCopied": "Group style copied", "invalidFlow": "Invalid flow: __message__", + "recoveredNodes": "Recovered Nodes", + "recoveredNodesInfo": "The nodes on this flow were missing a valid flow id when they were imported. They have been added to this flow so you can either restore or delete them.", + "recoveredNodesNotification": "

Imported nodes without a valid flow id

They have been added to a new flow called '__flowName__'.

", "export": { "selected":"selected nodes", "current":"current flow", diff --git a/packages/node_modules/@node-red/editor-client/src/js/nodes.js b/packages/node_modules/@node-red/editor-client/src/js/nodes.js index 84499ffe6..5a8473dd7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/nodes.js +++ b/packages/node_modules/@node-red/editor-client/src/js/nodes.js @@ -897,12 +897,23 @@ RED.nodes = (function() { } return true; } - - function importNodes(newNodesObj,createNewIds,createMissingWorkspace) { + /** + * Options: + * - generateIds - whether to replace all node ids + * - addFlow - whether to import nodes to a new tab + */ + function importNodes(newNodesObj,options) { // createNewIds,createMissingWorkspace) { + options = options || { + generateIds: false, + addFlow: false + } + var createNewIds = options.generateIds; + var createMissingWorkspace = options.addFlow; var i; var n; var newNodes; var nodeZmap = {}; + var recoveryWorkspace; if (typeof newNodesObj === "string") { if (newNodesObj === "") { return; @@ -956,6 +967,18 @@ RED.nodes = (function() { if (n.z) { nodeZmap[n.z] = nodeZmap[n.z] || []; nodeZmap[n.z].push(n); + } else if (n.z === 0) { + // Hit the rare issue where node z values get set to 0. + // Repair the flow - but we really need to track that down. + if (!recoveryWorkspace) { + recoveryWorkspace = RED.workspaces.add(null,true); + recoveryWorkspace.label = RED._("clipboard.recoveredNodes"); + recoveryWorkspace.info = RED._("clipboard.recoveredNodesInfo"); + + nodeZmap[recoveryWorkspace.id] = []; + } + n.z = recoveryWorkspace.id; + nodeZmap[recoveryWorkspace.id].push(n); } } @@ -1005,6 +1028,10 @@ RED.nodes = (function() { var missingWorkspace = null; var d; + if (recoveryWorkspace) { + new_workspaces.push(recoveryWorkspace); + } + // Find all tabs and subflow templates for (i=0;i a.selected").attr('id') === 'red-ui-clipboard-dialog-import-opt-new'); if (activeTab === "red-ui-clipboard-dialog-import-tab-clipboard") { - RED.view.importNodes($("#red-ui-clipboard-dialog-import-text").val(),addNewFlow); + importNodes($("#red-ui-clipboard-dialog-import-text").val(),addNewFlow); } else { var selectedPath; if (activeTab === "red-ui-clipboard-dialog-import-tab-library") { @@ -151,7 +151,7 @@ RED.clipboard = (function() { } if (selectedPath.path) { $.get('library/'+selectedPath.library+'/'+selectedPath.type+'/'+selectedPath.path, function(data) { - RED.view.importNodes(data,addNewFlow); + importNodes(data,addNewFlow); }); } } @@ -359,7 +359,7 @@ RED.clipboard = (function() { } } - function importNodes(mode) { + function showImportNodes(mode) { if (disabled) { return; } @@ -494,7 +494,7 @@ RED.clipboard = (function() { }); } - function exportNodes(mode) { + function showExportNodes(mode) { if (disabled) { return; } @@ -746,19 +746,39 @@ RED.clipboard = (function() { } return result; } + + + function importNodes(nodesStr,addFlow) { + var newNodes; + try { + nodesStr = nodesStr.trim(); + if (nodesStr.length === 0) { + return; + } + newNodes = JSON.parse(nodesStr); + } catch(err) { + var e = new Error(RED._("clipboard.invalidFlow",{message:err.message})); + e.code = "NODE_RED"; + throw e; + } + + + RED.view.importNodes(newNodes,{addFlow: addFlow}); + } + return { init: function() { setupDialogs(); $('').appendTo("#red-ui-editor"); - RED.actions.add("core:show-export-dialog",exportNodes); - RED.actions.add("core:show-import-dialog",importNodes); + RED.actions.add("core:show-export-dialog",showExportNodes); + RED.actions.add("core:show-import-dialog",showImportNodes); - RED.actions.add("core:show-library-export-dialog",function() { exportNodes('library') }); - RED.actions.add("core:show-library-import-dialog",function() { importNodes('library') }); + RED.actions.add("core:show-library-export-dialog",function() { showExportNodes('library') }); + RED.actions.add("core:show-library-import-dialog",function() { showImportNodes('library') }); - RED.actions.add("core:show-examples-import-dialog",function() { importNodes('examples') }); + RED.actions.add("core:show-examples-import-dialog",function() { showImportNodes('examples') }); RED.events.on("editor:open",function() { disabled = true; }); RED.events.on("editor:close",function() { disabled = false; }); @@ -792,7 +812,7 @@ RED.clipboard = (function() { if ($.inArray("text/plain",event.originalEvent.dataTransfer.types) != -1) { var data = event.originalEvent.dataTransfer.getData("text/plain"); data = data.substring(data.indexOf('['),data.lastIndexOf(']')+1); - RED.view.importNodes(data); + importNodes(data); } else if ($.inArray("Files",event.originalEvent.dataTransfer.types) != -1) { var files = event.originalEvent.dataTransfer.files; if (files.length === 1) { @@ -800,7 +820,7 @@ RED.clipboard = (function() { var reader = new FileReader(); reader.onload = (function(theFile) { return function(e) { - RED.view.importNodes(e.target.result); + importNodes(e.target.result); }; })(file); reader.readAsText(file); @@ -811,8 +831,8 @@ RED.clipboard = (function() { }); }, - import: importNodes, - export: exportNodes, + import: showImportNodes, + export: showExportNodes, copyText: copyText } })(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 8695c3729..8db5173dc 100755 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -3479,7 +3479,7 @@ RED.view = (function() { options.push({name:"delete",disabled:(movingSet.length()===0 && selected_link === null),onselect:function() {deleteSelection();}}); options.push({name:"cut",disabled:(movingSet.length()===0),onselect:function() {copySelection();deleteSelection();}}); options.push({name:"copy",disabled:(movingSet.length()===0),onselect:function() {copySelection();}}); - options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,false,true);}}); + options.push({name:"paste",disabled:(clipboard.length===0),onselect:function() {importNodes(clipboard,{touchImport:true});}}); options.push({name:"edit",disabled:(movingSet.length() != 1),onselect:function() { RED.editor.edit(mdn);}}); options.push({name:"select",onselect:function() {selectAll();}}); options.push({name:"undo",disabled:(RED.history.depth() === 0),onselect:function() {RED.history.pop();}}); @@ -4630,11 +4630,26 @@ RED.view = (function() { /** * Imports a new collection of nodes from a JSON String. + * * - all get new IDs assigned * - all "selected" * - attached to mouse for placing - "IMPORT_DRAGGING" + * @param {String/Array} newNodesStr nodes to import + * @param {Object} options options object + * + * Options: + * - addFlow - whether to import nodes to a new tab + * - touchImport - whether this is a touch import. If not, imported nodes are + * attachedto mouse for placing - "IMPORT_DRAGGING" state */ - function importNodes(newNodesStr,addNewFlow,touchImport) { + function importNodes(newNodesStr,options) { + options = options || { + addFlow: false, + touchImport: false + } + var addNewFlow = options.addFlow + var touchImport = options.touchImport; + if (mouse_mode === RED.state.SELECTING_NODE) { return; } @@ -4643,7 +4658,7 @@ RED.view = (function() { if (activeSubflow) { activeSubflowChanged = activeSubflow.changed; } - var result = RED.nodes.import(newNodesStr,true,addNewFlow); + var result = RED.nodes.import(newNodesStr,{generateIds:true, addFlow: addNewFlow}); if (result) { var new_nodes = result[0]; var new_links = result[1];