1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Three-way-diff

This commit is contained in:
Nick O'Leary 2016-12-20 19:42:38 +00:00
parent d3dfbc3034
commit 31a72b6562
7 changed files with 645 additions and 332 deletions

View File

@ -24,10 +24,6 @@ RED.nodes = (function() {
var workspacesOrder =[]; var workspacesOrder =[];
var subflows = {}; var subflows = {};
var loadedFlowVersion = null; var loadedFlowVersion = null;
var pending = {
deleted: {},
added: {}
};
var initialLoad; var initialLoad;
@ -35,12 +31,6 @@ RED.nodes = (function() {
function setDirty(d) { function setDirty(d) {
dirty = d; dirty = d;
if (!d) {
pending = {
deleted: {},
added: {}
};
}
RED.events.emit("nodes:change",{dirty:dirty}); RED.events.emit("nodes:change",{dirty:dirty});
} }
@ -191,8 +181,6 @@ RED.nodes = (function() {
} }
nodes.push(n); nodes.push(n);
} }
delete pending.deleted[n.id];
pending.added[n.id] = true;
RED.events.emit('nodes:add',n); RED.events.emit('nodes:add',n);
} }
function addLink(l) { function addLink(l) {
@ -258,12 +246,6 @@ RED.nodes = (function() {
if (node && node._def.onremove) { if (node && node._def.onremove) {
node._def.onremove.call(n); node._def.onremove.call(n);
} }
delete pending.added[id];
pending.deleted[id] = true;
removedNodes.forEach(function(node) {
delete pending.added[node.id];
pending.deleted[node.id] = true;
});
return {links:removedLinks,nodes:removedNodes}; return {links:removedLinks,nodes:removedNodes};
} }
@ -276,8 +258,6 @@ RED.nodes = (function() {
function addWorkspace(ws) { function addWorkspace(ws) {
workspaces[ws.id] = ws; workspaces[ws.id] = ws;
pending.added[ws.id] = true;
delete pending.deleted[ws.id];
ws._def = { ws._def = {
defaults: { defaults: {
label: {value:""} label: {value:""}
@ -315,8 +295,6 @@ RED.nodes = (function() {
var result = removeNode(removedNodes[n].id); var result = removeNode(removedNodes[n].id);
removedLinks = removedLinks.concat(result.links); removedLinks = removedLinks.concat(result.links);
} }
pending.deleted[id] = true;
delete pending.added[id]
return {nodes:removedNodes,links:removedLinks}; return {nodes:removedNodes,links:removedLinks};
} }
@ -346,8 +324,6 @@ RED.nodes = (function() {
outputs: sf.out.length outputs: sf.out.length
} }
subflows[sf.id] = sf; subflows[sf.id] = sf;
delete pending.deleted[sf.id];
pending.added[sf.id] = true;
RED.nodes.registerType("subflow:"+sf.id, { RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}}, defaults:{name:{value:""}},
info: sf.info, info: sf.info,
@ -371,8 +347,6 @@ RED.nodes = (function() {
} }
function removeSubflow(sf) { function removeSubflow(sf) {
delete subflows[sf.id]; delete subflows[sf.id];
delete pending.added[sf.id];
pending.deleted[sf.id] = true;
registry.removeNodeType("subflow:"+sf.id); registry.removeNodeType("subflow:"+sf.id);
} }
@ -1027,8 +1001,9 @@ RED.nodes = (function() {
for (var w1=0;w1<n.wires.length;w1++) { for (var w1=0;w1<n.wires.length;w1++) {
var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]]; var wires = (n.wires[w1] instanceof Array)?n.wires[w1]:[n.wires[w1]];
for (var w2=0;w2<wires.length;w2++) { for (var w2=0;w2<wires.length;w2++) {
if (wires[w2] in node_map) { var existingNode = node_map[wires[w2]] || getNode(wires[w2]);
var link = {source:n,sourcePort:w1,target:node_map[wires[w2]]}; if (existingNode) {
var link = {source:n,sourcePort:w1,target:existingNode};
addLink(link); addLink(link);
new_links.push(link); new_links.push(link);
} }
@ -1246,8 +1221,6 @@ RED.nodes = (function() {
import: importNodes, import: importNodes,
pending: function() { return pending },
getAllFlowNodes: getAllFlowNodes, getAllFlowNodes: getAllFlowNodes,
createExportableNodeSet: createExportableNodeSet, createExportableNodeSet: createExportableNodeSet,
createCompleteNodeSet: createCompleteNodeSet, createCompleteNodeSet: createCompleteNodeSet,

View File

@ -30,6 +30,8 @@ RED.deploy = (function() {
var deploymentType = "full"; var deploymentType = "full";
var currentDiff = null;
function changeDeploymentType(type) { function changeDeploymentType(type) {
deploymentType = type; deploymentType = type;
$("#btn-deploy-icon").attr("src",deploymentTypes[type].img); $("#btn-deploy-icon").attr("src",deploymentTypes[type].img);
@ -96,21 +98,31 @@ RED.deploy = (function() {
height: "auto", height: "auto",
buttons: [ buttons: [
{ {
text: RED._("deploy.confirm.button.cancel"), text: RED._("common.label.cancel"),
click: function() { click: function() {
$( this ).dialog( "close" ); $( this ).dialog( "close" );
} }
}, },
// {
// id: "node-dialog-confirm-deploy-review",
// text: RED._("deploy.confirm.button.review"),
// class: "primary",
// click: function() {
// showDiff();
// $( this ).dialog( "close" );
// }
// },
{ {
id: "node-dialog-confirm-deploy-review",
text: RED._("deploy.confirm.button.review"),
class: "primary disabled",
click: function() {
if (!$("#node-dialog-confirm-deploy-review").hasClass('disabled')) {
RED.diff.showRemoteDiff();
$( this ).dialog( "close" );
}
}
},
{
id: "node-dialog-confirm-deploy-merge",
text: RED._("deploy.confirm.button.merge"),
class: "primary disabled",
click: function() {
}
},
{
id: "node-dialog-confirm-deploy-deploy",
text: RED._("deploy.confirm.button.confirm"), text: RED._("deploy.confirm.button.confirm"),
class: "primary", class: "primary",
click: function() { click: function() {
@ -134,10 +146,37 @@ RED.deploy = (function() {
}, },
open: function() { open: function() {
if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") { if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") {
// $("#node-dialog-confirm-deploy-review").show(); $("#node-dialog-confirm-deploy-deploy").hide();
$("#node-dialog-confirm-deploy-review").addClass('disabled').show();
$("#node-dialog-confirm-deploy-merge").addClass('disabled').show();
currentDiff = null;
$("#node-dialog-confirm-deploy-conflict-checking").show();
$("#node-dialog-confirm-deploy-conflict-auto-merge").hide();
$("#node-dialog-confirm-deploy-conflict-manual-merge").hide();
var now = Date.now();
RED.diff.getRemoteDiff(function(diff) {
var ellapsed = Math.max(2000 - (Date.now()-now), 0);
currentDiff = diff;
setTimeout(function() {
$("#node-dialog-confirm-deploy-conflict-checking").hide();
var d = Object.keys(diff.conflicts);
if (d.length === 0) {
$("#node-dialog-confirm-deploy-conflict-auto-merge").show();
$("#node-dialog-confirm-deploy-merge").removeClass('disabled')
} else {
$("#node-dialog-confirm-deploy-conflict-manual-merge").show();
}
$("#node-dialog-confirm-deploy-review").removeClass('disabled')
},ellapsed);
})
$("#node-dialog-confirm-deploy-hide").parent().hide(); $("#node-dialog-confirm-deploy-hide").parent().hide();
} else { } else {
// $("#node-dialog-confirm-deploy-review").hide(); $("#node-dialog-confirm-deploy-deploy").show();
$("#node-dialog-confirm-deploy-review").hide();
$("#node-dialog-confirm-deploy-merge").hide();
$("#node-dialog-confirm-deploy-hide").parent().show(); $("#node-dialog-confirm-deploy-hide").parent().show();
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -306,50 +306,13 @@ RED.subflow = (function() {
$("#workspace-subflow-delete").click(function(event) { $("#workspace-subflow-delete").click(function(event) {
event.preventDefault(); event.preventDefault();
var removedNodes = [];
var removedLinks = [];
var startDirty = RED.nodes.dirty(); var startDirty = RED.nodes.dirty();
var historyEvent = removeSubflow(RED.workspaces.active());
historyEvent.t = 'delete';
historyEvent.dirty = startDirty;
var activeSubflow = getSubflow(); RED.history.push(historyEvent);
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+activeSubflow.id) {
removedNodes.push(n);
}
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
var removedConfigNodes = [];
for (var i=0;i<removedNodes.length;i++) {
var removedEntities = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(removedEntities.links);
removedConfigNodes = removedConfigNodes.concat(removedEntities.nodes);
}
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
RED.nodes.removeSubflow(activeSubflow);
RED.history.push({
t:'delete',
nodes:removedNodes,
links:removedLinks,
subflow: {
subflow: activeSubflow
},
dirty:startDirty
});
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
RED.view.redraw();
}); });
refreshToolbar(activeSubflow); refreshToolbar(activeSubflow);
@ -362,7 +325,48 @@ RED.subflow = (function() {
$("#chart").css({"margin-top": "0"}); $("#chart").css({"margin-top": "0"});
} }
function removeSubflow(id) {
var removedNodes = [];
var removedLinks = [];
var activeSubflow = RED.nodes.subflow(id);
RED.nodes.eachNode(function(n) {
if (n.type == "subflow:"+activeSubflow.id) {
removedNodes.push(n);
}
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
RED.nodes.eachConfig(function(n) {
if (n.z == activeSubflow.id) {
removedNodes.push(n);
}
});
var removedConfigNodes = [];
for (var i=0;i<removedNodes.length;i++) {
var removedEntities = RED.nodes.remove(removedNodes[i].id);
removedLinks = removedLinks.concat(removedEntities.links);
removedConfigNodes = removedConfigNodes.concat(removedEntities.nodes);
}
// TODO: this whole delete logic should be in RED.nodes.removeSubflow..
removedNodes = removedNodes.concat(removedConfigNodes);
RED.nodes.removeSubflow(activeSubflow);
RED.workspaces.remove(activeSubflow);
RED.nodes.dirty(true);
RED.view.redraw();
return {
nodes:removedNodes,
links:removedLinks,
subflow: {
subflow: activeSubflow
}
}
}
function init() { function init() {
RED.events.on("workspace:change",function(event) { RED.events.on("workspace:change",function(event) {
var activeSubflow = RED.nodes.subflow(event.workspace); var activeSubflow = RED.nodes.subflow(event.workspace);
@ -619,6 +623,7 @@ RED.subflow = (function() {
init: init, init: init,
createSubflow: createSubflow, createSubflow: createSubflow,
convertToSubflow: convertToSubflow, convertToSubflow: convertToSubflow,
removeSubflow: removeSubflow,
refresh: refresh, refresh: refresh,
removeInput: removeSubflowInput, removeInput: removeSubflowInput,
removeOutput: removeSubflowOutput removeOutput: removeSubflowOutput

View File

@ -125,7 +125,7 @@
//display: none; //display: none;
} }
.node-diff-node-entry-cell:first-child { .node-diff-node-entry-cell:first-child {
width: 100% //width: 100%
} }
} }
@ -170,7 +170,10 @@
} }
.node-diff-three-way { .node-diff-three-way {
.node-diff-node-entry-cell { .node-diff-node-entry-cell {
width: 33.3333333% width: calc((100% - 220px) / 2);
&:first-child {
width: 220px;
}
} }
td:not(:first-child) { td:not(:first-child) {
width: calc( (100% - 140px) / 2); width: calc( (100% - 140px) / 2);
@ -178,11 +181,11 @@
.node-diff-node-entry { .node-diff-node-entry {
.node-diff-node-entry-cell { .node-diff-node-entry-cell {
width: calc( ( 100% + 20px ) / 3 ); width: calc((100% + 20px - 220px) / 2);
&:first-child { &:first-child {
width: calc( ( 100% + 20px ) / 3 - 20px ); width: 200px;
} }
} }
} }
} }
@ -336,14 +339,15 @@
padding-top: 2px; padding-top: 2px;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
position: relative;
} }
.node-diff-empty { .node-diff-empty {
background: #f3f3f3; background: #f3f3f3;
background: repeating-linear-gradient( background: repeating-linear-gradient(
20deg, 20deg,
#fff, #fff 5px, #fff, #fff 5px,
#f9f9f9 5px, #f6f6f6 5px,
#f9f9f9 10px #f6f6f6 10px
); );
} }
.node-diff-node-entry-cell:first-child { .node-diff-node-entry-cell:first-child {
@ -410,10 +414,92 @@
//min-height: 30px; //min-height: 30px;
&.node-diff-node-changed { &.node-diff-node-changed {
background: #fff2e1; background: #fff2e1 !important;
} }
&.node-diff-node-conflict { &.node-diff-node-conflict {
background: #ffdad4; background: #ffdad4 !important;
}
}
.node-diff-selectbox {
position: absolute;
top:0;
right:0;
bottom:0;
width: 35px;
text-align: center;
border-left: 1px solid #eee;
margin:0;
input {
margin-top: 8px;
} }
&:hover {
background: #f3f3f3;
}
}
.node-diff-node-entry-conflict.node-diff-select-remote {
.node-diff-node-remote {
background: #e7ffe3;
label {
border-left-color: #b8daad;
}
}
.node-diff-node-local {
background: #ffe1e1;
label {
border-left-color: #e4bcbc;
}
}
}
.node-diff-node-entry-conflict.node-diff-select-local {
.node-diff-node-local {
background: #e7ffe3;
label {
border-left-color: #b8daad;
}
}
.node-diff-node-remote {
background: #ffe1e1;
label {
border-left-color: #e4bcbc;
}
}
}
#node-dialog-confirm-deploy {
.node-dialog-confirm-row {
text-align: left; padding-top: 10px;
}
ul {
font-size: 0.9em;
width: 400px;
margin: 10px auto;
text-align: left;
}
.node-dialog-confirm-conflict-row {
img {
vertical-align:middle;
height: 30px;
margin-right: 10px;
}
i {
vertical-align:middle;
text-align: center;
font-size: 30px;
width: 30px;
margin-right: 10px;
}
div {
vertical-align: middle;
width: calc(100% - 60px);
display:inline-block;
}
}
}
#node-diff-toolbar-resolved-conflicts .node-diff-status {
margin:0;
} }

View File

@ -87,13 +87,25 @@
<div id="node-dialog-confirm-deploy" class="hide"> <div id="node-dialog-confirm-deploy" class="hide">
<form class="form-horizontal"> <form class="form-horizontal">
<div id="node-dialog-confirm-deploy-config" style="text-align: left; padding-top: 30px;" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm"> <div id="node-dialog-confirm-deploy-config" class="node-dialog-confirm-row" data-i18n="[prepend]deploy.confirm.improperlyConfigured;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-invalid-list"></ul> <ul id="node-dialog-confirm-deploy-invalid-list"></ul>
</div> </div>
<div id="node-dialog-confirm-deploy-unknown" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm"> <div id="node-dialog-confirm-deploy-unknown" class="node-dialog-confirm-row" data-i18n="[prepend]deploy.confirm.unknown;[append]deploy.confirm.confirm">
<ul style="font-size: 0.9em; width: 400px; margin: 10px auto; text-align: left;" id="node-dialog-confirm-deploy-unknown-list"></ul> <ul id="node-dialog-confirm-deploy-unknown-list"></ul>
</div> </div>
<div id="node-dialog-confirm-deploy-conflict" style="text-align: left; padding-top: 10px;" data-i18n="[prepend]deploy.confirm.conflict;[append]deploy.confirm.confirm"> <div id="node-dialog-confirm-deploy-conflict" class="node-dialog-confirm-row">
<div style="margin-left: 40px; margin-bottom: 10px;">
<span data-i18n="deploy.confirm.conflict"></span>
</div>
<div id="node-dialog-confirm-deploy-conflict-checking" class="node-dialog-confirm-conflict-row">
<img src="red/images/spin.svg"/><div data-i18n="deploy.confirm.conflictChecking"></div>
</div>
<div id="node-dialog-confirm-deploy-conflict-auto-merge" class="node-dialog-confirm-conflict-row">
<i style="color: #3a3;" class="fa fa-check"></i><div data-i18n="deploy.confirm.conflictAutoMerge"></div>
</div>
<div id="node-dialog-confirm-deploy-conflict-manual-merge" class="node-dialog-confirm-conflict-row">
<i style="color: #999;" class="fa fa-exclamation"></i><div data-i18n="deploy.confirm.conflictManualMerge"></div>
</div>
</div> </div>
</form> </form>
</div> </div>

View File

@ -130,9 +130,16 @@
"improperlyConfigured": "The workspace contains some nodes that are not properly configured:", "improperlyConfigured": "The workspace contains some nodes that are not properly configured:",
"unknown": "The workspace contains some unknown node types:", "unknown": "The workspace contains some unknown node types:",
"confirm": "Are you sure you want to deploy?", "confirm": "Are you sure you want to deploy?",
"conflict": "The server is running a more recent set of flows." "conflict": "The server is running a more recent set of flows.",
"conflictChecking": "Checking to see if the changes can be merged automatically",
"conflictAutoMerge": "The changes include no conflicts and can be merged automatically.",
"conflictManualMerge": "The changes include conflicts that must be resolved before they can be deployed."
} }
}, },
"diff": {
"unresolvedCount": "__count__ unresolved conflict",
"unresolvedCount_plural": "__count__ unresolved conflicts"
},
"subflow": { "subflow": {
"editSubflow": "Edit flow template: __name__", "editSubflow": "Edit flow template: __name__",
"edit": "Edit flow template", "edit": "Edit flow template",