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 d53f84547..ab5ba95fb 100644 --- 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 @@ -372,6 +372,7 @@ "deleted": "deleted", "flowDeleted": "flow deleted", "flowAdded": "flow added", + "moved": "moved", "movedTo": "moved to __id__", "movedFrom": "moved from __id__" }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/history.js b/packages/node_modules/@node-red/editor-client/src/js/history.js index c3a966890..2fa4e4427 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/history.js +++ b/packages/node_modules/@node-red/editor-client/src/js/history.js @@ -29,7 +29,14 @@ RED.history = (function() { } return RED.nodes.junction(id); } - + function ensureUnlocked(id, flowsToLock) { + const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null); + const isLocked = flow ? flow.locked : false; + if (flow && isLocked) { + flow.locked = false; + flowsToLock.add(flow) + } + } function undoEvent(ev) { var i; var len; @@ -59,18 +66,46 @@ RED.history = (function() { t: 'replace', config: RED.nodes.createCompleteNodeSet(), changed: {}, - rev: RED.nodes.version() + moved: {}, + complete: true, + rev: RED.nodes.version(), + dirty: RED.nodes.dirty() }; + var selectedTab = RED.workspaces.active(); + inverseEv.config.forEach(n => { + const node = RED.nodes.node(n.id) + if (node) { + inverseEv.changed[n.id] = node.changed + inverseEv.moved[n.id] = node.moved + } + }) RED.nodes.clear(); var imported = RED.nodes.import(ev.config); + // Clear all change flags from the import + RED.nodes.dirty(false); + + const flowsToLock = new Set() + imported.nodes.forEach(function(n) { if (ev.changed[n.id]) { + ensureUnlocked(n.z, flowsToLock) n.changed = true; - inverseEv.changed[n.id] = true; } + if (ev.moved[n.id]) { + ensureUnlocked(n.z, flowsToLock) + n.moved = true; + } + }) + flowsToLock.forEach(flow => { + flow.locked = true }) RED.nodes.version(ev.rev); + RED.view.redraw(true); + RED.palette.refresh(); + RED.workspaces.refresh(); + RED.workspaces.show(selectedTab, true); + RED.sidebar.config.refresh(); } else { var importMap = {}; ev.config.forEach(function(n) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index 83c04d775..36f2a971c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -34,6 +34,8 @@ RED.deploy = (function() { var currentDiff = null; + var activeBackgroundDeployNotification; + function changeDeploymentType(type) { deploymentType = type; $("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img); @@ -133,37 +135,38 @@ RED.deploy = (function() { } }); - var activeNotifyMessage; RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) { - if (!activeNotifyMessage) { - var currentRev = RED.nodes.version(); - if (currentRev === null || deployInflight || currentRev === msg.revision) { - return; - } - var message = $('

').text(RED._('deploy.confirm.backgroundUpdate')); - activeNotifyMessage = RED.notify(message,{ - modal: true, - fixed: true, - buttons: [ - { - text: RED._('deploy.confirm.button.ignore'), - click: function() { - activeNotifyMessage.close(); - activeNotifyMessage = null; - } - }, - { - text: RED._('deploy.confirm.button.review'), - class: "primary", - click: function() { - activeNotifyMessage.close(); - var nns = RED.nodes.createCompleteNodeSet(); - resolveConflict(nns,false); - activeNotifyMessage = null; - } + var currentRev = RED.nodes.version(); + if (currentRev === null || deployInflight || currentRev === msg.revision) { + return; + } + if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) { + activeBackgroundDeployNotification.showNotification() + return + } + const message = $('

').text(RED._('deploy.confirm.backgroundUpdate')); + const options = { + id: 'background-update', + type: 'compact', + modal: false, + fixed: true, + timeout: 10000, + buttons: [ + { + text: RED._('deploy.confirm.button.review'), + class: "primary", + click: function() { + activeBackgroundDeployNotification.hideNotification(); + var nns = RED.nodes.createCompleteNodeSet(); + resolveConflict(nns,false); } - ] - }); + } + ] + } + if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) { + activeBackgroundDeployNotification = RED.notify(message, options) + } else { + activeBackgroundDeployNotification.update(message, options) } }); } @@ -220,7 +223,11 @@ RED.deploy = (function() { class: "primary disabled", click: function() { if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) { - RED.diff.showRemoteDiff(); + RED.diff.showRemoteDiff(null, { + onmerge: function () { + activeBackgroundDeployNotification.close() + } + }); conflictNotification.close(); } } @@ -233,6 +240,7 @@ RED.deploy = (function() { if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) { RED.diff.mergeDiff(currentDiff); conflictNotification.close(); + activeBackgroundDeployNotification.close() } } } @@ -245,6 +253,7 @@ RED.deploy = (function() { click: function() { save(true,activeDeploy); conflictNotification.close(); + activeBackgroundDeployNotification.close() } }) } @@ -255,21 +264,17 @@ RED.deploy = (function() { buttons: buttons }); - var now = Date.now(); RED.diff.getRemoteDiff(function(diff) { - var ellapsed = Math.max(1000 - (Date.now()-now), 0); currentDiff = diff; - setTimeout(function() { - conflictCheck.hide(); - var d = Object.keys(diff.conflicts); - if (d.length === 0) { - conflictAutoMerge.show(); - $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled') - } else { - conflictManualMerge.show(); - } - $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') - },ellapsed); + conflictCheck.hide(); + var d = Object.keys(diff.conflicts); + if (d.length === 0) { + conflictAutoMerge.show(); + $("#red-ui-deploy-dialog-confirm-deploy-merge").removeClass('disabled') + } else { + conflictManualMerge.show(); + } + $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') }) } function cropList(list) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js index 3f73e29aa..ebdf683e3 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js @@ -1,5 +1,4 @@ RED.diff = (function() { - var currentDiff = {}; var diffVisible = false; var diffList; @@ -62,12 +61,14 @@ RED.diff = (function() { addedCount:0, deletedCount:0, changedCount:0, + movedCount:0, unchangedCount: 0 }, remote: { addedCount:0, deletedCount:0, changedCount:0, + movedCount:0, unchangedCount: 0 }, conflicts: 0 @@ -138,7 +139,7 @@ RED.diff = (function() { $(this).parent().toggleClass('collapsed'); }); - createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div); + createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div); selectState = ""; if (conflicts[tab.id]) { flowStats.conflicts++; @@ -208,19 +209,26 @@ RED.diff = (function() { var localStats = $('',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell); $('').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats); - if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) { + if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.movedCount + flowStats.local.deletedCount > 0) { $(' [ ').appendTo(localStats); if (flowStats.conflicts > 0) { $(' '+flowStats.conflicts+'').appendTo(localStats); } if (flowStats.local.addedCount > 0) { - $(' '+flowStats.local.addedCount+'').appendTo(localStats); + const cell = $(' '+flowStats.local.addedCount+'').appendTo(localStats); + RED.popover.tooltip(cell, RED._('diff.type.added')) } if (flowStats.local.changedCount > 0) { - $(' '+flowStats.local.changedCount+'').appendTo(localStats); + const cell = $(' '+flowStats.local.changedCount+'').appendTo(localStats); + RED.popover.tooltip(cell, RED._('diff.type.changed')) + } + if (flowStats.local.movedCount > 0) { + const cell = $(' '+flowStats.local.movedCount+'').appendTo(localStats); + RED.popover.tooltip(cell, RED._('diff.type.moved')) } if (flowStats.local.deletedCount > 0) { - $(' '+flowStats.local.deletedCount+'').appendTo(localStats); + const cell = $(' '+flowStats.local.deletedCount+'').appendTo(localStats); + RED.popover.tooltip(cell, RED._('diff.type.deleted')) } $(' ] ').appendTo(localStats); } @@ -246,19 +254,26 @@ RED.diff = (function() { } var remoteStats = $('',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell); $('').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats); - if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) { + if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.movedCount + flowStats.remote.deletedCount > 0) { $(' [ ').appendTo(remoteStats); if (flowStats.conflicts > 0) { $(' '+flowStats.conflicts+'').appendTo(remoteStats); } if (flowStats.remote.addedCount > 0) { - $(' '+flowStats.remote.addedCount+'').appendTo(remoteStats); + const cell = $(' '+flowStats.remote.addedCount+'').appendTo(remoteStats); + RED.popover.tooltip(cell, RED._('diff.type.added')) } if (flowStats.remote.changedCount > 0) { - $(' '+flowStats.remote.changedCount+'').appendTo(remoteStats); + const cell = $(' '+flowStats.remote.changedCount+'').appendTo(remoteStats); + RED.popover.tooltip(cell, RED._('diff.type.changed')) + } + if (flowStats.remote.movedCount > 0) { + const cell = $(' '+flowStats.remote.movedCount+'').appendTo(remoteStats); + RED.popover.tooltip(cell, RED._('diff.type.moved')) } if (flowStats.remote.deletedCount > 0) { - $(' '+flowStats.remote.deletedCount+'').appendTo(remoteStats); + const cell = $(' '+flowStats.remote.deletedCount+'').appendTo(remoteStats); + RED.popover.tooltip(cell, RED._('diff.type.deleted')) } $(' ] ').appendTo(remoteStats); } @@ -293,7 +308,7 @@ RED.diff = (function() { if (options.mode === "merge") { diffPanel.addClass("red-ui-diff-panel-merge"); } - var diffList = createDiffTable(diffPanel, diff); + var diffList = createDiffTable(diffPanel, diff, options); var localDiff = diff.localDiff; var remoteDiff = diff.remoteDiff; @@ -516,7 +531,6 @@ RED.diff = (function() { var hasChanges = false; // exists in original and local/remote but with changes var unChanged = true; // existing in original,local,remote unchanged - var localChanged = false; if (localDiff.added[node.id]) { stats.local.addedCount++; @@ -535,12 +549,20 @@ RED.diff = (function() { unChanged = false; } if (localDiff.changed[node.id]) { - stats.local.changedCount++; + if (localDiff.positionChanged[node.id]) { + stats.local.movedCount++ + } else { + stats.local.changedCount++; + } hasChanges = true; unChanged = false; } if (remoteDiff && remoteDiff.changed[node.id]) { - stats.remote.changedCount++; + if (remoteDiff.positionChanged[node.id]) { + stats.remote.movedCount++ + } else { + stats.remote.changedCount++; + } hasChanges = true; unChanged = false; } @@ -605,27 +627,32 @@ RED.diff = (function() { localNodeDiv.addClass("red-ui-diff-status-moved"); var localMovedMessage = ""; if (node.z === localN.z) { - localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')}); + const movedFromNodeTab = localDiff.currentConfig.all[localDiff.currentConfig.all[node.id].z] + const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'` + localMovedMessage = RED._("diff.type.movedFrom",{id: movedFromLabel}); } else { - localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')}); + const movedToNodeTab = localDiff.newConfig.all[localN.z] + const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'` + localMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel}); } $(' '+localMovedMessage+'').appendTo(localNodeDiv); } - localChanged = true; } else if (localDiff.deleted[node.z]) { localNodeDiv.addClass("red-ui-diff-empty"); - localChanged = true; } else if (localDiff.deleted[node.id]) { localNodeDiv.addClass("red-ui-diff-status-deleted"); $(' ').appendTo(localNodeDiv); - localChanged = true; } else if (localDiff.changed[node.id]) { if (localDiff.newConfig.all[node.id].z !== node.z) { localNodeDiv.addClass("red-ui-diff-empty"); } else { - localNodeDiv.addClass("red-ui-diff-status-changed"); - $(' ').appendTo(localNodeDiv); - localChanged = true; + if (localDiff.positionChanged[node.id]) { + localNodeDiv.addClass("red-ui-diff-status-moved"); + $(' ').appendTo(localNodeDiv); + } else { + localNodeDiv.addClass("red-ui-diff-status-changed"); + $(' ').appendTo(localNodeDiv); + } } } else { if (localDiff.newConfig.all[node.id].z !== node.z) { @@ -646,9 +673,13 @@ RED.diff = (function() { remoteNodeDiv.addClass("red-ui-diff-status-moved"); var remoteMovedMessage = ""; if (node.z === remoteN.z) { - remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')}); + const movedFromNodeTab = remoteDiff.currentConfig.all[remoteDiff.currentConfig.all[node.id].z] + const movedFromLabel = `'${movedFromNodeTab ? (movedFromNodeTab.label || movedFromNodeTab.id) : 'global'}'` + remoteMovedMessage = RED._("diff.type.movedFrom",{id:movedFromLabel}); } else { - remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')}); + const movedToNodeTab = remoteDiff.newConfig.all[remoteN.z] + const movedToLabel = `'${movedToNodeTab ? (movedToNodeTab.label || movedToNodeTab.id) : 'global'}'` + remoteMovedMessage = RED._("diff.type.movedTo",{id:movedToLabel}); } $(' '+remoteMovedMessage+'').appendTo(remoteNodeDiv); } @@ -661,8 +692,13 @@ RED.diff = (function() { if (remoteDiff.newConfig.all[node.id].z !== node.z) { remoteNodeDiv.addClass("red-ui-diff-empty"); } else { - remoteNodeDiv.addClass("red-ui-diff-status-changed"); - $(' ').appendTo(remoteNodeDiv); + if (remoteDiff.positionChanged[node.id]) { + remoteNodeDiv.addClass("red-ui-diff-status-moved"); + $(' ').appendTo(remoteNodeDiv); + } else { + remoteNodeDiv.addClass("red-ui-diff-status-changed"); + $(' ').appendTo(remoteNodeDiv); + } } } else { if (remoteDiff.newConfig.all[node.id].z !== node.z) { @@ -788,7 +824,7 @@ RED.diff = (function() { $("",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row); localCell = $("",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row); if (localNode) { - localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged")); + localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged")); $(''+(localChanged?'':'')+'').appendTo(localCell); element = $('').appendTo(localCell); var localPosition = {x:localNode.x,y:localNode.y}; @@ -813,7 +849,7 @@ RED.diff = (function() { if (remoteNode !== undefined) { remoteCell = $("",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row); - remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"changed":"unchanged")); + remoteCell.addClass("red-ui-diff-status-"+(remoteChanged?"moved":"unchanged")); if (remoteNode) { $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); element = $('').appendTo(remoteCell); @@ -1099,11 +1135,11 @@ RED.diff = (function() { // var diff = generateDiff(originalFlow,nns); // showDiff(diff); // } - function showRemoteDiff(diff) { - if (diff === undefined) { - getRemoteDiff(showRemoteDiff); + function showRemoteDiff(diff, options = {}) { + if (!diff) { + getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options)); } else { - showDiff(diff,{mode:'merge'}); + showDiff(diff,{...options, mode:'merge'}); } } function parseNodes(nodeList) { @@ -1144,23 +1180,53 @@ RED.diff = (function() { } } function generateDiff(currentNodes,newNodes) { - var currentConfig = parseNodes(currentNodes); - var newConfig = parseNodes(newNodes); - var added = {}; - var deleted = {}; - var changed = {}; - var moved = {}; + const currentConfig = parseNodes(currentNodes); + const newConfig = parseNodes(newNodes); + const added = {}; + const deleted = {}; + const changed = {}; + const positionChanged = {}; + const moved = {}; Object.keys(currentConfig.all).forEach(function(id) { - var node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id); + const node = RED.nodes.workspace(id)||RED.nodes.subflow(id)||RED.nodes.node(id); if (!newConfig.all.hasOwnProperty(id)) { deleted[id] = true; - } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) { + return + } + const currentConfigJSON = JSON.stringify(currentConfig.all[id]) + const newConfigJSON = JSON.stringify(newConfig.all[id]) + + if (currentConfigJSON !== newConfigJSON) { changed[id] = true; - if (currentConfig.all[id].z !== newConfig.all[id].z) { moved[id] = true; + } else if ( + currentConfig.all[id].x !== newConfig.all[id].x || + currentConfig.all[id].y !== newConfig.all[id].y || + currentConfig.all[id].w !== newConfig.all[id].w || + currentConfig.all[id].h !== newConfig.all[id].h + ) { + // This node's position on its parent has changed. We want to + // check if this is the *only* change for this given node + const currentNodeClone = JSON.parse(currentConfigJSON) + const newNodeClone = JSON.parse(newConfigJSON) + + delete currentNodeClone.x + delete currentNodeClone.y + delete currentNodeClone.w + delete currentNodeClone.h + delete newNodeClone.x + delete newNodeClone.y + delete newNodeClone.w + delete newNodeClone.h + + if (JSON.stringify(currentNodeClone) === JSON.stringify(newNodeClone)) { + // Only the position has changed - everything else is the same + positionChanged[id] = true + } } + } }); Object.keys(newConfig.all).forEach(function(id) { @@ -1169,13 +1235,14 @@ RED.diff = (function() { } }); - var diff = { - currentConfig: currentConfig, - newConfig: newConfig, - added: added, - deleted: deleted, - changed: changed, - moved: moved + const diff = { + currentConfig, + newConfig, + added, + deleted, + changed, + positionChanged, + moved }; return diff; } @@ -1240,12 +1307,14 @@ RED.diff = (function() { return diff; } - function showDiff(diff,options) { + function showDiff(diff, options) { if (diffVisible) { return; } options = options || {}; var mode = options.mode || 'merge'; + + options.hidePositionChanges = true var localDiff = diff.localDiff; var remoteDiff = diff.remoteDiff; @@ -1315,6 +1384,9 @@ RED.diff = (function() { if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) { refreshConflictHeader(diff); mergeDiff(diff); + if (options.onmerge) { + options.onmerge() + } RED.tray.close(); } } @@ -1345,6 +1417,7 @@ RED.diff = (function() { var newConfig = []; var node; var nodeChangedStates = {}; + var nodeMovedStates = {}; var localChangedStates = {}; for (id in localDiff.newConfig.all) { if (localDiff.newConfig.all.hasOwnProperty(id)) { @@ -1352,12 +1425,14 @@ RED.diff = (function() { if (resolutions[id] === 'local') { if (node) { nodeChangedStates[id] = node.changed; + nodeMovedStates[id] = node.moved; } newConfig.push(localDiff.newConfig.all[id]); } else if (resolutions[id] === 'remote') { if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) { if (node) { nodeChangedStates[id] = node.changed; + nodeMovedStates[id] = node.moved; } localChangedStates[id] = 1; newConfig.push(remoteDiff.newConfig.all[id]); @@ -1381,8 +1456,9 @@ RED.diff = (function() { } return { config: newConfig, - nodeChangedStates: nodeChangedStates, - localChangedStates: localChangedStates + nodeChangedStates, + nodeMovedStates, + localChangedStates } } @@ -1393,6 +1469,7 @@ RED.diff = (function() { var newConfig = appliedDiff.config; var nodeChangedStates = appliedDiff.nodeChangedStates; + var nodeMovedStates = appliedDiff.nodeMovedStates; var localChangedStates = appliedDiff.localChangedStates; var isDirty = RED.nodes.dirty(); @@ -1401,33 +1478,56 @@ RED.diff = (function() { t:"replace", config: RED.nodes.createCompleteNodeSet(), changed: nodeChangedStates, + moved: nodeMovedStates, + complete: true, dirty: isDirty, rev: RED.nodes.version() } RED.history.push(historyEvent); - var originalFlow = RED.nodes.originalFlow(); - // originalFlow is what the editor things it loaded - // - add any newly added nodes from remote diff as they are now part of the record - for (var id in diff.remoteDiff.added) { - if (diff.remoteDiff.added.hasOwnProperty(id)) { - if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { - originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); - } - } - } + // var originalFlow = RED.nodes.originalFlow(); + // // originalFlow is what the editor thinks it loaded + // // - add any newly added nodes from remote diff as they are now part of the record + // for (var id in diff.remoteDiff.added) { + // if (diff.remoteDiff.added.hasOwnProperty(id)) { + // if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { + // originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); + // } + // } + // } RED.nodes.clear(); var imported = RED.nodes.import(newConfig); - // Restore the original flow so subsequent merge resolutions can properly - // identify new-vs-old - RED.nodes.originalFlow(originalFlow); + // // Restore the original flow so subsequent merge resolutions can properly + // // identify new-vs-old + // RED.nodes.originalFlow(originalFlow); + + // Clear all change flags from the import + RED.nodes.dirty(false); + + const flowsToLock = new Set() + function ensureUnlocked(id) { + const flow = id && (RED.nodes.workspace(id) || RED.nodes.subflow(id) || null); + const isLocked = flow ? flow.locked : false; + if (flow && isLocked) { + flow.locked = false; + flowsToLock.add(flow) + } + } imported.nodes.forEach(function(n) { - if (nodeChangedStates[n.id] || localChangedStates[n.id]) { + if (nodeChangedStates[n.id]) { + ensureUnlocked(n.z) n.changed = true; } + if (nodeMovedStates[n.id]) { + ensureUnlocked(n.z) + n.moved = true; + } + }) + flowsToLock.forEach(flow => { + flow.locked = true }) RED.nodes.version(diff.remoteDiff.rev); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js b/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js index 30dcc4bd5..d68d03d3f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/notifications.js @@ -221,12 +221,12 @@ RED.notifications = (function() { if (newType) { n.className = "red-ui-notification red-ui-notification-"+newType; } - + newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout if (!fixed || newOptions.fixed === false) { - newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000; + newTimeout = newTimeout || 5000 } if (newOptions.buttons) { - var buttonSet = $('

').appendTo(nn) + var buttonSet = $('
').appendTo(nn) newOptions.buttons.forEach(function(buttonDef) { var b = $('