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() { $("