Merge pull request #4692 from node-red/improve-conflict-handling

Improve background-deploy notification handling
This commit is contained in:
Nick O'Leary 2024-05-14 16:04:58 +01:00 committed by GitHub
commit a977b87cb3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 264 additions and 114 deletions

View File

@ -372,6 +372,7 @@
"deleted": "deleted", "deleted": "deleted",
"flowDeleted": "flow deleted", "flowDeleted": "flow deleted",
"flowAdded": "flow added", "flowAdded": "flow added",
"moved": "moved",
"movedTo": "moved to __id__", "movedTo": "moved to __id__",
"movedFrom": "moved from __id__" "movedFrom": "moved from __id__"
}, },

View File

@ -29,7 +29,14 @@ RED.history = (function() {
} }
return RED.nodes.junction(id); 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) { function undoEvent(ev) {
var i; var i;
var len; var len;
@ -59,18 +66,46 @@ RED.history = (function() {
t: 'replace', t: 'replace',
config: RED.nodes.createCompleteNodeSet(), config: RED.nodes.createCompleteNodeSet(),
changed: {}, 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(); RED.nodes.clear();
var imported = RED.nodes.import(ev.config); 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) { imported.nodes.forEach(function(n) {
if (ev.changed[n.id]) { if (ev.changed[n.id]) {
ensureUnlocked(n.z, flowsToLock)
n.changed = true; 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.nodes.version(ev.rev);
RED.view.redraw(true);
RED.palette.refresh();
RED.workspaces.refresh();
RED.workspaces.show(selectedTab, true);
RED.sidebar.config.refresh();
} else { } else {
var importMap = {}; var importMap = {};
ev.config.forEach(function(n) { ev.config.forEach(function(n) {

View File

@ -34,6 +34,8 @@ RED.deploy = (function() {
var currentDiff = null; var currentDiff = null;
var activeBackgroundDeployNotification;
function changeDeploymentType(type) { function changeDeploymentType(type) {
deploymentType = type; deploymentType = type;
$("#red-ui-header-button-deploy-icon").attr("src",deploymentTypes[type].img); $("#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) { RED.comms.subscribe("notification/runtime-deploy",function(topic,msg) {
if (!activeNotifyMessage) {
var currentRev = RED.nodes.version(); var currentRev = RED.nodes.version();
if (currentRev === null || deployInflight || currentRev === msg.revision) { if (currentRev === null || deployInflight || currentRev === msg.revision) {
return; return;
} }
var message = $('<p>').text(RED._('deploy.confirm.backgroundUpdate')); if (activeBackgroundDeployNotification?.hidden && !activeBackgroundDeployNotification?.closed) {
activeNotifyMessage = RED.notify(message,{ activeBackgroundDeployNotification.showNotification()
modal: true, return
fixed: true,
buttons: [
{
text: RED._('deploy.confirm.button.ignore'),
click: function() {
activeNotifyMessage.close();
activeNotifyMessage = null;
} }
}, const message = $('<p>').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'), text: RED._('deploy.confirm.button.review'),
class: "primary", class: "primary",
click: function() { click: function() {
activeNotifyMessage.close(); activeBackgroundDeployNotification.hideNotification();
var nns = RED.nodes.createCompleteNodeSet(); var nns = RED.nodes.createCompleteNodeSet();
resolveConflict(nns,false); resolveConflict(nns,false);
activeNotifyMessage = null;
} }
} }
] ]
}); }
if (!activeBackgroundDeployNotification || activeBackgroundDeployNotification.closed) {
activeBackgroundDeployNotification = RED.notify(message, options)
} else {
activeBackgroundDeployNotification.update(message, options)
} }
}); });
} }
@ -220,7 +223,11 @@ RED.deploy = (function() {
class: "primary disabled", class: "primary disabled",
click: function() { click: function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) { if (!$("#red-ui-deploy-dialog-confirm-deploy-review").hasClass('disabled')) {
RED.diff.showRemoteDiff(); RED.diff.showRemoteDiff(null, {
onmerge: function () {
activeBackgroundDeployNotification.close()
}
});
conflictNotification.close(); conflictNotification.close();
} }
} }
@ -233,6 +240,7 @@ RED.deploy = (function() {
if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) { if (!$("#red-ui-deploy-dialog-confirm-deploy-merge").hasClass('disabled')) {
RED.diff.mergeDiff(currentDiff); RED.diff.mergeDiff(currentDiff);
conflictNotification.close(); conflictNotification.close();
activeBackgroundDeployNotification.close()
} }
} }
} }
@ -245,6 +253,7 @@ RED.deploy = (function() {
click: function() { click: function() {
save(true,activeDeploy); save(true,activeDeploy);
conflictNotification.close(); conflictNotification.close();
activeBackgroundDeployNotification.close()
} }
}) })
} }
@ -255,11 +264,8 @@ RED.deploy = (function() {
buttons: buttons buttons: buttons
}); });
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) { RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(1000 - (Date.now()-now), 0);
currentDiff = diff; currentDiff = diff;
setTimeout(function() {
conflictCheck.hide(); conflictCheck.hide();
var d = Object.keys(diff.conflicts); var d = Object.keys(diff.conflicts);
if (d.length === 0) { if (d.length === 0) {
@ -269,7 +275,6 @@ RED.deploy = (function() {
conflictManualMerge.show(); conflictManualMerge.show();
} }
$("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled') $("#red-ui-deploy-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
}) })
} }
function cropList(list) { function cropList(list) {

View File

@ -1,5 +1,4 @@
RED.diff = (function() { RED.diff = (function() {
var currentDiff = {}; var currentDiff = {};
var diffVisible = false; var diffVisible = false;
var diffList; var diffList;
@ -62,12 +61,14 @@ RED.diff = (function() {
addedCount:0, addedCount:0,
deletedCount:0, deletedCount:0,
changedCount:0, changedCount:0,
movedCount:0,
unchangedCount: 0 unchangedCount: 0
}, },
remote: { remote: {
addedCount:0, addedCount:0,
deletedCount:0, deletedCount:0,
changedCount:0, changedCount:0,
movedCount:0,
unchangedCount: 0 unchangedCount: 0
}, },
conflicts: 0 conflicts: 0
@ -138,7 +139,7 @@ RED.diff = (function() {
$(this).parent().toggleClass('collapsed'); $(this).parent().toggleClass('collapsed');
}); });
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div); createNodePropertiesTable(def,tab,localTabNode,remoteTabNode).appendTo(div);
selectState = ""; selectState = "";
if (conflicts[tab.id]) { if (conflicts[tab.id]) {
flowStats.conflicts++; flowStats.conflicts++;
@ -208,19 +209,26 @@ RED.diff = (function() {
var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell); var localStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(localCell);
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats); $('<span class="red-ui-diff-status"></span>').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) {
$('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats); $('<span class="red-ui-diff-status"> [ </span>').appendTo(localStats);
if (flowStats.conflicts > 0) { if (flowStats.conflicts > 0) {
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats); $('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
} }
if (flowStats.local.addedCount > 0) { if (flowStats.local.addedCount > 0) {
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats); const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.added'))
} }
if (flowStats.local.changedCount > 0) { if (flowStats.local.changedCount > 0) {
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats); const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.changed'))
}
if (flowStats.local.movedCount > 0) {
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.local.movedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.moved'))
} }
if (flowStats.local.deletedCount > 0) { if (flowStats.local.deletedCount > 0) {
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats); const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
} }
$('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats); $('<span class="red-ui-diff-status"> ] </span>').appendTo(localStats);
} }
@ -246,19 +254,26 @@ RED.diff = (function() {
} }
var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell); var remoteStats = $('<span>',{class:"red-ui-diff-list-flow-stats"}).appendTo(remoteCell);
$('<span class="red-ui-diff-status"></span>').text(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats); $('<span class="red-ui-diff-status"></span>').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) {
$('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats); $('<span class="red-ui-diff-status"> [ </span>').appendTo(remoteStats);
if (flowStats.conflicts > 0) { if (flowStats.conflicts > 0) {
$('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats); $('<span class="red-ui-diff-status-conflict"><span class="red-ui-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
} }
if (flowStats.remote.addedCount > 0) { if (flowStats.remote.addedCount > 0) {
$('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats); const cell = $('<span class="red-ui-diff-status-added"><span class="red-ui-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.added'))
} }
if (flowStats.remote.changedCount > 0) { if (flowStats.remote.changedCount > 0) {
$('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats); const cell = $('<span class="red-ui-diff-status-changed"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.changed'))
}
if (flowStats.remote.movedCount > 0) {
const cell = $('<span class="red-ui-diff-status-moved"><span class="red-ui-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.movedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.moved'))
} }
if (flowStats.remote.deletedCount > 0) { if (flowStats.remote.deletedCount > 0) {
$('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats); const cell = $('<span class="red-ui-diff-status-deleted"><span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
RED.popover.tooltip(cell, RED._('diff.type.deleted'))
} }
$('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats); $('<span class="red-ui-diff-status"> ] </span>').appendTo(remoteStats);
} }
@ -293,7 +308,7 @@ RED.diff = (function() {
if (options.mode === "merge") { if (options.mode === "merge") {
diffPanel.addClass("red-ui-diff-panel-merge"); diffPanel.addClass("red-ui-diff-panel-merge");
} }
var diffList = createDiffTable(diffPanel, diff); var diffList = createDiffTable(diffPanel, diff, options);
var localDiff = diff.localDiff; var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff; var remoteDiff = diff.remoteDiff;
@ -516,7 +531,6 @@ RED.diff = (function() {
var hasChanges = false; // exists in original and local/remote but with changes var hasChanges = false; // exists in original and local/remote but with changes
var unChanged = true; // existing in original,local,remote unchanged var unChanged = true; // existing in original,local,remote unchanged
var localChanged = false;
if (localDiff.added[node.id]) { if (localDiff.added[node.id]) {
stats.local.addedCount++; stats.local.addedCount++;
@ -535,12 +549,20 @@ RED.diff = (function() {
unChanged = false; unChanged = false;
} }
if (localDiff.changed[node.id]) { if (localDiff.changed[node.id]) {
if (localDiff.positionChanged[node.id]) {
stats.local.movedCount++
} else {
stats.local.changedCount++; stats.local.changedCount++;
}
hasChanges = true; hasChanges = true;
unChanged = false; unChanged = false;
} }
if (remoteDiff && remoteDiff.changed[node.id]) { if (remoteDiff && remoteDiff.changed[node.id]) {
if (remoteDiff.positionChanged[node.id]) {
stats.remote.movedCount++
} else {
stats.remote.changedCount++; stats.remote.changedCount++;
}
hasChanges = true; hasChanges = true;
unChanged = false; unChanged = false;
} }
@ -605,27 +627,32 @@ RED.diff = (function() {
localNodeDiv.addClass("red-ui-diff-status-moved"); localNodeDiv.addClass("red-ui-diff-status-moved");
var localMovedMessage = ""; var localMovedMessage = "";
if (node.z === localN.z) { 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 { } 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});
} }
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv); $('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
} }
localChanged = true;
} else if (localDiff.deleted[node.z]) { } else if (localDiff.deleted[node.z]) {
localNodeDiv.addClass("red-ui-diff-empty"); localNodeDiv.addClass("red-ui-diff-empty");
localChanged = true;
} else if (localDiff.deleted[node.id]) { } else if (localDiff.deleted[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-deleted"); localNodeDiv.addClass("red-ui-diff-status-deleted");
$('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv); $('<span class="red-ui-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(localNodeDiv);
localChanged = true;
} else if (localDiff.changed[node.id]) { } else if (localDiff.changed[node.id]) {
if (localDiff.newConfig.all[node.id].z !== node.z) { if (localDiff.newConfig.all[node.id].z !== node.z) {
localNodeDiv.addClass("red-ui-diff-empty"); localNodeDiv.addClass("red-ui-diff-empty");
} else {
if (localDiff.positionChanged[node.id]) {
localNodeDiv.addClass("red-ui-diff-status-moved");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(localNodeDiv);
} else { } else {
localNodeDiv.addClass("red-ui-diff-status-changed"); localNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv); $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
localChanged = true; }
} }
} else { } else {
if (localDiff.newConfig.all[node.id].z !== node.z) { if (localDiff.newConfig.all[node.id].z !== node.z) {
@ -646,9 +673,13 @@ RED.diff = (function() {
remoteNodeDiv.addClass("red-ui-diff-status-moved"); remoteNodeDiv.addClass("red-ui-diff-status-moved");
var remoteMovedMessage = ""; var remoteMovedMessage = "";
if (node.z === remoteN.z) { 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 { } 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});
} }
$('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv); $('<span class="red-ui-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
} }
@ -660,10 +691,15 @@ RED.diff = (function() {
} else if (remoteDiff.changed[node.id]) { } else if (remoteDiff.changed[node.id]) {
if (remoteDiff.newConfig.all[node.id].z !== node.z) { if (remoteDiff.newConfig.all[node.id].z !== node.z) {
remoteNodeDiv.addClass("red-ui-diff-empty"); remoteNodeDiv.addClass("red-ui-diff-empty");
} else {
if (remoteDiff.positionChanged[node.id]) {
remoteNodeDiv.addClass("red-ui-diff-status-moved");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.moved"></span></span>').appendTo(remoteNodeDiv);
} else { } else {
remoteNodeDiv.addClass("red-ui-diff-status-changed"); remoteNodeDiv.addClass("red-ui-diff-status-changed");
$('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv); $('<span class="red-ui-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
} }
}
} else { } else {
if (remoteDiff.newConfig.all[node.id].z !== node.z) { if (remoteDiff.newConfig.all[node.id].z !== node.z) {
remoteNodeDiv.addClass("red-ui-diff-empty"); remoteNodeDiv.addClass("red-ui-diff-empty");
@ -788,7 +824,7 @@ RED.diff = (function() {
$("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row); $("<td>",{class:"red-ui-diff-list-cell-label"}).text("position").appendTo(row);
localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row); localCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-local"}).appendTo(row);
if (localNode) { if (localNode) {
localCell.addClass("red-ui-diff-status-"+(localChanged?"changed":"unchanged")); localCell.addClass("red-ui-diff-status-"+(localChanged?"moved":"unchanged"));
$('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell); $('<span class="red-ui-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell); element = $('<span class="red-ui-diff-list-element"></span>').appendTo(localCell);
var localPosition = {x:localNode.x,y:localNode.y}; var localPosition = {x:localNode.x,y:localNode.y};
@ -813,7 +849,7 @@ RED.diff = (function() {
if (remoteNode !== undefined) { if (remoteNode !== undefined) {
remoteCell = $("<td>",{class:"red-ui-diff-list-cell red-ui-diff-list-node-remote"}).appendTo(row); remoteCell = $("<td>",{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) { if (remoteNode) {
$('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell); $('<span class="red-ui-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell); element = $('<span class="red-ui-diff-list-element"></span>').appendTo(remoteCell);
@ -1099,11 +1135,11 @@ RED.diff = (function() {
// var diff = generateDiff(originalFlow,nns); // var diff = generateDiff(originalFlow,nns);
// showDiff(diff); // showDiff(diff);
// } // }
function showRemoteDiff(diff) { function showRemoteDiff(diff, options = {}) {
if (diff === undefined) { if (!diff) {
getRemoteDiff(showRemoteDiff); getRemoteDiff((remoteDiff) => showRemoteDiff(remoteDiff, options));
} else { } else {
showDiff(diff,{mode:'merge'}); showDiff(diff,{...options, mode:'merge'});
} }
} }
function parseNodes(nodeList) { function parseNodes(nodeList) {
@ -1144,24 +1180,54 @@ RED.diff = (function() {
} }
} }
function generateDiff(currentNodes,newNodes) { function generateDiff(currentNodes,newNodes) {
var currentConfig = parseNodes(currentNodes); const currentConfig = parseNodes(currentNodes);
var newConfig = parseNodes(newNodes); const newConfig = parseNodes(newNodes);
var added = {}; const added = {};
var deleted = {}; const deleted = {};
var changed = {}; const changed = {};
var moved = {}; const positionChanged = {};
const moved = {};
Object.keys(currentConfig.all).forEach(function(id) { 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)) { if (!newConfig.all.hasOwnProperty(id)) {
deleted[id] = true; deleted[id] = true;
} else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) { return
changed[id] = true; }
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) { if (currentConfig.all[id].z !== newConfig.all[id].z) {
moved[id] = true; 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) { Object.keys(newConfig.all).forEach(function(id) {
if (!currentConfig.all.hasOwnProperty(id)) { if (!currentConfig.all.hasOwnProperty(id)) {
@ -1169,13 +1235,14 @@ RED.diff = (function() {
} }
}); });
var diff = { const diff = {
currentConfig: currentConfig, currentConfig,
newConfig: newConfig, newConfig,
added: added, added,
deleted: deleted, deleted,
changed: changed, changed,
moved: moved positionChanged,
moved
}; };
return diff; return diff;
} }
@ -1240,13 +1307,15 @@ RED.diff = (function() {
return diff; return diff;
} }
function showDiff(diff,options) { function showDiff(diff, options) {
if (diffVisible) { if (diffVisible) {
return; return;
} }
options = options || {}; options = options || {};
var mode = options.mode || 'merge'; var mode = options.mode || 'merge';
options.hidePositionChanges = true
var localDiff = diff.localDiff; var localDiff = diff.localDiff;
var remoteDiff = diff.remoteDiff; var remoteDiff = diff.remoteDiff;
var conflicts = diff.conflicts; var conflicts = diff.conflicts;
@ -1315,6 +1384,9 @@ RED.diff = (function() {
if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) { if (!$("#red-ui-diff-view-diff-merge").hasClass('disabled')) {
refreshConflictHeader(diff); refreshConflictHeader(diff);
mergeDiff(diff); mergeDiff(diff);
if (options.onmerge) {
options.onmerge()
}
RED.tray.close(); RED.tray.close();
} }
} }
@ -1345,6 +1417,7 @@ RED.diff = (function() {
var newConfig = []; var newConfig = [];
var node; var node;
var nodeChangedStates = {}; var nodeChangedStates = {};
var nodeMovedStates = {};
var localChangedStates = {}; var localChangedStates = {};
for (id in localDiff.newConfig.all) { for (id in localDiff.newConfig.all) {
if (localDiff.newConfig.all.hasOwnProperty(id)) { if (localDiff.newConfig.all.hasOwnProperty(id)) {
@ -1352,12 +1425,14 @@ RED.diff = (function() {
if (resolutions[id] === 'local') { if (resolutions[id] === 'local') {
if (node) { if (node) {
nodeChangedStates[id] = node.changed; nodeChangedStates[id] = node.changed;
nodeMovedStates[id] = node.moved;
} }
newConfig.push(localDiff.newConfig.all[id]); newConfig.push(localDiff.newConfig.all[id]);
} else if (resolutions[id] === 'remote') { } else if (resolutions[id] === 'remote') {
if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) { if (!remoteDiff.deleted[id] && remoteDiff.newConfig.all.hasOwnProperty(id)) {
if (node) { if (node) {
nodeChangedStates[id] = node.changed; nodeChangedStates[id] = node.changed;
nodeMovedStates[id] = node.moved;
} }
localChangedStates[id] = 1; localChangedStates[id] = 1;
newConfig.push(remoteDiff.newConfig.all[id]); newConfig.push(remoteDiff.newConfig.all[id]);
@ -1381,8 +1456,9 @@ RED.diff = (function() {
} }
return { return {
config: newConfig, config: newConfig,
nodeChangedStates: nodeChangedStates, nodeChangedStates,
localChangedStates: localChangedStates nodeMovedStates,
localChangedStates
} }
} }
@ -1393,6 +1469,7 @@ RED.diff = (function() {
var newConfig = appliedDiff.config; var newConfig = appliedDiff.config;
var nodeChangedStates = appliedDiff.nodeChangedStates; var nodeChangedStates = appliedDiff.nodeChangedStates;
var nodeMovedStates = appliedDiff.nodeMovedStates;
var localChangedStates = appliedDiff.localChangedStates; var localChangedStates = appliedDiff.localChangedStates;
var isDirty = RED.nodes.dirty(); var isDirty = RED.nodes.dirty();
@ -1401,33 +1478,56 @@ RED.diff = (function() {
t:"replace", t:"replace",
config: RED.nodes.createCompleteNodeSet(), config: RED.nodes.createCompleteNodeSet(),
changed: nodeChangedStates, changed: nodeChangedStates,
moved: nodeMovedStates,
complete: true,
dirty: isDirty, dirty: isDirty,
rev: RED.nodes.version() rev: RED.nodes.version()
} }
RED.history.push(historyEvent); RED.history.push(historyEvent);
var originalFlow = RED.nodes.originalFlow(); // var originalFlow = RED.nodes.originalFlow();
// originalFlow is what the editor things it loaded // // originalFlow is what the editor thinks it loaded
// - add any newly added nodes from remote diff as they are now part of the record // // - add any newly added nodes from remote diff as they are now part of the record
for (var id in diff.remoteDiff.added) { // for (var id in diff.remoteDiff.added) {
if (diff.remoteDiff.added.hasOwnProperty(id)) { // if (diff.remoteDiff.added.hasOwnProperty(id)) {
if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) { // if (diff.remoteDiff.newConfig.all.hasOwnProperty(id)) {
originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id]))); // originalFlow.push(JSON.parse(JSON.stringify(diff.remoteDiff.newConfig.all[id])));
} // }
} // }
} // }
RED.nodes.clear(); RED.nodes.clear();
var imported = RED.nodes.import(newConfig); var imported = RED.nodes.import(newConfig);
// Restore the original flow so subsequent merge resolutions can properly // // Restore the original flow so subsequent merge resolutions can properly
// identify new-vs-old // // identify new-vs-old
RED.nodes.originalFlow(originalFlow); // 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) { imported.nodes.forEach(function(n) {
if (nodeChangedStates[n.id] || localChangedStates[n.id]) { if (nodeChangedStates[n.id]) {
ensureUnlocked(n.z)
n.changed = true; 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); RED.nodes.version(diff.remoteDiff.rev);

View File

@ -221,12 +221,12 @@ RED.notifications = (function() {
if (newType) { if (newType) {
n.className = "red-ui-notification red-ui-notification-"+newType; n.className = "red-ui-notification red-ui-notification-"+newType;
} }
newTimeout = newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout
if (!fixed || newOptions.fixed === false) { if (!fixed || newOptions.fixed === false) {
newTimeout = (newOptions.hasOwnProperty('timeout')?newOptions.timeout:timeout)||5000; newTimeout = newTimeout || 5000
} }
if (newOptions.buttons) { if (newOptions.buttons) {
var buttonSet = $('<div style="margin-top: 20px;" class="ui-dialog-buttonset"></div>').appendTo(nn) var buttonSet = $('<div class="ui-dialog-buttonset"></div>').appendTo(nn)
newOptions.buttons.forEach(function(buttonDef) { newOptions.buttons.forEach(function(buttonDef) {
var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet); var b = $('<button>').text(buttonDef.text).on("click", buttonDef.click).appendTo(buttonSet);
if (buttonDef.id) { if (buttonDef.id) {
@ -272,6 +272,15 @@ RED.notifications = (function() {
}; };
})()); })());
n.timeoutid = window.setTimeout(n.close,timeout||5000); n.timeoutid = window.setTimeout(n.close,timeout||5000);
} else if (timeout) {
$(n).on("click.red-ui-notification-close", (function() {
var nn = n;
return function() {
nn.hideNotification();
window.clearTimeout(nn.timeoutid);
};
})());
n.timeoutid = window.setTimeout(n.hideNotification,timeout||5000);
} }
currentNotifications.push(n); currentNotifications.push(n);
if (options.id) { if (options.id) {