mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
2136 lines
99 KiB
JavaScript
2136 lines
99 KiB
JavaScript
RED.diff = (function() {
|
|
|
|
var currentDiff = {};
|
|
var diffVisible = false;
|
|
var diffList;
|
|
|
|
function init() {
|
|
|
|
// RED.actions.add("core:show-current-diff",showLocalDiff);
|
|
RED.actions.add("core:show-remote-diff",showRemoteDiff);
|
|
// RED.keyboard.add("*","ctrl-shift-l","core:show-current-diff");
|
|
RED.keyboard.add("*","ctrl-shift-r","core:show-remote-diff");
|
|
|
|
|
|
RED.actions.add("core:show-test-flow-diff-1",function(){showTestFlowDiff(1)});
|
|
RED.keyboard.add("*","ctrl-shift-f 1","core:show-test-flow-diff-1");
|
|
|
|
RED.actions.add("core:show-test-flow-diff-2",function(){showTestFlowDiff(2)});
|
|
RED.keyboard.add("*","ctrl-shift-f 2","core:show-test-flow-diff-2");
|
|
RED.actions.add("core:show-test-flow-diff-3",function(){showTestFlowDiff(3)});
|
|
RED.keyboard.add("*","ctrl-shift-f 3","core:show-test-flow-diff-3");
|
|
|
|
}
|
|
function createDiffTable(container) {
|
|
var diffList = $('<ol class="node-dialog-view-diff-diff"></ol>').appendTo(container);
|
|
diffList.editableList({
|
|
addButton: false,
|
|
scrollOnAdd: false,
|
|
addItem: function(container,i,object) {
|
|
var localDiff = object.diff;
|
|
var remoteDiff = object.remoteDiff;
|
|
var tab = object.tab.n;
|
|
var def = object.def;
|
|
var conflicts = currentDiff.conflicts;
|
|
|
|
var tabDiv = $('<div>',{class:"node-diff-tab"}).appendTo(container);
|
|
tabDiv.addClass('collapsed');
|
|
var titleRow = $('<div>',{class:"node-diff-tab-title"}).appendTo(tabDiv);
|
|
var nodesDiv = $('<div>').appendTo(tabDiv);
|
|
var originalCell = $('<div>',{class:"node-diff-node-entry-cell"}).appendTo(titleRow);
|
|
var localCell = $('<div>',{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(titleRow);
|
|
var remoteCell;
|
|
var selectState;
|
|
|
|
if (remoteDiff) {
|
|
remoteCell = $('<div>',{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(titleRow);
|
|
}
|
|
$('<span class="node-diff-chevron"><i class="fa fa-angle-down"></i></span>').appendTo(originalCell);
|
|
createNodeIcon(tab,def).appendTo(originalCell);
|
|
var tabForLabel = (object.newTab || object.tab).n;
|
|
var titleSpan = $('<span>',{class:"node-diff-tab-title-meta"}).appendTo(originalCell);
|
|
if (tabForLabel.type === 'tab') {
|
|
titleSpan.html(tabForLabel.label||tabForLabel.id);
|
|
} else if (tab.type === 'subflow') {
|
|
titleSpan.html((tabForLabel.name||tabForLabel.id));
|
|
} else {
|
|
titleSpan.html(RED._("diff.globalNodes"));
|
|
}
|
|
var flowStats = {
|
|
local: {
|
|
addedCount:0,
|
|
deletedCount:0,
|
|
changedCount:0,
|
|
unchangedCount: 0
|
|
},
|
|
remote: {
|
|
addedCount:0,
|
|
deletedCount:0,
|
|
changedCount:0,
|
|
unchangedCount: 0
|
|
},
|
|
conflicts: 0
|
|
}
|
|
if (object.newTab || object.remoteTab) {
|
|
var localTabNode = {
|
|
node: localDiff.newConfig.all[tab.id],
|
|
all: localDiff.newConfig.all,
|
|
diff: localDiff
|
|
}
|
|
var remoteTabNode;
|
|
if (remoteDiff) {
|
|
remoteTabNode = {
|
|
node:remoteDiff.newConfig.all[tab.id]||null,
|
|
all: remoteDiff.newConfig.all,
|
|
diff: remoteDiff
|
|
}
|
|
}
|
|
if (tab.type !== undefined) {
|
|
var div = $("<div>",{class:"node-diff-node-entry node-diff-node-props collapsed"}).appendTo(nodesDiv);
|
|
var row = $("<div>",{class:"node-diff-node-entry-header"}).appendTo(div);
|
|
var originalNodeDiv = $("<div>",{class:"node-diff-node-entry-cell"}).appendTo(row);
|
|
var localNodeDiv = $("<div>",{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(row);
|
|
var localChanged = false;
|
|
var remoteChanged = false;
|
|
|
|
if (!localDiff.newConfig.all[tab.id]) {
|
|
localNodeDiv.addClass("node-diff-empty");
|
|
} else if (localDiff.added[tab.id]) {
|
|
localNodeDiv.addClass("node-diff-node-added");
|
|
localChanged = true;
|
|
$('<span class="node-diff-status"><i class="fa fa-plus-square"></i> <span data-i18n="diff.type.added"></span></span>').appendTo(localNodeDiv);
|
|
} else if (localDiff.changed[tab.id]) {
|
|
localNodeDiv.addClass("node-diff-node-changed");
|
|
localChanged = true;
|
|
$('<span class="node-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
|
|
} else {
|
|
localNodeDiv.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"><i class="fa fa-square-o"></i> <span data-i18n="diff.type.unchanged"></span></span>').appendTo(localNodeDiv);
|
|
}
|
|
|
|
var remoteNodeDiv;
|
|
if (remoteDiff) {
|
|
remoteNodeDiv = $("<div>",{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(row);
|
|
if (!remoteDiff.newConfig.all[tab.id]) {
|
|
remoteNodeDiv.addClass("node-diff-empty");
|
|
if (remoteDiff.deleted[tab.id]) {
|
|
remoteChanged = true;
|
|
}
|
|
} else if (remoteDiff.added[tab.id]) {
|
|
remoteNodeDiv.addClass("node-diff-node-added");
|
|
remoteChanged = true;
|
|
$('<span class="node-diff-status"><i class="fa fa-plus-square"></i> <span data-i18n="diff.type.added"></span></span>').appendTo(remoteNodeDiv);
|
|
} else if (remoteDiff.changed[tab.id]) {
|
|
remoteNodeDiv.addClass("node-diff-node-changed");
|
|
remoteChanged = true;
|
|
$('<span class="node-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
|
|
} else {
|
|
remoteNodeDiv.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"><i class="fa fa-square-o"></i> <span data-i18n="diff.type.unchanged"></span></span>').appendTo(remoteNodeDiv);
|
|
}
|
|
}
|
|
$('<span class="node-diff-chevron"><i class="fa fa-angle-down"></i></span>').appendTo(originalNodeDiv);
|
|
$('<span>').html(RED._("diff.flowProperties")).appendTo(originalNodeDiv);
|
|
|
|
row.click(function(evt) {
|
|
evt.preventDefault();
|
|
$(this).parent().toggleClass('collapsed');
|
|
});
|
|
|
|
createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,conflicts).appendTo(div);
|
|
selectState = "";
|
|
if (conflicts[tab.id]) {
|
|
flowStats.conflicts++;
|
|
|
|
if (!localNodeDiv.hasClass("node-diff-empty")) {
|
|
$('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i></span></span>').prependTo(localNodeDiv);
|
|
}
|
|
if (!remoteNodeDiv.hasClass("node-diff-empty")) {
|
|
$('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i></span></span>').prependTo(remoteNodeDiv);
|
|
}
|
|
div.addClass("node-diff-node-entry-conflict");
|
|
} else {
|
|
selectState = currentDiff.resolutions[tab.id];
|
|
}
|
|
// Tab properties row
|
|
createNodeConflictRadioBoxes(tab,div,localNodeDiv,remoteNodeDiv,true,!conflicts[tab.id],selectState);
|
|
}
|
|
}
|
|
// var stats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(titleRow);
|
|
var localNodeCount = 0;
|
|
var remoteNodeCount = 0;
|
|
var seen = {};
|
|
object.tab.nodes.forEach(function(node) {
|
|
seen[node.id] = true;
|
|
createNodeDiffRow(node,flowStats).appendTo(nodesDiv)
|
|
});
|
|
if (object.newTab) {
|
|
localNodeCount = object.newTab.nodes.length;
|
|
object.newTab.nodes.forEach(function(node) {
|
|
if (!seen[node.id]) {
|
|
seen[node.id] = true;
|
|
createNodeDiffRow(node,flowStats).appendTo(nodesDiv)
|
|
}
|
|
});
|
|
}
|
|
if (object.remoteTab) {
|
|
remoteNodeCount = object.remoteTab.nodes.length;
|
|
object.remoteTab.nodes.forEach(function(node) {
|
|
if (!seen[node.id]) {
|
|
createNodeDiffRow(node,flowStats).appendTo(nodesDiv)
|
|
}
|
|
});
|
|
}
|
|
titleRow.click(function(evt) {
|
|
// if (titleRow.parent().find(".node-diff-node-entry:not(.hide)").length > 0) {
|
|
titleRow.parent().toggleClass('collapsed');
|
|
if ($(this).parent().hasClass('collapsed')) {
|
|
$(this).parent().find('.node-diff-node-entry').addClass('collapsed');
|
|
$(this).parent().find('.debug-message-element').addClass('collapsed');
|
|
}
|
|
// }
|
|
})
|
|
|
|
if (localDiff.deleted[tab.id]) {
|
|
$('<span class="node-diff-node-deleted"><span class="node-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.flowDeleted"></span></span></span>').appendTo(localCell);
|
|
} else if (object.newTab) {
|
|
if (localDiff.added[tab.id]) {
|
|
$('<span class="node-diff-node-added"><span class="node-diff-status"><i class="fa fa-plus-square"></i> <span data-i18n="diff.type.flowAdded"></span></span></span>').appendTo(localCell);
|
|
} else {
|
|
if (tab.id) {
|
|
if (localDiff.changed[tab.id]) {
|
|
flowStats.local.changedCount++;
|
|
} else {
|
|
flowStats.local.unchangedCount++;
|
|
}
|
|
}
|
|
var localStats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(localCell);
|
|
$('<span class="node-diff-status"></span>').html(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats);
|
|
|
|
if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) {
|
|
$('<span class="node-diff-status"> [ </span>').appendTo(localStats);
|
|
if (flowStats.conflicts > 0) {
|
|
$('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(localStats);
|
|
}
|
|
if (flowStats.local.addedCount > 0) {
|
|
$('<span class="node-diff-node-added"><span class="node-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.local.addedCount+'</span></span>').appendTo(localStats);
|
|
}
|
|
if (flowStats.local.changedCount > 0) {
|
|
$('<span class="node-diff-node-changed"><span class="node-diff-status"><i class="fa fa-square"></i> '+flowStats.local.changedCount+'</span></span>').appendTo(localStats);
|
|
}
|
|
if (flowStats.local.deletedCount > 0) {
|
|
$('<span class="node-diff-node-deleted"><span class="node-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.local.deletedCount+'</span></span>').appendTo(localStats);
|
|
}
|
|
$('<span class="node-diff-status"> ] </span>').appendTo(localStats);
|
|
}
|
|
|
|
}
|
|
} else {
|
|
localCell.addClass("node-diff-empty");
|
|
}
|
|
|
|
if (remoteDiff) {
|
|
if (remoteDiff.deleted[tab.id]) {
|
|
$('<span class="node-diff-node-deleted"><span class="node-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.flowDeleted"></span></span></span>').appendTo(remoteCell);
|
|
} else if (object.remoteTab) {
|
|
if (remoteDiff.added[tab.id]) {
|
|
$('<span class="node-diff-node-added"><span class="node-diff-status"><i class="fa fa-plus-square"></i> <span data-i18n="diff.type.flowAdded"></span></span></span>').appendTo(remoteCell);
|
|
} else {
|
|
if (tab.id) {
|
|
if (remoteDiff.changed[tab.id]) {
|
|
flowStats.remote.changedCount++;
|
|
} else {
|
|
flowStats.remote.unchangedCount++;
|
|
}
|
|
}
|
|
var remoteStats = $('<span>',{class:"node-diff-tab-stats"}).appendTo(remoteCell);
|
|
$('<span class="node-diff-status"></span>').html(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats);
|
|
if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) {
|
|
$('<span class="node-diff-status"> [ </span>').appendTo(remoteStats);
|
|
if (flowStats.conflicts > 0) {
|
|
$('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i> '+flowStats.conflicts+'</span></span>').appendTo(remoteStats);
|
|
}
|
|
if (flowStats.remote.addedCount > 0) {
|
|
$('<span class="node-diff-node-added"><span class="node-diff-status"><i class="fa fa-plus-square"></i> '+flowStats.remote.addedCount+'</span></span>').appendTo(remoteStats);
|
|
}
|
|
if (flowStats.remote.changedCount > 0) {
|
|
$('<span class="node-diff-node-changed"><span class="node-diff-status"><i class="fa fa-square"></i> '+flowStats.remote.changedCount+'</span></span>').appendTo(remoteStats);
|
|
}
|
|
if (flowStats.remote.deletedCount > 0) {
|
|
$('<span class="node-diff-node-deleted"><span class="node-diff-status"><i class="fa fa-minus-square"></i> '+flowStats.remote.deletedCount+'</span></span>').appendTo(remoteStats);
|
|
}
|
|
$('<span class="node-diff-status"> ] </span>').appendTo(remoteStats);
|
|
}
|
|
}
|
|
} else {
|
|
remoteCell.addClass("node-diff-empty");
|
|
}
|
|
selectState = "";
|
|
if (flowStats.conflicts > 0) {
|
|
titleRow.addClass("node-diff-node-entry-conflict");
|
|
} else {
|
|
selectState = currentDiff.resolutions[tab.id];
|
|
}
|
|
if (tab.id) {
|
|
var hide = !(flowStats.conflicts > 0 &&(localDiff.deleted[tab.id] || remoteDiff.deleted[tab.id]));
|
|
// Tab parent row
|
|
createNodeConflictRadioBoxes(tab,titleRow,localCell,remoteCell, false, hide, selectState);
|
|
}
|
|
}
|
|
|
|
if (tabDiv.find(".node-diff-node-entry").length === 0) {
|
|
tabDiv.addClass("node-diff-tab-empty");
|
|
}
|
|
container.i18n();
|
|
}
|
|
});
|
|
return diffList;
|
|
}
|
|
function buildDiffPanel(container,diff,options) {
|
|
var diffPanel = $('<div class="node-dialog-view-diff-panel"></div>').appendTo(container);
|
|
var diffHeaders = $('<div class="node-dialog-view-diff-headers"></div>').appendTo(diffPanel);
|
|
if (options.mode === "merge") {
|
|
diffPanel.addClass("node-dialog-view-diff-panel-merge");
|
|
var toolbar = $('<div class="node-diff-toolbar">'+
|
|
'<span><span id="node-diff-toolbar-resolved-conflicts"></span></span> '+
|
|
'</div>').prependTo(diffPanel);
|
|
}
|
|
var diffList = createDiffTable(diffPanel);
|
|
|
|
var localDiff = diff.localDiff;
|
|
var remoteDiff = diff.remoteDiff;
|
|
var conflicts = diff.conflicts;
|
|
|
|
var currentConfig = localDiff.currentConfig;
|
|
var newConfig = localDiff.newConfig;
|
|
|
|
|
|
if (remoteDiff !== undefined) {
|
|
diffPanel.addClass('node-diff-three-way');
|
|
var localTitle = options.oldRevTitle || RED._('diff.local');
|
|
var remoteTitle = options.newRevTitle || RED._('diff.remote');
|
|
$('<div></div>').text(localTitle).appendTo(diffHeaders);
|
|
$('<div></div>').text(remoteTitle).appendTo(diffHeaders);
|
|
} else {
|
|
diffPanel.removeClass('node-diff-three-way');
|
|
}
|
|
|
|
return {
|
|
list: diffList,
|
|
finish: function() {
|
|
var el = {
|
|
diff: localDiff,
|
|
def: {
|
|
category: 'config',
|
|
color: '#f0f0f0'
|
|
},
|
|
tab: {
|
|
n: {},
|
|
nodes: currentConfig.globals
|
|
},
|
|
newTab: {
|
|
n: {},
|
|
nodes: newConfig.globals
|
|
}
|
|
};
|
|
if (remoteDiff !== undefined) {
|
|
el.remoteTab = {
|
|
n:{},
|
|
nodes:remoteDiff.newConfig.globals
|
|
};
|
|
el.remoteDiff = remoteDiff;
|
|
}
|
|
diffList.editableList('addItem',el);
|
|
|
|
var seenTabs = {};
|
|
|
|
currentConfig.tabOrder.forEach(function(tabId) {
|
|
var tab = currentConfig.tabs[tabId];
|
|
var el = {
|
|
diff: localDiff,
|
|
def: RED.nodes.getType('tab'),
|
|
tab:tab
|
|
};
|
|
if (newConfig.tabs.hasOwnProperty(tabId)) {
|
|
el.newTab = newConfig.tabs[tabId];
|
|
}
|
|
if (remoteDiff !== undefined) {
|
|
el.remoteTab = remoteDiff.newConfig.tabs[tabId];
|
|
el.remoteDiff = remoteDiff;
|
|
}
|
|
seenTabs[tabId] = true;
|
|
diffList.editableList('addItem',el)
|
|
});
|
|
newConfig.tabOrder.forEach(function(tabId) {
|
|
if (!seenTabs[tabId]) {
|
|
seenTabs[tabId] = true;
|
|
var tab = newConfig.tabs[tabId];
|
|
var el = {
|
|
diff: localDiff,
|
|
def: RED.nodes.getType('tab'),
|
|
tab:tab,
|
|
newTab: tab
|
|
};
|
|
if (remoteDiff !== undefined) {
|
|
el.remoteDiff = remoteDiff;
|
|
}
|
|
diffList.editableList('addItem',el)
|
|
}
|
|
});
|
|
if (remoteDiff !== undefined) {
|
|
remoteDiff.newConfig.tabOrder.forEach(function(tabId) {
|
|
if (!seenTabs[tabId]) {
|
|
var tab = remoteDiff.newConfig.tabs[tabId];
|
|
// TODO how to recognise this is a remotely added flow
|
|
var el = {
|
|
diff: localDiff,
|
|
remoteDiff: remoteDiff,
|
|
def: RED.nodes.getType('tab'),
|
|
tab:tab,
|
|
remoteTab:tab
|
|
};
|
|
diffList.editableList('addItem',el)
|
|
}
|
|
});
|
|
}
|
|
var subflowId;
|
|
for (subflowId in currentConfig.subflows) {
|
|
if (currentConfig.subflows.hasOwnProperty(subflowId)) {
|
|
seenTabs[subflowId] = true;
|
|
el = {
|
|
diff: localDiff,
|
|
def: {
|
|
defaults:{},
|
|
icon:"subflow.png",
|
|
category: "subflows",
|
|
color: "#da9"
|
|
},
|
|
tab:currentConfig.subflows[subflowId]
|
|
}
|
|
if (newConfig.subflows.hasOwnProperty(subflowId)) {
|
|
el.newTab = newConfig.subflows[subflowId];
|
|
}
|
|
if (remoteDiff !== undefined) {
|
|
el.remoteTab = remoteDiff.newConfig.subflows[subflowId];
|
|
el.remoteDiff = remoteDiff;
|
|
}
|
|
diffList.editableList('addItem',el)
|
|
}
|
|
}
|
|
for (subflowId in newConfig.subflows) {
|
|
if (newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) {
|
|
seenTabs[subflowId] = true;
|
|
el = {
|
|
diff: localDiff,
|
|
def: {
|
|
defaults:{},
|
|
icon:"subflow.png",
|
|
category: "subflows",
|
|
color: "#da9"
|
|
},
|
|
tab:newConfig.subflows[subflowId],
|
|
newTab:newConfig.subflows[subflowId]
|
|
}
|
|
if (remoteDiff !== undefined) {
|
|
el.remoteDiff = remoteDiff;
|
|
}
|
|
diffList.editableList('addItem',el)
|
|
}
|
|
}
|
|
if (remoteDiff !== undefined) {
|
|
for (subflowId in remoteDiff.newConfig.subflows) {
|
|
if (remoteDiff.newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) {
|
|
el = {
|
|
diff: localDiff,
|
|
remoteDiff: remoteDiff,
|
|
def: {
|
|
defaults:{},
|
|
icon:"subflow.png",
|
|
category: "subflows",
|
|
color: "#da9"
|
|
},
|
|
tab:remoteDiff.newConfig.subflows[subflowId],
|
|
remoteTab: remoteDiff.newConfig.subflows[subflowId]
|
|
}
|
|
diffList.editableList('addItem',el)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
function formatWireProperty(wires,allNodes) {
|
|
var result = $("<div>",{class:"node-diff-property-wires"})
|
|
var list = $("<ol></ol>");
|
|
var c = 0;
|
|
wires.forEach(function(p,i) {
|
|
var port = $("<li>").appendTo(list);
|
|
if (p && p.length > 0) {
|
|
$("<span>").html(i+1).appendTo(port);
|
|
var links = $("<ul>").appendTo(port);
|
|
p.forEach(function(d) {
|
|
c++;
|
|
var entry = $("<li>").appendTo(links);
|
|
var node = allNodes[d];
|
|
if (node) {
|
|
var def = RED.nodes.getType(node.type)||{};
|
|
createNode(node,def).appendTo(entry);
|
|
} else {
|
|
entry.html(d);
|
|
}
|
|
})
|
|
} else {
|
|
port.html('none');
|
|
}
|
|
})
|
|
if (c === 0) {
|
|
result.html("none");
|
|
} else {
|
|
list.appendTo(result);
|
|
}
|
|
return result;
|
|
}
|
|
function createNodeIcon(node,def) {
|
|
var nodeDiv = $("<div>",{class:"node-diff-node-entry-node"});
|
|
var colour = def.color;
|
|
var icon_url = RED.utils.getNodeIcon(def,node);
|
|
if (node.type === 'tab') {
|
|
colour = "#C0DEED";
|
|
}
|
|
nodeDiv.css('backgroundColor',colour);
|
|
|
|
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
|
|
$('<div/>',{class:"palette_icon",style:"background-image: url("+icon_url+")"}).appendTo(iconContainer);
|
|
|
|
return nodeDiv;
|
|
}
|
|
function createNode(node,def) {
|
|
var nodeTitleDiv = $("<div>",{class:"node-diff-node-entry-title"})
|
|
createNodeIcon(node,def).appendTo(nodeTitleDiv);
|
|
var contentDiv = $('<div>',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv);
|
|
var nodeLabel = node.label || node.name || node.id;
|
|
$('<span>',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv);
|
|
return nodeTitleDiv;
|
|
}
|
|
function createNodeDiffRow(node,stats) {
|
|
var localDiff = currentDiff.localDiff;
|
|
var remoteDiff = currentDiff.remoteDiff;
|
|
var conflicted = currentDiff.conflicts[node.id];
|
|
|
|
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++;
|
|
unChanged = false;
|
|
}
|
|
if (remoteDiff && remoteDiff.added[node.id]) {
|
|
stats.remote.addedCount++;
|
|
unChanged = false;
|
|
}
|
|
if (localDiff.deleted[node.id]) {
|
|
stats.local.deletedCount++;
|
|
unChanged = false;
|
|
}
|
|
if (remoteDiff && remoteDiff.deleted[node.id]) {
|
|
stats.remote.deletedCount++;
|
|
unChanged = false;
|
|
}
|
|
if (localDiff.changed[node.id]) {
|
|
stats.local.changedCount++;
|
|
hasChanges = true;
|
|
unChanged = false;
|
|
}
|
|
if (remoteDiff && remoteDiff.changed[node.id]) {
|
|
stats.remote.changedCount++;
|
|
hasChanges = true;
|
|
unChanged = false;
|
|
}
|
|
// console.log(node.id,localDiff.added[node.id],remoteDiff.added[node.id],localDiff.deleted[node.id],remoteDiff.deleted[node.id],localDiff.changed[node.id],remoteDiff.changed[node.id])
|
|
var def = RED.nodes.getType(node.type);
|
|
if (def === undefined) {
|
|
if (/^subflow:/.test(node.type)) {
|
|
def = {
|
|
icon:"subflow.png",
|
|
category: "subflows",
|
|
color: "#da9",
|
|
defaults:{name:{value:""}}
|
|
}
|
|
} else {
|
|
def = {};
|
|
}
|
|
}
|
|
var div = $("<div>",{class:"node-diff-node-entry collapsed"});
|
|
var row = $("<div>",{class:"node-diff-node-entry-header"}).appendTo(div);
|
|
|
|
var originalNodeDiv = $("<div>",{class:"node-diff-node-entry-cell"}).appendTo(row);
|
|
var localNodeDiv = $("<div>",{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(row);
|
|
var remoteNodeDiv;
|
|
var chevron;
|
|
if (remoteDiff) {
|
|
remoteNodeDiv = $("<div>",{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(row);
|
|
}
|
|
$('<span class="node-diff-chevron"><i class="fa fa-angle-down"></i></span>').appendTo(originalNodeDiv);
|
|
|
|
if (unChanged) {
|
|
stats.local.unchangedCount++;
|
|
createNode(node,def).appendTo(originalNodeDiv);
|
|
localNodeDiv.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"><i class="fa fa-square-o"></i> <span data-i18n="diff.type.unchanged"></span></span>').appendTo(localNodeDiv);
|
|
if (remoteDiff) {
|
|
stats.remote.unchangedCount++;
|
|
remoteNodeDiv.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"><i class="fa fa-square-o"></i> <span data-i18n="diff.type.unchanged"></span></span>').appendTo(remoteNodeDiv);
|
|
}
|
|
div.addClass("node-diff-node-unchanged");
|
|
} else if (localDiff.added[node.id]) {
|
|
localNodeDiv.addClass("node-diff-node-added");
|
|
if (remoteNodeDiv) {
|
|
remoteNodeDiv.addClass("node-diff-empty");
|
|
}
|
|
$('<span class="node-diff-status"><i class="fa fa-plus-square"></i> <span data-i18n="diff.type.added"></span></span>').appendTo(localNodeDiv);
|
|
createNode(node,def).appendTo(originalNodeDiv);
|
|
} else if (remoteDiff && remoteDiff.added[node.id]) {
|
|
localNodeDiv.addClass("node-diff-empty");
|
|
remoteNodeDiv.addClass("node-diff-node-added");
|
|
$('<span class="node-diff-status"><i class="fa fa-plus-square"></i> <span data-i18n="diff.type.added"></span></span>').appendTo(remoteNodeDiv);
|
|
createNode(node,def).appendTo(originalNodeDiv);
|
|
} else {
|
|
createNode(node,def).appendTo(originalNodeDiv);
|
|
if (localDiff.moved[node.id]) {
|
|
var localN = localDiff.newConfig.all[node.id];
|
|
if (!localDiff.deleted[node.z] && node.z !== localN.z && node.z !== "" && !localDiff.newConfig.all[node.z]) {
|
|
localNodeDiv.addClass("node-diff-empty");
|
|
} else {
|
|
localNodeDiv.addClass("node-diff-node-moved");
|
|
var localMovedMessage = "";
|
|
if (node.z === localN.z) {
|
|
localMovedMessage = RED._("diff.type.movedFrom",{id:(localDiff.currentConfig.all[node.id].z||'global')});
|
|
} else {
|
|
localMovedMessage = RED._("diff.type.movedTo",{id:(localN.z||'global')});
|
|
}
|
|
$('<span class="node-diff-status"><i class="fa fa-caret-square-o-right"></i> '+localMovedMessage+'</span>').appendTo(localNodeDiv);
|
|
}
|
|
localChanged = true;
|
|
} else if (localDiff.deleted[node.z]) {
|
|
localNodeDiv.addClass("node-diff-empty");
|
|
localChanged = true;
|
|
} else if (localDiff.deleted[node.id]) {
|
|
localNodeDiv.addClass("node-diff-node-deleted");
|
|
$('<span class="node-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]) {
|
|
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
|
localNodeDiv.addClass("node-diff-empty");
|
|
} else {
|
|
localNodeDiv.addClass("node-diff-node-changed");
|
|
$('<span class="node-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(localNodeDiv);
|
|
localChanged = true;
|
|
}
|
|
} else {
|
|
if (localDiff.newConfig.all[node.id].z !== node.z) {
|
|
localNodeDiv.addClass("node-diff-empty");
|
|
} else {
|
|
stats.local.unchangedCount++;
|
|
localNodeDiv.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"><i class="fa fa-square-o"></i> <span data-i18n="diff.type.unchanged"></span></span>').appendTo(localNodeDiv);
|
|
}
|
|
}
|
|
|
|
if (remoteDiff) {
|
|
if (remoteDiff.moved[node.id]) {
|
|
var remoteN = remoteDiff.newConfig.all[node.id];
|
|
if (!remoteDiff.deleted[node.z] && node.z !== remoteN.z && node.z !== "" && !remoteDiff.newConfig.all[node.z]) {
|
|
remoteNodeDiv.addClass("node-diff-empty");
|
|
} else {
|
|
remoteNodeDiv.addClass("node-diff-node-moved");
|
|
var remoteMovedMessage = "";
|
|
if (node.z === remoteN.z) {
|
|
remoteMovedMessage = RED._("diff.type.movedFrom",{id:(remoteDiff.currentConfig.all[node.id].z||'global')});
|
|
} else {
|
|
remoteMovedMessage = RED._("diff.type.movedTo",{id:(remoteN.z||'global')});
|
|
}
|
|
$('<span class="node-diff-status"><i class="fa fa-caret-square-o-right"></i> '+remoteMovedMessage+'</span>').appendTo(remoteNodeDiv);
|
|
}
|
|
} else if (remoteDiff.deleted[node.z]) {
|
|
remoteNodeDiv.addClass("node-diff-empty");
|
|
} else if (remoteDiff.deleted[node.id]) {
|
|
remoteNodeDiv.addClass("node-diff-node-deleted");
|
|
$('<span class="node-diff-status"><i class="fa fa-minus-square"></i> <span data-i18n="diff.type.deleted"></span></span>').appendTo(remoteNodeDiv);
|
|
} else if (remoteDiff.changed[node.id]) {
|
|
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
|
remoteNodeDiv.addClass("node-diff-empty");
|
|
} else {
|
|
remoteNodeDiv.addClass("node-diff-node-changed");
|
|
$('<span class="node-diff-status"><i class="fa fa-square"></i> <span data-i18n="diff.type.changed"></span></span>').appendTo(remoteNodeDiv);
|
|
}
|
|
} else {
|
|
if (remoteDiff.newConfig.all[node.id].z !== node.z) {
|
|
remoteNodeDiv.addClass("node-diff-empty");
|
|
} else {
|
|
stats.remote.unchangedCount++;
|
|
remoteNodeDiv.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"><i class="fa fa-square-o"></i> <span data-i18n="diff.type.unchanged"></span></span>').appendTo(remoteNodeDiv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var localNode = {
|
|
node: localDiff.newConfig.all[node.id],
|
|
all: localDiff.newConfig.all,
|
|
diff: localDiff
|
|
};
|
|
var remoteNode;
|
|
if (remoteDiff) {
|
|
remoteNode = {
|
|
node:remoteDiff.newConfig.all[node.id]||null,
|
|
all: remoteDiff.newConfig.all,
|
|
diff: remoteDiff
|
|
}
|
|
}
|
|
createNodePropertiesTable(def,node,localNode,remoteNode).appendTo(div);
|
|
|
|
var selectState = "";
|
|
|
|
if (conflicted) {
|
|
stats.conflicts++;
|
|
if (!localNodeDiv.hasClass("node-diff-empty")) {
|
|
$('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i></span></span>').prependTo(localNodeDiv);
|
|
}
|
|
if (!remoteNodeDiv.hasClass("node-diff-empty")) {
|
|
$('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i></span></span>').prependTo(remoteNodeDiv);
|
|
}
|
|
div.addClass("node-diff-node-entry-conflict");
|
|
} else {
|
|
selectState = currentDiff.resolutions[node.id];
|
|
}
|
|
// Node row
|
|
createNodeConflictRadioBoxes(node,div,localNodeDiv,remoteNodeDiv,false,!conflicted,selectState);
|
|
row.click(function(evt) {
|
|
$(this).parent().toggleClass('collapsed');
|
|
});
|
|
|
|
return div;
|
|
}
|
|
function createNodePropertiesTable(def,node,localNodeObj,remoteNodeObj) {
|
|
var propertyElements = {};
|
|
var localNode = localNodeObj.node;
|
|
var remoteNode;
|
|
if (remoteNodeObj) {
|
|
remoteNode = remoteNodeObj.node;
|
|
}
|
|
|
|
var nodePropertiesDiv = $("<div>",{class:"node-diff-node-entry-properties"});
|
|
var nodePropertiesTable = $("<table>").appendTo(nodePropertiesDiv);
|
|
var nodePropertiesTableCols = $('<colgroup><col/><col/></colgroup>').appendTo(nodePropertiesTable);
|
|
if (remoteNode !== undefined) {
|
|
$("<col/>").appendTo(nodePropertiesTableCols);
|
|
}
|
|
var nodePropertiesTableBody = $("<tbody>").appendTo(nodePropertiesTable);
|
|
|
|
var row;
|
|
var localCell, remoteCell;
|
|
var element;
|
|
var currentValue, localValue, remoteValue;
|
|
var localChanged = false;
|
|
var remoteChanged = false;
|
|
var localChanges = 0;
|
|
var remoteChanges = 0;
|
|
var conflict = false;
|
|
var status;
|
|
|
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
|
$("<td>",{class:"node-diff-property-cell-label"}).html("id").appendTo(row);
|
|
localCell = $("<td>",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row);
|
|
if (localNode) {
|
|
localCell.addClass("node-diff-node-unchanged");
|
|
$('<span class="node-diff-status"></span>').appendTo(localCell);
|
|
element = $('<span class="node-diff-element"></span>').appendTo(localCell);
|
|
propertyElements['local.id'] = RED.utils.createObjectElement(localNode.id).appendTo(element);
|
|
} else {
|
|
localCell.addClass("node-diff-empty");
|
|
}
|
|
if (remoteNode !== undefined) {
|
|
remoteCell = $("<td>",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row);
|
|
remoteCell.addClass("node-diff-node-unchanged");
|
|
if (remoteNode) {
|
|
$('<span class="node-diff-status"></span>').appendTo(remoteCell);
|
|
element = $('<span class="node-diff-element"></span>').appendTo(remoteCell);
|
|
propertyElements['remote.id'] = RED.utils.createObjectElement(remoteNode.id).appendTo(element);
|
|
} else {
|
|
remoteCell.addClass("node-diff-empty");
|
|
}
|
|
}
|
|
|
|
|
|
if (node.hasOwnProperty('x')) {
|
|
if (localNode) {
|
|
if (localNode.x !== node.x || localNode.y !== node.y) {
|
|
localChanged = true;
|
|
localChanges++;
|
|
}
|
|
}
|
|
if (remoteNode) {
|
|
if (remoteNode.x !== node.x || remoteNode.y !== node.y) {
|
|
remoteChanged = true;
|
|
remoteChanges++;
|
|
}
|
|
}
|
|
if ( (remoteChanged && localChanged && (localNode.x !== remoteNode.x || localNode.y !== remoteNode.y)) ||
|
|
(!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) ||
|
|
(localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id])
|
|
) {
|
|
conflict = true;
|
|
}
|
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
|
$("<td>",{class:"node-diff-property-cell-label"}).html("position").appendTo(row);
|
|
localCell = $("<td>",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row);
|
|
if (localNode) {
|
|
localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged"));
|
|
$('<span class="node-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
|
element = $('<span class="node-diff-element"></span>').appendTo(localCell);
|
|
propertyElements['local.position'] = RED.utils.createObjectElement({x:localNode.x,y:localNode.y},
|
|
{
|
|
path: "position",
|
|
exposeApi: true,
|
|
ontoggle: function(path,state) {
|
|
if (propertyElements['remote.'+path]) {
|
|
propertyElements['remote.'+path].prop('expand')(path,state)
|
|
}
|
|
}
|
|
}
|
|
).appendTo(element);
|
|
} else {
|
|
localCell.addClass("node-diff-empty");
|
|
}
|
|
|
|
if (remoteNode !== undefined) {
|
|
remoteCell = $("<td>",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row);
|
|
remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged"));
|
|
if (remoteNode) {
|
|
$('<span class="node-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
|
element = $('<span class="node-diff-element"></span>').appendTo(remoteCell);
|
|
propertyElements['remote.position'] = RED.utils.createObjectElement({x:remoteNode.x,y:remoteNode.y},
|
|
{
|
|
path: "position",
|
|
exposeApi: true,
|
|
ontoggle: function(path,state) {
|
|
if (propertyElements['local.'+path]) {
|
|
propertyElements['local.'+path].prop('expand')(path,state);
|
|
}
|
|
}
|
|
}
|
|
).appendTo(element);
|
|
} else {
|
|
remoteCell.addClass("node-diff-empty");
|
|
}
|
|
}
|
|
}
|
|
//
|
|
localChanged = remoteChanged = conflict = false;
|
|
if (node.hasOwnProperty('wires')) {
|
|
currentValue = JSON.stringify(node.wires);
|
|
if (localNode) {
|
|
localValue = JSON.stringify(localNode.wires);
|
|
if (currentValue !== localValue) {
|
|
localChanged = true;
|
|
localChanges++;
|
|
}
|
|
}
|
|
if (remoteNode) {
|
|
remoteValue = JSON.stringify(remoteNode.wires);
|
|
if (currentValue !== remoteValue) {
|
|
remoteChanged = true;
|
|
remoteChanges++;
|
|
}
|
|
}
|
|
if ( (remoteChanged && localChanged && (localValue !== remoteValue)) ||
|
|
(!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) ||
|
|
(localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id])
|
|
){
|
|
conflict = true;
|
|
}
|
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
|
$("<td>",{class:"node-diff-property-cell-label"}).html("wires").appendTo(row);
|
|
localCell = $("<td>",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row);
|
|
if (localNode) {
|
|
if (!conflict) {
|
|
localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged"));
|
|
$('<span class="node-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
|
} else {
|
|
localCell.addClass("node-diff-node-conflict");
|
|
$('<span class="node-diff-status"><i class="fa fa-exclamation"></i></span>').appendTo(localCell);
|
|
}
|
|
formatWireProperty(localNode.wires,localNodeObj.all).appendTo(localCell);
|
|
} else {
|
|
localCell.addClass("node-diff-empty");
|
|
}
|
|
|
|
if (remoteNode !== undefined) {
|
|
remoteCell = $("<td>",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row);
|
|
if (remoteNode) {
|
|
if (!conflict) {
|
|
remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged"));
|
|
$('<span class="node-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
|
} else {
|
|
remoteCell.addClass("node-diff-node-conflict");
|
|
$('<span class="node-diff-status"><i class="fa fa-exclamation"></i></span>').appendTo(remoteCell);
|
|
}
|
|
formatWireProperty(remoteNode.wires,remoteNodeObj.all).appendTo(remoteCell);
|
|
} else {
|
|
remoteCell.addClass("node-diff-empty");
|
|
}
|
|
}
|
|
}
|
|
|
|
var properties = Object.keys(node).filter(function(p) { return p!='inputLabels'&&p!='outputLabels'&&p!='z'&&p!='wires'&&p!=='x'&&p!=='y'&&p!=='id'&&p!=='type'&&(!def.defaults||!def.defaults.hasOwnProperty(p))});
|
|
if (def.defaults) {
|
|
properties = properties.concat(Object.keys(def.defaults));
|
|
}
|
|
if (node.type !== 'tab') {
|
|
properties = properties.concat(['inputLabels','outputLabels']);
|
|
}
|
|
properties.forEach(function(d) {
|
|
localChanged = false;
|
|
remoteChanged = false;
|
|
conflict = false;
|
|
currentValue = JSON.stringify(node[d]);
|
|
if (localNode) {
|
|
localValue = JSON.stringify(localNode[d]);
|
|
if (currentValue !== localValue) {
|
|
localChanged = true;
|
|
localChanges++;
|
|
}
|
|
}
|
|
if (remoteNode) {
|
|
remoteValue = JSON.stringify(remoteNode[d]);
|
|
if (currentValue !== remoteValue) {
|
|
remoteChanged = true;
|
|
remoteChanges++;
|
|
}
|
|
}
|
|
|
|
if ( (remoteChanged && localChanged && (localValue !== remoteValue)) ||
|
|
(!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) ||
|
|
(localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id])
|
|
){
|
|
conflict = true;
|
|
}
|
|
|
|
row = $("<tr>").appendTo(nodePropertiesTableBody);
|
|
var propertyNameCell = $("<td>",{class:"node-diff-property-cell-label"}).html(d).appendTo(row);
|
|
localCell = $("<td>",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row);
|
|
if (localNode) {
|
|
if (!conflict) {
|
|
localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged"));
|
|
$('<span class="node-diff-status">'+(localChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(localCell);
|
|
} else {
|
|
localCell.addClass("node-diff-node-conflict");
|
|
$('<span class="node-diff-status"><i class="fa fa-exclamation"></i></span>').appendTo(localCell);
|
|
}
|
|
element = $('<span class="node-diff-element"></span>').appendTo(localCell);
|
|
propertyElements['local.'+d] = RED.utils.createObjectElement(localNode[d],
|
|
{
|
|
path: d,
|
|
exposeApi: true,
|
|
ontoggle: function(path,state) {
|
|
if (propertyElements['remote.'+d]) {
|
|
propertyElements['remote.'+d].prop('expand')(path,state)
|
|
}
|
|
}
|
|
}
|
|
).appendTo(element);
|
|
} else {
|
|
localCell.addClass("node-diff-empty");
|
|
}
|
|
if (remoteNode !== undefined) {
|
|
remoteCell = $("<td>",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row);
|
|
if (remoteNode) {
|
|
if (!conflict) {
|
|
remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged"));
|
|
$('<span class="node-diff-status">'+(remoteChanged?'<i class="fa fa-square"></i>':'')+'</span>').appendTo(remoteCell);
|
|
} else {
|
|
remoteCell.addClass("node-diff-node-conflict");
|
|
$('<span class="node-diff-status"><i class="fa fa-exclamation"></i></span>').appendTo(remoteCell);
|
|
}
|
|
element = $('<span class="node-diff-element"></span>').appendTo(remoteCell);
|
|
propertyElements['remote.'+d] = RED.utils.createObjectElement(remoteNode[d],
|
|
{
|
|
path: d,
|
|
exposeApi: true,
|
|
ontoggle: function(path,state) {
|
|
if (propertyElements['local.'+d]) {
|
|
propertyElements['local.'+d].prop('expand')(path,state)
|
|
}
|
|
}
|
|
}
|
|
).appendTo(element);
|
|
} else {
|
|
remoteCell.addClass("node-diff-empty");
|
|
}
|
|
}
|
|
if (localNode && remoteNode && typeof localNode[d] === "string") {
|
|
if (/\n/.test(localNode[d]) || /\n/.test(remoteNode[d])) {
|
|
$('<button class="editor-button editor-button-small node-diff-text-diff-button"><i class="fa fa-file-o"> <i class="fa fa-caret-left"></i> <i class="fa fa-caret-right"></i> <i class="fa fa-file-o"></i></button>').click(function() {
|
|
showTextDiff(localNode[d],remoteNode[d]);
|
|
}).appendTo(propertyNameCell);
|
|
}
|
|
}
|
|
|
|
|
|
});
|
|
return nodePropertiesDiv;
|
|
}
|
|
function createNodeConflictRadioBoxes(node,row,localDiv,remoteDiv,propertiesTable,hide,state) {
|
|
var safeNodeId = "node-diff-selectbox-"+node.id.replace(/\./g,'-')+(propertiesTable?"-props":"");
|
|
var className = "";
|
|
if (node.z||propertiesTable) {
|
|
className = "node-diff-selectbox-tab-"+(propertiesTable?node.id:node.z).replace(/\./g,'-');
|
|
}
|
|
var titleRow = !propertiesTable && (node.type === 'tab' || node.type === 'subflow');
|
|
var changeHandler = function(evt) {
|
|
var className;
|
|
if (node.type === undefined) {
|
|
// TODO: handle globals
|
|
} else if (titleRow) {
|
|
className = "node-diff-selectbox-tab-"+node.id.replace(/\./g,'-');
|
|
$("."+className+"-"+this.value).prop('checked',true);
|
|
if (this.value === 'local') {
|
|
$("."+className+"-"+this.value).closest(".node-diff-node-entry").addClass("node-diff-select-local");
|
|
$("."+className+"-"+this.value).closest(".node-diff-node-entry").removeClass("node-diff-select-remote");
|
|
} else {
|
|
$("."+className+"-"+this.value).closest(".node-diff-node-entry").removeClass("node-diff-select-local");
|
|
$("."+className+"-"+this.value).closest(".node-diff-node-entry").addClass("node-diff-select-remote");
|
|
}
|
|
} else {
|
|
// Individual node or properties table
|
|
var parentId = "node-diff-selectbox-"+(propertiesTable?node.id:node.z).replace(/\./g,'-');
|
|
$('#'+parentId+"-local").prop('checked',false);
|
|
$('#'+parentId+"-remote").prop('checked',false);
|
|
var titleRowDiv = $('#'+parentId+"-local").closest(".node-diff-tab").find(".node-diff-tab-title");
|
|
titleRowDiv.removeClass("node-diff-select-local");
|
|
titleRowDiv.removeClass("node-diff-select-remote");
|
|
}
|
|
if (this.value === 'local') {
|
|
row.removeClass("node-diff-select-remote");
|
|
row.addClass("node-diff-select-local");
|
|
} else if (this.value === 'remote') {
|
|
row.addClass("node-diff-select-remote");
|
|
row.removeClass("node-diff-select-local");
|
|
}
|
|
refreshConflictHeader();
|
|
}
|
|
|
|
var localSelectDiv = $('<label>',{class:"node-diff-selectbox",for:safeNodeId+"-local"}).click(function(e) { e.stopPropagation();}).appendTo(localDiv);
|
|
var localRadio = $('<input>',{id:safeNodeId+"-local",type:'radio',value:"local",name:safeNodeId,class:className+"-local"+(titleRow?"":" node-diff-select-node")}).data('node-id',node.id).change(changeHandler).appendTo(localSelectDiv);
|
|
var remoteSelectDiv = $('<label>',{class:"node-diff-selectbox",for:safeNodeId+"-remote"}).click(function(e) { e.stopPropagation();}).appendTo(remoteDiv);
|
|
var remoteRadio = $('<input>',{id:safeNodeId+"-remote",type:'radio',value:"remote",name:safeNodeId,class:className+"-remote"+(titleRow?"":" node-diff-select-node")}).data('node-id',node.id).change(changeHandler).appendTo(remoteSelectDiv);
|
|
if (state === 'local') {
|
|
localRadio.prop('checked',true);
|
|
} else if (state === 'remote') {
|
|
remoteRadio.prop('checked',true);
|
|
}
|
|
if (hide||localDiv.hasClass("node-diff-empty") || remoteDiv.hasClass("node-diff-empty")) {
|
|
localSelectDiv.hide();
|
|
remoteSelectDiv.hide();
|
|
}
|
|
|
|
}
|
|
function refreshConflictHeader() {
|
|
var resolutionCount = 0;
|
|
$(".node-diff-selectbox>input:checked").each(function() {
|
|
if (currentDiff.conflicts[$(this).data('node-id')]) {
|
|
resolutionCount++;
|
|
}
|
|
currentDiff.resolutions[$(this).data('node-id')] = $(this).val();
|
|
})
|
|
var conflictCount = Object.keys(currentDiff.conflicts).length;
|
|
if (conflictCount - resolutionCount === 0) {
|
|
$("#node-diff-toolbar-resolved-conflicts").html('<span class="node-diff-node-added"><span class="node-diff-status"><i class="fa fa-check"></i></span></span> '+RED._("diff.unresolvedCount",{count:conflictCount - resolutionCount}));
|
|
} else {
|
|
$("#node-diff-toolbar-resolved-conflicts").html('<span class="node-diff-node-conflict"><span class="node-diff-status"><i class="fa fa-exclamation"></i></span></span> '+RED._("diff.unresolvedCount",{count:conflictCount - resolutionCount}));
|
|
}
|
|
if (conflictCount === resolutionCount) {
|
|
$("#node-diff-view-diff-merge").removeClass('disabled');
|
|
}
|
|
}
|
|
function getRemoteDiff(callback) {
|
|
$.ajax({
|
|
headers: {
|
|
"Accept":"application/json",
|
|
},
|
|
cache: false,
|
|
url: 'flows',
|
|
success: function(nodes) {
|
|
var localFlow = RED.nodes.createCompleteNodeSet();
|
|
var originalFlow = RED.nodes.originalFlow();
|
|
var remoteFlow = nodes.flows;
|
|
var localDiff = generateDiff(originalFlow,localFlow);
|
|
var remoteDiff = generateDiff(originalFlow,remoteFlow);
|
|
remoteDiff.rev = nodes.rev;
|
|
callback(resolveDiffs(localDiff,remoteDiff))
|
|
}
|
|
});
|
|
|
|
}
|
|
// function showLocalDiff() {
|
|
// var nns = RED.nodes.createCompleteNodeSet();
|
|
// var originalFlow = RED.nodes.originalFlow();
|
|
// var diff = generateDiff(originalFlow,nns);
|
|
// showDiff(diff);
|
|
// }
|
|
function showRemoteDiff(diff) {
|
|
if (diff === undefined) {
|
|
getRemoteDiff(showRemoteDiff);
|
|
} else {
|
|
showDiff(diff,{mode:'merge'});
|
|
}
|
|
}
|
|
function parseNodes(nodeList) {
|
|
var tabOrder = [];
|
|
var tabs = {};
|
|
var subflows = {};
|
|
var globals = [];
|
|
var all = {};
|
|
|
|
nodeList.forEach(function(node) {
|
|
all[node.id] = node;
|
|
if (node.type === 'tab') {
|
|
tabOrder.push(node.id);
|
|
tabs[node.id] = {n:node,nodes:[]};
|
|
} else if (node.type === 'subflow') {
|
|
subflows[node.id] = {n:node,nodes:[]};
|
|
}
|
|
});
|
|
|
|
nodeList.forEach(function(node) {
|
|
if (node.type !== 'tab' && node.type !== 'subflow') {
|
|
if (tabs[node.z]) {
|
|
tabs[node.z].nodes.push(node);
|
|
} else if (subflows[node.z]) {
|
|
subflows[node.z].nodes.push(node);
|
|
} else {
|
|
globals.push(node);
|
|
}
|
|
}
|
|
});
|
|
|
|
return {
|
|
all: all,
|
|
tabOrder: tabOrder,
|
|
tabs: tabs,
|
|
subflows: subflows,
|
|
globals: globals
|
|
}
|
|
}
|
|
function generateDiff(currentNodes,newNodes) {
|
|
var currentConfig = parseNodes(currentNodes);
|
|
var newConfig = parseNodes(newNodes);
|
|
var added = {};
|
|
var deleted = {};
|
|
var changed = {};
|
|
var moved = {};
|
|
|
|
Object.keys(currentConfig.all).forEach(function(id) {
|
|
var 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])) {
|
|
changed[id] = true;
|
|
|
|
if (currentConfig.all[id].z !== newConfig.all[id].z) {
|
|
moved[id] = true;
|
|
}
|
|
}
|
|
});
|
|
Object.keys(newConfig.all).forEach(function(id) {
|
|
if (!currentConfig.all.hasOwnProperty(id)) {
|
|
added[id] = true;
|
|
}
|
|
});
|
|
|
|
return {
|
|
currentConfig: currentConfig,
|
|
newConfig: newConfig,
|
|
added: added,
|
|
deleted: deleted,
|
|
changed: changed,
|
|
moved: moved
|
|
}
|
|
}
|
|
function resolveDiffs(localDiff,remoteDiff) {
|
|
var conflicted = {};
|
|
var resolutions = {};
|
|
|
|
var diff = {
|
|
localDiff: localDiff,
|
|
remoteDiff: remoteDiff,
|
|
conflicts: conflicted,
|
|
resolutions: resolutions
|
|
}
|
|
var seen = {};
|
|
var id,node;
|
|
for (id in localDiff.currentConfig.all) {
|
|
if (localDiff.currentConfig.all.hasOwnProperty(id)) {
|
|
seen[id] = true;
|
|
var localNode = localDiff.newConfig.all[id];
|
|
if (localDiff.changed[id] && remoteDiff.deleted[id]) {
|
|
conflicted[id] = true;
|
|
} else if (localDiff.deleted[id] && remoteDiff.changed[id]) {
|
|
conflicted[id] = true;
|
|
} else if (localDiff.changed[id] && remoteDiff.changed[id]) {
|
|
var remoteNode = remoteDiff.newConfig.all[id];
|
|
if (JSON.stringify(localNode) !== JSON.stringify(remoteNode)) {
|
|
conflicted[id] = true;
|
|
}
|
|
}
|
|
if (!conflicted[id]) {
|
|
if (remoteDiff.added[id]||remoteDiff.changed[id]||remoteDiff.deleted[id]) {
|
|
resolutions[id] = 'remote';
|
|
} else {
|
|
resolutions[id] = 'local';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
for (id in localDiff.added) {
|
|
if (localDiff.added.hasOwnProperty(id)) {
|
|
node = localDiff.newConfig.all[id];
|
|
if (remoteDiff.deleted[node.z]) {
|
|
conflicted[id] = true;
|
|
// conflicted[node.z] = true;
|
|
} else {
|
|
resolutions[id] = 'local';
|
|
}
|
|
}
|
|
}
|
|
for (id in remoteDiff.added) {
|
|
if (remoteDiff.added.hasOwnProperty(id)) {
|
|
node = remoteDiff.newConfig.all[id];
|
|
if (localDiff.deleted[node.z]) {
|
|
conflicted[id] = true;
|
|
// conflicted[node.z] = true;
|
|
} else {
|
|
resolutions[id] = 'remote';
|
|
}
|
|
}
|
|
}
|
|
// console.log(diff.resolutions);
|
|
// console.log(conflicted);
|
|
return diff;
|
|
}
|
|
|
|
function showDiff(diff,options) {
|
|
if (diffVisible) {
|
|
return;
|
|
}
|
|
options = options || {};
|
|
var mode = options.mode || 'merge';
|
|
|
|
var localDiff = diff.localDiff;
|
|
var remoteDiff = diff.remoteDiff;
|
|
var conflicts = diff.conflicts;
|
|
currentDiff = diff;
|
|
|
|
var trayOptions = {
|
|
title: options.title||"Review Changes", //TODO: nls
|
|
width: Infinity,
|
|
overlay: true,
|
|
buttons: [
|
|
{
|
|
text: RED._((options.mode === 'merge')?"common.label.cancel":"common.label.close"),
|
|
click: function() {
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
],
|
|
resize: function(dimensions) {
|
|
// trayWidth = dimensions.width;
|
|
},
|
|
open: function(tray) {
|
|
var trayBody = tray.find('.editor-tray-body');
|
|
var diffTable = buildDiffPanel(trayBody,diff,options);
|
|
diffTable.list.hide();
|
|
if (remoteDiff) {
|
|
$("#node-diff-view-diff-merge").show();
|
|
if (Object.keys(conflicts).length === 0) {
|
|
$("#node-diff-view-diff-merge").removeClass('disabled');
|
|
} else {
|
|
$("#node-diff-view-diff-merge").addClass('disabled');
|
|
}
|
|
} else {
|
|
$("#node-diff-view-diff-merge").hide();
|
|
}
|
|
refreshConflictHeader();
|
|
// console.log("--------------");
|
|
// console.log(localDiff);
|
|
// console.log(remoteDiff);
|
|
|
|
setTimeout(function() {
|
|
diffTable.finish();
|
|
diffTable.list.show();
|
|
},300);
|
|
$("#sidebar-shade").show();
|
|
},
|
|
close: function() {
|
|
diffVisible = false;
|
|
$("#sidebar-shade").hide();
|
|
|
|
},
|
|
show: function() {
|
|
|
|
}
|
|
}
|
|
if (options.mode === 'merge') {
|
|
trayOptions.buttons.push(
|
|
{
|
|
id: "node-diff-view-diff-merge",
|
|
text: RED._("deploy.confirm.button.merge"),
|
|
class: "primary disabled",
|
|
click: function() {
|
|
if (!$("#node-diff-view-diff-merge").hasClass('disabled')) {
|
|
refreshConflictHeader();
|
|
mergeDiff(currentDiff);
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
|
|
function mergeDiff(diff) {
|
|
var currentConfig = diff.localDiff.currentConfig;
|
|
var localDiff = diff.localDiff;
|
|
var remoteDiff = diff.remoteDiff;
|
|
var conflicts = diff.conflicts;
|
|
var resolutions = diff.resolutions;
|
|
var id;
|
|
|
|
for (id in conflicts) {
|
|
if (conflicts.hasOwnProperty(id)) {
|
|
if (!resolutions.hasOwnProperty(id)) {
|
|
console.log(diff);
|
|
throw new Error("No resolution for conflict on node",id);
|
|
}
|
|
}
|
|
}
|
|
|
|
var newConfig = [];
|
|
var node;
|
|
var nodeChangedStates = {};
|
|
var localChangedStates = {};
|
|
for (id in localDiff.newConfig.all) {
|
|
if (localDiff.newConfig.all.hasOwnProperty(id)) {
|
|
node = RED.nodes.node(id);
|
|
if (resolutions[id] === 'local') {
|
|
if (node) {
|
|
nodeChangedStates[id] = node.changed;
|
|
}
|
|
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;
|
|
}
|
|
localChangedStates[id] = true;
|
|
newConfig.push(remoteDiff.newConfig.all[id]);
|
|
}
|
|
} else {
|
|
console.log("Unresolved",id)
|
|
}
|
|
}
|
|
}
|
|
for (id in remoteDiff.added) {
|
|
if (remoteDiff.added.hasOwnProperty(id)) {
|
|
node = RED.nodes.node(id);
|
|
if (node) {
|
|
nodeChangedStates[id] = node.changed;
|
|
}
|
|
if (!localDiff.added.hasOwnProperty(id)) {
|
|
localChangedStates[id] = true;
|
|
newConfig.push(remoteDiff.newConfig.all[id]);
|
|
}
|
|
}
|
|
}
|
|
var historyEvent = {
|
|
t:"replace",
|
|
config: RED.nodes.createCompleteNodeSet(),
|
|
changed: nodeChangedStates,
|
|
dirty: RED.nodes.dirty(),
|
|
rev: RED.nodes.version()
|
|
}
|
|
|
|
RED.history.push(historyEvent);
|
|
|
|
RED.nodes.clear();
|
|
var imported = RED.nodes.import(newConfig);
|
|
imported[0].forEach(function(n) {
|
|
if (nodeChangedStates[n.id] || localChangedStates[n.id]) {
|
|
n.changed = true;
|
|
}
|
|
})
|
|
|
|
RED.nodes.version(remoteDiff.rev);
|
|
|
|
RED.view.redraw(true);
|
|
RED.palette.refresh();
|
|
RED.workspaces.refresh();
|
|
RED.sidebar.config.refresh();
|
|
}
|
|
function showTestFlowDiff(index) {
|
|
if (index === 1) {
|
|
var localFlow = RED.nodes.createCompleteNodeSet();
|
|
var originalFlow = RED.nodes.originalFlow();
|
|
showTextDiff(JSON.stringify(localFlow,null,4),JSON.stringify(originalFlow,null,4))
|
|
} else if (index === 2) {
|
|
var local = "1\n2\n3\n4\n5\nA\n6\n7\n8\n9\n";
|
|
var remote = "1\nA\n2\n3\nD\nE\n6\n7\n8\n9\n";
|
|
showTextDiff(local,remote);
|
|
} else if (index === 3) {
|
|
var local = "1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n14\n15\n16\n17\n18\n19\n20\n21\n22";
|
|
var remote = "1\nTWO\nTHREE\nEXTRA\n4\n5\n6\n7\n8\n9\n10\n11\n12\nTHIRTEEN\n14\n15\n16\n17\n18\n19\n20\n21\n22";
|
|
showTextDiff(local,remote);
|
|
}
|
|
}
|
|
|
|
function showTextDiff(textA,textB) {
|
|
var trayOptions = {
|
|
title: "Compare Changes", //TODO: nls
|
|
width: Infinity,
|
|
overlay: true,
|
|
buttons: [
|
|
{
|
|
text: RED._("common.label.close"),
|
|
click: function() {
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
],
|
|
resize: function(dimensions) {
|
|
// trayWidth = dimensions.width;
|
|
},
|
|
open: function(tray) {
|
|
var trayBody = tray.find('.editor-tray-body');
|
|
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
|
|
|
|
var codeTable = $("<table>").appendTo(diffPanel);
|
|
$('<colgroup><col width="50"><col width="50%"><col width="50"><col width="50%"></colgroup>').appendTo(codeTable);
|
|
var codeBody = $('<tbody>').appendTo(codeTable);
|
|
var diffSummary = diffText(textA||"",textB||"");
|
|
var aIndex = 0;
|
|
var bIndex = 0;
|
|
var diffLength = Math.max(diffSummary.a.length, diffSummary.b.length);
|
|
|
|
var diffLines = [];
|
|
var diffBlocks = [];
|
|
var currentBlock;
|
|
var blockLength = 0;
|
|
var blockType = 0;
|
|
|
|
for (var i=0;i<diffLength;i++) {
|
|
var diffLine = diffSummary[i];
|
|
var Adiff = (aIndex < diffSummary.a.length)?diffSummary.a[aIndex]:{type:2,line:""};
|
|
var Bdiff = (bIndex < diffSummary.b.length)?diffSummary.b[bIndex]:{type:2,line:""};
|
|
if (Adiff.type === 0 && Bdiff.type !== 0) {
|
|
Adiff = {type:2,line:""};
|
|
bIndex++;
|
|
} else if (Bdiff.type === 0 && Adiff.type !== 0) {
|
|
Bdiff = {type:2,line:""};
|
|
aIndex++;
|
|
} else {
|
|
aIndex++;
|
|
bIndex++;
|
|
}
|
|
diffLines.push({
|
|
a: Adiff,
|
|
b: Bdiff
|
|
});
|
|
if (currentBlock === undefined) {
|
|
currentBlock = {start:i,end:i};
|
|
blockLength = 0;
|
|
blockType = (Adiff.type === 0 && Bdiff.type === 0)?0:1;
|
|
} else {
|
|
if (Adiff.type === 0 && Bdiff.type === 0) {
|
|
// Unchanged line
|
|
if (blockType === 0) {
|
|
// still unchanged - extend the block
|
|
currentBlock.end = i;
|
|
blockLength++;
|
|
} else if (blockType === 1) {
|
|
// end of a change
|
|
currentBlock.end = i;
|
|
blockType = 2;
|
|
blockLength = 0;
|
|
} else if (blockType === 2) {
|
|
// post-change unchanged
|
|
currentBlock.end = i;
|
|
blockLength++;
|
|
if (blockLength === 8) {
|
|
currentBlock.end -= 5; // rollback the end
|
|
diffBlocks.push(currentBlock);
|
|
currentBlock = {start:i-5,end:i-5};
|
|
blockType = 0;
|
|
blockLength = 0;
|
|
}
|
|
}
|
|
} else {
|
|
// in a change
|
|
currentBlock.end = i;
|
|
blockLength++;
|
|
if (blockType === 0) {
|
|
if (currentBlock.end > 3) {
|
|
currentBlock.end -= 3;
|
|
currentBlock.empty = true;
|
|
diffBlocks.push(currentBlock);
|
|
currentBlock = {start:i-3,end:i-3};
|
|
}
|
|
blockType = 1;
|
|
} else if (blockType === 2) {
|
|
// we were in unchanged, but hit a change again
|
|
blockType = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (blockType === 0) {
|
|
currentBlock.empty = true;
|
|
}
|
|
currentBlock.end = diffLength;
|
|
diffBlocks.push(currentBlock);
|
|
// console.table(diffBlocks);
|
|
var diffRow;
|
|
for (var b = 0; b<diffBlocks.length; b++) {
|
|
currentBlock = diffBlocks[b];
|
|
if (currentBlock.empty) {
|
|
diffRow = createExpandLine(currentBlock.start,currentBlock.end,diffLines).appendTo(codeBody);
|
|
} else {
|
|
for (var i=currentBlock.start;i<currentBlock.end;i++) {
|
|
var row = createDiffLine(diffLines[i]).appendTo(codeBody);
|
|
if (i === currentBlock.start) {
|
|
row.addClass("start-block");
|
|
} else if (i === currentBlock.end-1) {
|
|
row.addClass("end-block");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
},
|
|
close: function() {
|
|
diffVisible = false;
|
|
|
|
},
|
|
show: function() {
|
|
|
|
}
|
|
}
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
|
|
function createExpandLine(start,end,diffLines) {
|
|
diffRow = $('<tr class="node-text-diff-header node-text-diff-expand">');
|
|
var content = $('<td colspan="4"> <i class="fa fa-arrows-v"></i> </td>').appendTo(diffRow);
|
|
var label = $('<span></span>').appendTo(content);
|
|
if (end < diffLines.length-1) {
|
|
label.text("@@ -"+(diffLines[end-1].a.i+1)+" +"+(diffLines[end-1].b.i+1));
|
|
}
|
|
diffRow.click(function(evt) {
|
|
// console.log(start,end,diffLines.length);
|
|
if (end - start > 20) {
|
|
var startPos = $(this).offset();
|
|
// console.log(startPos);
|
|
if (start > 0) {
|
|
for (var i=start;i<start+10;i++) {
|
|
createDiffLine(diffLines[i]).addClass("unchanged").insertBefore($(this));
|
|
}
|
|
start += 10;
|
|
}
|
|
if (end < diffLines.length-1) {
|
|
for (var i=end-1;i>end-11;i--) {
|
|
createDiffLine(diffLines[i]).addClass("unchanged").insertAfter($(this));
|
|
}
|
|
end -= 10;
|
|
}
|
|
if (end < diffLines.length-1) {
|
|
label.text("@@ -"+(diffLines[end-1].a.i+1)+" +"+(diffLines[end-1].b.i+1));
|
|
}
|
|
var endPos = $(this).offset();
|
|
var delta = endPos.top - startPos.top;
|
|
$(".node-text-diff").scrollTop($(".node-text-diff").scrollTop() + delta);
|
|
} else {
|
|
for (var i=start;i<end;i++) {
|
|
createDiffLine(diffLines[i]).addClass("unchanged").insertBefore($(this));
|
|
}
|
|
$(this).remove();
|
|
}
|
|
});
|
|
return diffRow;
|
|
}
|
|
|
|
function createDiffLine(diffLine) {
|
|
var diffRow = $('<tr>');
|
|
var Adiff = diffLine.a;
|
|
var Bdiff = diffLine.b;
|
|
//console.log(diffLine);
|
|
var cellNo = $('<td class="lineno">').text(Adiff.type === 2?"":Adiff.i).appendTo(diffRow);
|
|
var cellLine = $('<td class="linetext">').text(Adiff.line).appendTo(diffRow);
|
|
if (Adiff.type === 2) {
|
|
cellNo.addClass('blank');
|
|
cellLine.addClass('blank');
|
|
} else if (Adiff.type === 4) {
|
|
cellNo.addClass('added');
|
|
cellLine.addClass('added');
|
|
} else if (Adiff.type === 1) {
|
|
cellNo.addClass('removed');
|
|
cellLine.addClass('removed');
|
|
}
|
|
cellNo = $('<td class="lineno">').text(Bdiff.type === 2?"":Bdiff.i).appendTo(diffRow);
|
|
cellLine = $('<td class="linetext">').text(Bdiff.line).appendTo(diffRow);
|
|
if (Bdiff.type === 2) {
|
|
cellNo.addClass('blank');
|
|
cellLine.addClass('blank');
|
|
} else if (Bdiff.type === 4) {
|
|
cellNo.addClass('added');
|
|
cellLine.addClass('added');
|
|
} else if (Bdiff.type === 1) {
|
|
cellNo.addClass('removed');
|
|
cellLine.addClass('removed');
|
|
}
|
|
return diffRow;
|
|
}
|
|
|
|
function diffText(string1, string2,ignoreWhitespace) {
|
|
var lines1 = string1.split(/\r?\n/);
|
|
var lines2 = string2.split(/\r?\n/);
|
|
var i = lines1.length;
|
|
var j = lines2.length;
|
|
var k;
|
|
var m;
|
|
var diffSummary = {a:[],b:[]};
|
|
var diffMap = [];
|
|
for (k = 0; k < i + 1; k++) {
|
|
diffMap[k] = [];
|
|
for (m = 0; m < j + 1; m++) {
|
|
diffMap[k][m] = 0;
|
|
}
|
|
}
|
|
var c = 0;
|
|
for (k = i - 1; k >= 0; k--) {
|
|
for (m = j - 1; m >=0; m--) {
|
|
c++;
|
|
if (compareLines(lines1[k],lines2[m],ignoreWhitespace) !== 1) {
|
|
diffMap[k][m] = diffMap[k+1][m+1]+1;
|
|
} else {
|
|
diffMap[k][m] = Math.max(diffMap[(k + 1)][m], diffMap[k][(m + 1)]);
|
|
}
|
|
}
|
|
}
|
|
//console.log(c);
|
|
k = 0;
|
|
m = 0;
|
|
|
|
while ((k < i) && (m < j)) {
|
|
var n = compareLines(lines1[k],lines2[m],ignoreWhitespace);
|
|
if (n !== 1) {
|
|
var d = 0;
|
|
if (n===0) {
|
|
d = 0;
|
|
} else if (n==2) {
|
|
d = 3;
|
|
}
|
|
diffSummary.a.push({i:k+1,j:m+1,line:lines1[k],type:d});
|
|
diffSummary.b.push({i:m+1,j:k+1,line:lines2[m],type:d});
|
|
k++;
|
|
m++;
|
|
} else if (diffMap[(k + 1)][m] >= diffMap[k][(m + 1)]) {
|
|
diffSummary.a.push({i:k+1,line:lines1[k],type:1});
|
|
k++;
|
|
} else {
|
|
diffSummary.b.push({i:m+1,line:lines2[m],type:4});
|
|
m++;
|
|
}
|
|
}
|
|
while ((k < i) || (m < j)) {
|
|
if (k == i) {
|
|
diffSummary.b.push({i:m+1,line:lines2[m],type:4});
|
|
m++;
|
|
} else if (m == j) {
|
|
diffSummary.a.push({i:k+1,line:lines1[k],type:1});
|
|
k++;
|
|
}
|
|
}
|
|
return diffSummary;
|
|
}
|
|
|
|
function compareLines(string1, string2, ignoreWhitespace) {
|
|
if (ignoreWhitespace) {
|
|
if (string1 === string2) {
|
|
return 0;
|
|
}
|
|
return string1.trim() === string2.trime() ? 2 : 1;
|
|
}
|
|
return string1 === string2 ? 0 : 1;
|
|
}
|
|
|
|
function createUnifiedDiffTable(files,commitOptions) {
|
|
var diffPanel = $('<div></div>');
|
|
files.forEach(function(file) {
|
|
var hunks = file.hunks;
|
|
var isBinary = file.binary;
|
|
var codeTable = $("<table>").appendTo(diffPanel);
|
|
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
|
|
var codeBody = $('<tbody>').appendTo(codeTable);
|
|
|
|
var diffFileRow = $('<tr class="node-text-diff-file-header">').appendTo(codeBody);
|
|
var content = $('<td colspan="3"></td>').appendTo(diffFileRow);
|
|
|
|
var chevron = $('<i class="node-diff-chevron fa fa-angle-down"></i>').appendTo(content);
|
|
diffFileRow.click(function(e) {
|
|
diffFileRow.toggleClass("collapsed");
|
|
var isCollapsed = diffFileRow.hasClass("collapsed");
|
|
diffFileRow.nextUntil(".node-text-diff-file-header").toggle(!isCollapsed);
|
|
})
|
|
var label = $('<span class="filename"></span>').text(file.file).appendTo(content);
|
|
|
|
var conflictHeader;
|
|
var unresolvedConflicts = 0;
|
|
var resolvedConflicts = 0;
|
|
var conflictResolutions = {};
|
|
|
|
if (!commitOptions.unmerged && commitOptions.project.files && commitOptions.project.files.flow === file.file) {
|
|
var tools = $('<span style="float: right;" class="button-group"></span>').appendTo(content);
|
|
$('<button class="editor-button editor-button-small">show flow diff</button>').appendTo(tools).click(function(e) {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
var projectName = commitOptions.project.name;
|
|
var filename = commitOptions.project.files.flow;
|
|
var oldVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.oldRev+"/"+filename;
|
|
var newVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.newRev+"/"+filename;
|
|
$.when($.getJSON(oldVersionUrl),$.getJSON(newVersionUrl)).done(function(oldVersion,newVersion) {
|
|
var oldFlow;
|
|
var newFlow;
|
|
try {
|
|
oldFlow = JSON.parse(oldVersion[0].content||"[]");
|
|
} catch(err) {
|
|
console.log("Old Version doesn't contain valid JSON:",oldVersionUrl);
|
|
console.log(err);
|
|
return;
|
|
}
|
|
try {
|
|
newFlow = JSON.parse(newVersion[0].content||"[]");
|
|
} catch(err) {
|
|
console.log("New Version doesn't contain valid JSON:",newFlow);
|
|
console.log(err);
|
|
return;
|
|
}
|
|
var localDiff = generateDiff(oldFlow,oldFlow);
|
|
var remoteDiff = generateDiff(oldFlow,newFlow);
|
|
var diff = resolveDiffs(localDiff,remoteDiff);
|
|
showDiff(diff,{
|
|
title: filename,
|
|
mode: 'view',
|
|
oldRevTitle: commitOptions.oldRevTitle,
|
|
newRevTitle: commitOptions.newRevTitle
|
|
});
|
|
// var flowDiffRow = $("<tr>").insertAfter(diffRow);
|
|
// var content = $('<td colspan="3"></td>').appendTo(flowDiffRow);
|
|
// currentDiff = diff;
|
|
// var diffTable = buildDiffPanel(content,diff,{mode:"view"}).finish();
|
|
});
|
|
})
|
|
}
|
|
|
|
if (isBinary) {
|
|
var diffBinaryRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
|
|
var binaryContent = $('<td colspan="3"></td>').appendTo(diffBinaryRow);
|
|
$('<span></span>').text("Cannot show binary file contents").appendTo(binaryContent);
|
|
|
|
} else {
|
|
hunks.forEach(function(hunk) {
|
|
var diffRow = $('<tr class="node-text-diff-header">').appendTo(codeBody);
|
|
var content = $('<td colspan="3"></td>').appendTo(diffRow);
|
|
var label = $('<span></span>').text(hunk.header).appendTo(content);
|
|
var isConflict = hunk.conflict;
|
|
var localLine = hunk.localStartLine;
|
|
var remoteLine = hunk.remoteStartLine;
|
|
if (isConflict) {
|
|
unresolvedConflicts++;
|
|
}
|
|
|
|
hunk.lines.forEach(function(lineText,lineNumber) {
|
|
// if (lineText[0] === '\\' || lineText === "") {
|
|
// // Comment line - bail out of this hunk
|
|
// break;
|
|
// }
|
|
|
|
var actualLineNumber = hunk.diffStart + lineNumber;
|
|
var isMergeHeader = isConflict && /^..(<<<<<<<|=======$|>>>>>>>)/.test(lineText);
|
|
var diffRow = $('<tr>').appendTo(codeBody);
|
|
var localLineNo = $('<td class="lineno">').appendTo(diffRow);
|
|
var remoteLineNo;
|
|
if (!isMergeHeader) {
|
|
remoteLineNo = $('<td class="lineno">').appendTo(diffRow);
|
|
} else {
|
|
localLineNo.attr('colspan',2);
|
|
}
|
|
var line = $('<td class="linetext">').appendTo(diffRow);
|
|
var prefixStart = 0;
|
|
var prefixEnd = 1;
|
|
if (isConflict) {
|
|
prefixEnd = 2;
|
|
}
|
|
if (!isMergeHeader) {
|
|
var changeMarker = lineText[0];
|
|
if (isConflict && !commitOptions.unmerged && changeMarker === ' ') {
|
|
changeMarker = lineText[1];
|
|
}
|
|
$('<span class="prefix">').text(changeMarker).appendTo(line);
|
|
var handledlLine = false;
|
|
if (isConflict && commitOptions.unmerged) {
|
|
$('<span class="prefix">').text(lineText[1]).appendTo(line);
|
|
if (lineText[0] === '+') {
|
|
localLineNo.text(localLine++);
|
|
handledlLine = true;
|
|
}
|
|
if (lineText[1] === '+') {
|
|
remoteLineNo.text(remoteLine++);
|
|
handledlLine = true;
|
|
}
|
|
} else {
|
|
if (lineText[0] === '+' || (isConflict && lineText[1] === '+')) {
|
|
localLineNo.addClass("added");
|
|
remoteLineNo.addClass("added");
|
|
line.addClass("added");
|
|
remoteLineNo.text(remoteLine++);
|
|
handledlLine = true;
|
|
} else if (lineText[0] === '-' || (isConflict && lineText[1] === '-')) {
|
|
localLineNo.addClass("removed");
|
|
remoteLineNo.addClass("removed");
|
|
line.addClass("removed");
|
|
localLineNo.text(localLine++);
|
|
handledlLine = true;
|
|
}
|
|
}
|
|
if (!handledlLine) {
|
|
line.addClass("unchanged");
|
|
if (localLine > 0 && lineText[0] !== '\\' && lineText !== "") {
|
|
localLineNo.text(localLine++);
|
|
}
|
|
if (remoteLine > 0 && lineText[0] !== '\\' && lineText !== "") {
|
|
remoteLineNo.text(remoteLine++);
|
|
}
|
|
}
|
|
$('<span>').text(lineText.substring(prefixEnd)).appendTo(line);
|
|
} else {
|
|
diffRow.addClass("mergeHeader");
|
|
var isSeparator = /^..(=======$)/.test(lineText);
|
|
if (!isSeparator) {
|
|
var isOurs = /^..<<<<<<</.test(lineText);
|
|
if (isOurs) {
|
|
$('<span>').text("<<<<<<< Local Changes").appendTo(line);
|
|
hunk.localChangeStart = actualLineNumber;
|
|
} else {
|
|
hunk.remoteChangeEnd = actualLineNumber;
|
|
$('<span>').text(">>>>>>> Remote Changes").appendTo(line);
|
|
|
|
}
|
|
diffRow.addClass("mergeHeader-"+(isOurs?"ours":"theirs"));
|
|
$('<button class="editor-button editor-button-small" style="float: right; margin-right: 20px;"><i class="fa fa-angle-double-'+(isOurs?"down":"up")+'"></i> use '+(isOurs?"local":"remote")+' changes</button>')
|
|
.appendTo(line)
|
|
.click(function(evt) {
|
|
evt.preventDefault();
|
|
resolvedConflicts++;
|
|
var addedRows;
|
|
var midRow;
|
|
if (isOurs) {
|
|
addedRows = diffRow.nextUntil(".mergeHeader-separator");
|
|
midRow = addedRows.last().next();
|
|
midRow.nextUntil(".mergeHeader").remove();
|
|
midRow.next().remove();
|
|
} else {
|
|
addedRows = diffRow.prevUntil(".mergeHeader-separator");
|
|
midRow = addedRows.last().prev();
|
|
midRow.prevUntil(".mergeHeader").remove();
|
|
midRow.prev().remove();
|
|
}
|
|
midRow.remove();
|
|
diffRow.remove();
|
|
addedRows.find(".linetext").addClass('added');
|
|
conflictHeader.empty();
|
|
$('<span><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(conflictHeader);
|
|
|
|
conflictResolutions[file.file] = conflictResolutions[file.file] || {};
|
|
conflictResolutions[file.file][hunk.localChangeStart] = {
|
|
changeStart: hunk.localChangeStart,
|
|
separator: hunk.changeSeparator,
|
|
changeEnd: hunk.remoteChangeEnd,
|
|
selection: isOurs?"A":"B"
|
|
}
|
|
if (commitOptions.resolveConflict) {
|
|
commitOptions.resolveConflict({
|
|
conflicts: unresolvedConflicts,
|
|
resolved: resolvedConflicts,
|
|
resolutions: conflictResolutions
|
|
});
|
|
}
|
|
})
|
|
} else {
|
|
hunk.changeSeparator = actualLineNumber;
|
|
diffRow.addClass("mergeHeader-separator");
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
if (commitOptions.unmerged) {
|
|
conflictHeader = $('<span style="float: right;"><span>'+resolvedConflicts+'</span> of <span>'+unresolvedConflicts+'</span> conflicts resolved</span>').appendTo(content);
|
|
}
|
|
});
|
|
return diffPanel;
|
|
}
|
|
|
|
function showCommitDiff(options) {
|
|
var commit = parseCommitDiff(options.commit);
|
|
var trayOptions = {
|
|
title: "View Commit Changes", //TODO: nls
|
|
width: Infinity,
|
|
overlay: true,
|
|
buttons: [
|
|
{
|
|
text: RED._("common.label.close"),
|
|
click: function() {
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
],
|
|
resize: function(dimensions) {
|
|
// trayWidth = dimensions.width;
|
|
},
|
|
open: function(tray) {
|
|
var trayBody = tray.find('.editor-tray-body');
|
|
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
|
|
|
|
var codeTable = $("<table>").appendTo(diffPanel);
|
|
$('<colgroup><col width="50"><col width="50"><col width="100%"></colgroup>').appendTo(codeTable);
|
|
var codeBody = $('<tbody>').appendTo(codeTable);
|
|
|
|
var diffRow = $('<tr class="node-text-diff-commit-header">').appendTo(codeBody);
|
|
var content = $('<td colspan="3"></td>').appendTo(diffRow);
|
|
|
|
$("<h3>").text(commit.title).appendTo(content);
|
|
$('<div class="commit-body"></div>').text(commit.comment).appendTo(content);
|
|
var summary = $('<div class="commit-summary"></div>').appendTo(content);
|
|
$('<div style="float: right">').text("Commit "+commit.sha).appendTo(summary);
|
|
$('<div>').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary);
|
|
|
|
if (commit.files) {
|
|
createUnifiedDiffTable(commit.files,options).appendTo(diffPanel);
|
|
}
|
|
|
|
|
|
},
|
|
close: function() {
|
|
diffVisible = false;
|
|
},
|
|
show: function() {
|
|
|
|
}
|
|
}
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
function showUnifiedDiff(options) {
|
|
var diff = options.diff;
|
|
var title = options.title;
|
|
var files = parseUnifiedDiff(diff);
|
|
|
|
var currentResolution;
|
|
if (options.unmerged) {
|
|
options.resolveConflict = function(results) {
|
|
currentResolution = results;
|
|
if (results.conflicts === results.resolved) {
|
|
$("#node-diff-view-resolve-diff").removeClass('disabled');
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
var trayOptions = {
|
|
title: title||"Compare Changes", //TODO: nls
|
|
width: Infinity,
|
|
overlay: true,
|
|
buttons: [
|
|
{
|
|
text: RED._((options.unmerged)?"common.label.cancel":"common.label.close"),
|
|
click: function() {
|
|
if (options.oncancel) {
|
|
options.oncancel();
|
|
}
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
],
|
|
resize: function(dimensions) {
|
|
// trayWidth = dimensions.width;
|
|
},
|
|
open: function(tray) {
|
|
var trayBody = tray.find('.editor-tray-body');
|
|
var diffPanel = $('<div class="node-text-diff"></div>').appendTo(trayBody);
|
|
createUnifiedDiffTable(files,options).appendTo(diffPanel);
|
|
},
|
|
close: function() {
|
|
diffVisible = false;
|
|
},
|
|
show: function() {
|
|
|
|
}
|
|
}
|
|
if (options.unmerged) {
|
|
trayOptions.buttons.push(
|
|
{
|
|
id: "node-diff-view-resolve-diff",
|
|
text: "Save conflict resolution",
|
|
class: "primary disabled",
|
|
click: function() {
|
|
if (!$("#node-diff-view-resolve-diff").hasClass('disabled')) {
|
|
if (options.onresolve) {
|
|
options.onresolve(currentResolution);
|
|
}
|
|
RED.tray.close();
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
RED.tray.show(trayOptions);
|
|
}
|
|
|
|
function parseCommitDiff(diff) {
|
|
var result = {};
|
|
var lines = diff.split("\n");
|
|
var comment = [];
|
|
for (var i=0;i<lines.length;i++) {
|
|
if (/^commit /.test(lines[i])) {
|
|
result.sha = lines[i].substring(7);
|
|
} else if (/^Author: /.test(lines[i])) {
|
|
result.author = lines[i].substring(8);
|
|
var m = /^(.*) <(.*)>$/.exec(result.author);
|
|
if (m) {
|
|
result.authorName = m[1];
|
|
result.authorEmail = m[2];
|
|
}
|
|
} else if (/^Date: /.test(lines[i])) {
|
|
result.date = lines[i].substring(8);
|
|
} else if (/^ /.test(lines[i])) {
|
|
if (!result.title) {
|
|
result.title = lines[i].substring(4);
|
|
} else {
|
|
if (lines[i].length !== 4 || comment.length > 0) {
|
|
comment.push(lines[i].substring(4));
|
|
}
|
|
}
|
|
} else if (/^diff /.test(lines[i])) {
|
|
result.files = parseUnifiedDiff(lines.slice(i));
|
|
break;
|
|
}
|
|
}
|
|
result.comment = comment.join("\n");
|
|
return result;
|
|
}
|
|
function parseUnifiedDiff(diff) {
|
|
var lines;
|
|
if (Array.isArray(diff)) {
|
|
lines = diff;
|
|
} else {
|
|
lines = diff.split("\n");
|
|
}
|
|
var diffHeader = /^diff --git a\/(.*) b\/(.*)$/;
|
|
var fileHeader = /^\+\+\+ b\/(.*)\t?/;
|
|
var binaryFile = /^Binary files /;
|
|
var hunkHeader = /^@@ -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @@ ?(.*)$/;
|
|
var conflictHunkHeader = /^@+ -((\d+)(,(\d+))?) -((\d+)(,(\d+))?) \+((\d+)(,(\d+))?) @+/;
|
|
var files = [];
|
|
var currentFile;
|
|
var hunks = [];
|
|
var currentHunk;
|
|
for (var i=0;i<lines.length;i++) {
|
|
var line = lines[i];
|
|
var diffLine = diffHeader.exec(line);
|
|
if (diffLine) {
|
|
if (currentHunk) {
|
|
currentFile.hunks.push(currentHunk);
|
|
files.push(currentFile);
|
|
}
|
|
currentHunk = null;
|
|
currentFile = {
|
|
file: diffLine[1],
|
|
hunks: []
|
|
}
|
|
} else if (binaryFile.test(line)) {
|
|
if (currentFile) {
|
|
currentFile.binary = true;
|
|
}
|
|
} else {
|
|
var fileLine = fileHeader.exec(line);
|
|
if (fileLine) {
|
|
currentFile.file = fileLine[1];
|
|
} else {
|
|
var hunkLine = hunkHeader.exec(line);
|
|
if (hunkLine) {
|
|
if (currentHunk) {
|
|
currentFile.hunks.push(currentHunk);
|
|
}
|
|
currentHunk = {
|
|
header: line,
|
|
localStartLine: hunkLine[2],
|
|
localLength: hunkLine[4]||1,
|
|
remoteStartLine: hunkLine[6],
|
|
remoteLength: hunkLine[8]||1,
|
|
lines: [],
|
|
conflict: false
|
|
}
|
|
continue;
|
|
}
|
|
hunkLine = conflictHunkHeader.exec(line);
|
|
if (hunkLine) {
|
|
if (currentHunk) {
|
|
currentFile.hunks.push(currentHunk);
|
|
}
|
|
currentHunk = {
|
|
header: line,
|
|
localStartLine: hunkLine[2],
|
|
localLength: hunkLine[4]||1,
|
|
remoteStartLine: hunkLine[6],
|
|
remoteLength: hunkLine[8]||1,
|
|
diffStart: parseInt(hunkLine[10]),
|
|
lines: [],
|
|
conflict: true
|
|
}
|
|
continue;
|
|
}
|
|
if (currentHunk) {
|
|
currentHunk.lines.push(line);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (currentHunk) {
|
|
currentFile.hunks.push(currentHunk);
|
|
}
|
|
files.push(currentFile);
|
|
return files;
|
|
}
|
|
|
|
return {
|
|
init: init,
|
|
getRemoteDiff: getRemoteDiff,
|
|
showRemoteDiff: showRemoteDiff,
|
|
showUnifiedDiff: showUnifiedDiff,
|
|
showCommitDiff: showCommitDiff,
|
|
mergeDiff: mergeDiff
|
|
}
|
|
})();
|