diff --git a/editor/js/ui/diff.js b/editor/js/ui/diff.js index 14a153c16..6ccd05790 100644 --- a/editor/js/ui/diff.js +++ b/editor/js/ui/diff.js @@ -2,11 +2,14 @@ RED.diff = (function() { function init() { - RED.actions.add("core:show-current-diff",showlocalDiff); + 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"); - var dialog = $('
    ').appendTo(document.body); + var dialog = $('
      ').appendTo(document.body); var toolbar = $('
      '+ ''+ @@ -60,128 +63,268 @@ RED.diff = (function() { 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 tabDiv = $('
      ',{class:"node-diff-tab"}).appendTo(container); var titleRow = $('
      ',{class:"node-diff-tab-title"}).appendTo(tabDiv); - if (localDiff.added[tab.id]) { - titleRow.addClass("node-diff-node-added"); - } else if (localDiff.deleted[tab.id]) { - titleRow.addClass("node-diff-node-deleted"); + var nodesDiv = $('
      ').appendTo(tabDiv); + var originalCell = $('
      ',{class:"node-diff-node-entry-cell"}).appendTo(titleRow); + var localCell = $('
      ',{class:"node-diff-node-entry-cell"}).appendTo(titleRow); + var remoteCell; + if (remoteDiff) { + remoteCell = $('
      ',{class:"node-diff-node-entry-cell"}).appendTo(titleRow); } - var status = $('').appendTo(titleRow); + // if (localDiff.added[tab.id]) { + // titleRow.addClass("node-diff-node-added"); + // } else if (localDiff.deleted[tab.id]) { + // titleRow.addClass("node-diff-node-deleted"); + // } + // var status = $('').appendTo(originalCell); - $('').appendTo(titleRow); - createNodeIcon(tab,def).appendTo(titleRow); + // if (!object.newTab && object.remoteTab) { + // $('').appendTo(remoteCell); + // //} else if (object.newTab && (remoteDiff && !object.remoteTab)) { + // } else if (localDiff.added[tab.id]) { + // $('').appendTo(localCell); + // } + $('').appendTo(originalCell); + createNodeIcon(tab,def).appendTo(originalCell); var tabForLabel = (object.newTab || object.tab).n; + var titleSpan = $('',{class:"node-diff-tab-title-meta"}).appendTo(originalCell); if (tabForLabel.type === 'tab') { - $('').html(tabForLabel.label||tabForLabel.id).appendTo(titleRow); + titleSpan.html(tabForLabel.label||tabForLabel.id); } else if (tab.type === 'subflow') { - $('').html((tabForLabel.name||tabForLabel.id)).appendTo(titleRow); + titleSpan.html((tabForLabel.name||tabForLabel.id)); } else { - $('').html("Global configuration nodes").appendTo(titleRow); + titleSpan.html("Global configuration nodes"); } + 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 = $("
      ",{class:"node-diff-node-entry node-diff-node-props collapsed"}).appendTo(nodesDiv); + var row = $("
      ",{class:"node-diff-node-entry-header"}).appendTo(div); + var originalNodeDiv = $("
      ",{class:"node-diff-node-entry-cell"}).appendTo(row); + var localNodeDiv = $("
      ",{class:"node-diff-node-entry-cell"}).appendTo(row); + var localChanged = false; + var remoteChanged = false; - if (object.newTab) { - if (localDiff.changed[tab.id]) { - titleRow.addClass("node-diff-node-changed"); - var propTab = $('
      ',{class:"node-diff-node-entry node-diff-node-props"}).appendTo(tabDiv); - var props = createNodePropertiesTable(tab,object.newTab.n,def).appendTo(propTab); + if (!localDiff.newConfig.all[tab.id]) { + localNodeDiv.addClass("node-diff-empty"); + } else if (localDiff.added[tab.id]) { + localNodeDiv.addClass("node-diff-node-added"); + $(' added').appendTo(localNodeDiv); + } else if (localDiff.changed[tab.id]) { + localNodeDiv.addClass("node-diff-node-changed"); + $(' changed').appendTo(localNodeDiv); + } else { + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' unchanged').appendTo(localNodeDiv); + } + + + var remoteNodeDiv; + if (remoteDiff) { + remoteNodeDiv = $("
      ",{class:"node-diff-node-entry-cell"}).appendTo(row); + if (!remoteDiff.newConfig.all[tab.id]) { + remoteNodeDiv.addClass("node-diff-empty"); + } else if (remoteDiff.added[tab.id]) { + remoteNodeDiv.addClass("node-diff-node-added"); + $(' added').appendTo(remoteNodeDiv); + } else if (remoteDiff.changed[tab.id]) { + remoteNodeDiv.addClass("node-diff-node-changed"); + $(' changed').appendTo(remoteNodeDiv); + } else { + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' unchanged').appendTo(remoteNodeDiv); + } + } + $('').appendTo(originalNodeDiv); + $('').html("Flow Properties").appendTo(originalNodeDiv); + row.click(function(evt) { + evt.preventDefault(); + $(this).parent().toggleClass('collapsed'); + }); + createNodePropertiesTable(def,tab,localTabNode,remoteTabNode,flowStats).appendTo(div); } } - var stats = $('',{class:"node-diff-tab-stats"}).appendTo(titleRow); + // var stats = $('',{class:"node-diff-tab-stats"}).appendTo(titleRow); + - var flowStats = { - addedCount:0, - deletedCount:0, - changedCount:0, - conflictedCount:0 - } var seen = {}; object.tab.nodes.forEach(function(node) { seen[node.id] = true; - createNodeDiffRow(node,flowStats,localDiff).appendTo(tabDiv) + createNodeDiffRow(node,flowStats,localDiff,remoteDiff).appendTo(nodesDiv) }); if (object.newTab) { object.newTab.nodes.forEach(function(node) { if (!seen[node.id]) { - createNodeDiffRow(node,flowStats,localDiff).appendTo(tabDiv) + seen[node.id] = true; + createNodeDiffRow(node,flowStats,localDiff,remoteDiff).appendTo(nodesDiv) + } + }); + } + if (object.remoteTab) { + object.remoteTab.nodes.forEach(function(node) { + if (!seen[node.id]) { + createNodeDiffRow(node,flowStats,localDiff,remoteDiff).appendTo(nodesDiv) } }); } titleRow.click(function(evt) { evt.preventDefault(); - if (titleRow.parent().find(".node-diff-node-entry:not(.hide)").length > 0) { - titleRow.parent().toggleClass('collapsed'); + // 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'); } + // } }) - var changesCount = flowStats.addedCount+flowStats.deletedCount+flowStats.changedCount+flowStats.conflictedCount; - var tabModified = localDiff.added[tab.id] || localDiff.deleted[tab.id] || localDiff.changed[tab.id]; - if (changesCount === 0) { - tabDiv.addClass("collapsed"); - if (!tabModified) { - tabDiv.parent().addClass("hide"); - tabDiv.addClass("node-diff-tab-unchanged"); - } - } + var localChangesCount = flowStats.local.addedCount+flowStats.local.deletedCount+flowStats.local.changedCount; + var remoteChangesCount = flowStats.remote.addedCount+flowStats.remote.deletedCount+flowStats.remote.changedCount; + if (localDiff.deleted[tab.id]) { - $('').appendTo(status); - } else if (localDiff.added[tab.id]) { - $('').appendTo(status); - } else if (localDiff.changed[tab.id]) { - $('').appendTo(status); + $(' flow deleted').appendTo(localCell); + } else if (object.newTab) { + if (localDiff.added[tab.id]) { + $(' flow added').appendTo(localCell); + } else { + if (tab.id) { + if (localDiff.changed[tab.id]) { + localChangesCount++; + flowStats.local.changedCount++; + } else { + flowStats.local.unchangedCount++; + } + } + var localStats = $('',{class:"node-diff-tab-stats"}).appendTo(localCell); + if (flowStats.conflicts > 0) { + $(' '+flowStats.conflicts+'').appendTo(localStats); + } + if (flowStats.local.unchangedCount > 0) { + $(' '+flowStats.local.unchangedCount+'').appendTo(localStats); + } + if (flowStats.local.addedCount > 0) { + $(' '+flowStats.local.addedCount+'').appendTo(localStats); + } + if (flowStats.local.changedCount > 0) { + $(' '+flowStats.local.changedCount+'').appendTo(localStats); + } + if (flowStats.local.deletedCount > 0) { + $(' '+flowStats.local.deletedCount+'').appendTo(localStats); + } + } + } else { + localCell.addClass("node-diff-empty"); + } + + if (remoteDiff) { + if (remoteDiff.deleted[tab.id]) { + $(' flow deleted').appendTo(remoteCell); + } else if (object.remoteTab) { + if (remoteDiff.added[tab.id]) { + $(' flow added').appendTo(remoteCell); + } else { + if (tab.id) { + if (remoteDiff.changed[tab.id]) { + remoteChangesCount++; + flowStats.remote.changedCount++; + } else { + flowStats.remote.unchangedCount++; + } + } + var remoteStats = $('',{class:"node-diff-tab-stats"}).appendTo(remoteCell); + if (flowStats.conflicts > 0) { + $(' '+flowStats.conflicts+'').appendTo(remoteStats); + } + if (flowStats.remote.unchangedCount > 0) { + $(' '+flowStats.remote.unchangedCount+'').appendTo(remoteStats); + } + if (flowStats.remote.addedCount > 0) { + $(' '+flowStats.remote.addedCount+'').appendTo(remoteStats); + } + if (flowStats.remote.changedCount > 0) { + $(' '+flowStats.remote.changedCount+'').appendTo(remoteStats); + } + if (flowStats.remote.deletedCount > 0) { + $(' '+flowStats.remote.deletedCount+'').appendTo(remoteStats); + } + } + } else { + remoteCell.addClass("node-diff-empty"); + } } if (tabDiv.find(".node-diff-node-entry").length === 0) { tabDiv.addClass("node-diff-tab-empty"); } - - var statsInfo = ((flowStats.addedCount > 0)?''+flowStats.addedCount+' added ':'')+ - ((flowStats.deletedCount > 0)?''+flowStats.deletedCount+' deleted ':'')+ - ((flowStats.changedCount > 0)?''+flowStats.changedCount+' changed ':'')+ - ((flowStats.conflictedCount > 0)?''+flowStats.conflictedCount+' conflicts':''); - stats.html(statsInfo); - - - - // - // - // - // var node = object.node; - // var realNode = RED.nodes.node(node.id); - // var def = RED.nodes.getType(object.node.type)||{}; - // var l = ""; - // if (def && def.label && realNode) { - // l = def.label; - // try { - // l = (typeof l === "function" ? l.call(realNode) : l); - // } catch(err) { - // console.log("Definition error: "+node.type+".label",err); - // } - // } - // l = l||node.label||node.name||node.id||""; - // console.log(node); - // var div = $('
      ').appendTo(container); - // div.html(l); + // var statsInfo = ((flowStats.addedCount > 0)?''+flowStats.addedCount+' added ':'')+ + // ((flowStats.deletedCount > 0)?''+flowStats.deletedCount+' deleted ':'')+ + // ((flowStats.changedCount > 0)?''+flowStats.changedCount+' changed ':''); + // stats.html(statsInfo); } }); } - function formatWireProperty(wires) { - var result = $("
        "); + function formatWireProperty(wires,allNodes) { + var result = $("
        ",{class:"node-diff-property-wires"}) + var list = $("
          "); + var c = 0; wires.forEach(function(p,i) { - var port = $("
        1. ").appendTo(result); + var port = $("
        2. ").appendTo(list); if (p && p.length > 0) { + $("").html(i+1).appendTo(port); var links = $("
            ").appendTo(port); p.forEach(function(d) { - var entry = $("
          • ").text(d).appendTo(links); + c++; + var entry = $("
          • ").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) { @@ -205,119 +348,434 @@ RED.diff = (function() { return nodeDiv; } - function createNodeDiffRow(node,stats,localDiff) { - var realNode = RED.nodes.node(node.id); - var hasChanges = false; + function createNode(node,def) { + var nodeTitleDiv = $("
            ",{class:"node-diff-node-entry-title"}) + createNodeIcon(node,def).appendTo(nodeTitleDiv); + var contentDiv = $('
            ',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv); + var nodeLabel = node.label || node.name || node.id; + $('',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv); + return nodeTitleDiv; + } + function createNodeDiffRow(node,stats,localDiff,remoteDiff) { + var hasChanges = false; // exists in original and local/remote but with changes + var unChanged = true; // existing in original,local,remote unchanged + var conflicted = false; + if (localDiff.added[node.id]) { - stats.addedCount++; + stats.local.addedCount++; + unChanged = false; + } + if (remoteDiff && remoteDiff.added[node.id]) { + stats.remote.addedCount++; + unChanged = false; } if (localDiff.deleted[node.id]) { - stats.deletedCount++; + stats.local.deletedCount++; + unChanged = false; + } + if (remoteDiff && remoteDiff.deleted[node.id]) { + stats.remote.deletedCount++; + unChanged = false; } if (localDiff.changed[node.id]) { - stats.changedCount++; + stats.local.changedCount++; hasChanges = true; + unChanged = false; + } + if (remoteDiff && remoteDiff.changed[node.id]) { + stats.remote.changedCount++; + hasChanges = true; + unChanged = false; } - var def = RED.nodes.getType(node.type)||{}; - var div = $("
            ",{class:"node-diff-node-entry collapsed"}); - var nodeTitleDiv = $("
            ",{class:"node-diff-node-entry-title"}).appendTo(div); - var status = $('').appendTo(nodeTitleDiv); - var nodeLabel = node.label || node.name || node.id; - - if (hasChanges) { - nodeTitleDiv.addClass("node-diff-node-changed"); - $('').appendTo(status); - var newNode = localDiff.newConfig.all[node.id]; - if (newNode) { - nodeLabel = newNode.label || newNode.name || newNode.id; - nodeTitleDiv.click(function(evt) { - evt.preventDefault(); - $(this).parent().toggleClass('collapsed'); - }) - createNodePropertiesTable(node,newNode,def).appendTo(div); - $('').appendTo(nodeTitleDiv); + 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 = {}; } - } else if (localDiff.deleted[node.id]){ - $('').appendTo(nodeTitleDiv); - nodeTitleDiv.addClass("node-diff-node-deleted"); - $('').appendTo(status); - } else if (localDiff.added[node.id]) { - $('').appendTo(nodeTitleDiv); - nodeTitleDiv.addClass("node-diff-node-added") - $('').appendTo(status); - } else { - $('').appendTo(nodeTitleDiv); - nodeTitleDiv.addClass("node-diff-node-unchanged"); - div.addClass("hide"); } + var div = $("
            ",{class:"node-diff-node-entry collapsed"}); + var row = $("
            ").appendTo(div); - createNodeIcon(node,def).appendTo(nodeTitleDiv); + var originalNodeDiv = $("
            ",{class:"node-diff-node-entry-cell"}).appendTo(row); + var localNodeDiv = $("
            ",{class:"node-diff-node-entry-cell"}).appendTo(row); + var remoteNodeDiv; + var chevron; + if (remoteDiff) { + remoteNodeDiv = $("
            ",{class:"node-diff-node-entry-cell"}).appendTo(row); + } + $('').appendTo(originalNodeDiv); - var contentDiv = $('
            ',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv); + if (unChanged) { + stats.local.unchangedCount++; + // $('').appendTo(originalNodeDiv); + //$('').appendTo(originalNodeDiv); + createNode(node,def).appendTo(originalNodeDiv); + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' unchanged').appendTo(localNodeDiv); + if (remoteDiff) { + stats.remote.unchangedCount++; + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' unchanged').appendTo(remoteNodeDiv); + } - $('',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv); - $('',{class:"node-diff-node-meta"}).html(node.id).appendTo(nodeTitleDiv); + //$('').appendTo(localNodeDiv); + // createNode(node,def).appendTo(localNodeDiv); + // if (remoteDiff) { + // //$('').appendTo(remoteNodeDiv); + // createNode(node,def).appendTo(remoteNodeDiv); + // } + } else if (localDiff.added[node.id]) { + // $('').appendTo(originalNodeDiv); + localNodeDiv.addClass("node-diff-node-added"); + if (remoteNodeDiv) { + remoteNodeDiv.addClass("node-diff-empty"); + } + $(' added').appendTo(localNodeDiv); + createNode(node,def).appendTo(originalNodeDiv); + } else if (remoteDiff && remoteDiff.added[node.id]) { + // $('').appendTo(originalNodeDiv); + localNodeDiv.addClass("node-diff-empty"); + remoteNodeDiv.addClass("node-diff-node-added"); + $(' added').appendTo(remoteNodeDiv); + createNode(node,def).appendTo(originalNodeDiv); + } else { + // chevron = $('').appendTo(originalNodeDiv); + // if (localDiff.changed[node.id] || (remoteDiff && remoteDiff.changed[node.id])) { + // $('').appendTo(chevron); + // } + //$('').appendTo(originalNodeDiv); + createNode(node,def).appendTo(originalNodeDiv); + if (localDiff.deleted[node.z]) { + localNodeDiv.addClass("node-diff-empty"); + } else if (localDiff.deleted[node.id]) { + localNodeDiv.addClass("node-diff-node-deleted"); + $(' deleted').appendTo(localNodeDiv); + } else if (localDiff.changed[node.id]) { + localNodeDiv.addClass("node-diff-node-changed"); + $(' changed').appendTo(localNodeDiv); + } else { + stats.local.unchangedCount++; + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' unchanged').appendTo(localNodeDiv); + } + // createNode(node,def).appendTo(localNodeDiv); - //$('
            ',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv); - //$('
            ',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv); + if (remoteDiff) { + if (remoteDiff.deleted[node.z]) { + remoteNodeDiv.addClass("node-diff-empty"); + } else if (remoteDiff.deleted[node.id]) { + remoteNodeDiv.addClass("node-diff-node-deleted"); + $(' deleted').appendTo(remoteNodeDiv); + } else if (remoteDiff.changed[node.id]) { + remoteNodeDiv.addClass("node-diff-node-changed"); + $(' changed').appendTo(remoteNodeDiv); + } else { + stats.remote.unchangedCount++; + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' unchanged').appendTo(remoteNodeDiv); + } + //createNode(node,def).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 + } + } + var currentConflictCount = stats.conflicts; + createNodePropertiesTable(def,node,localNode,remoteNode,stats).appendTo(div); + if (currentConflictCount !== stats.conflicts) { + $('').prependTo(localNodeDiv); + $('').prependTo(remoteNodeDiv); + } + row.click(function(evt) { + evt.preventDefault(); + $(this).parent().toggleClass('collapsed'); + }); return div; } - function createNodePropertiesTable(node,newNode,def) { + function createNodePropertiesTable(def,node,localNodeObj,remoteNodeObj,stats) { + var localNode = localNodeObj.node; + var remoteNode; + if (remoteNodeObj) { + remoteNode = remoteNodeObj.node; + } + var nodePropertiesDiv = $("
            ",{class:"node-diff-node-entry-properties"}); var nodePropertiesTable = $("").appendTo(nodePropertiesDiv); + //var nodePropertiesTable = $("
            ").appendTo(nodePropertiesDiv); var row; - if (node.hasOwnProperty('x')) { - if (newNode.x !== node.x || newNode.y !== node.y) { - var currentPosition = RED.utils.createObjectElement({x:node.x,y:node.y}); - var newPosition = RED.utils.createObjectElement({x:newNode.x,y:newNode.y}); - row = $("
            ").appendTo(nodePropertiesTable); - currentPosition.appendTo(row.children()[1]); - newPosition.appendTo(row.children()[2]); + var localCell, remoteCell; + var currentValue, localValue, remoteValue; + var localChanged = false; + var remoteChanged = false; + var localChanges = 0; + var remoteChanges = 0; + var conflict = false; + var conflicted = false; + var status; + if (remoteNodeObj) { + if ( (remoteNodeObj.diff.changed[node.id] && localNodeObj.diff.deleted[node.id]) || + (remoteNodeObj.diff.deleted[node.id] && localNodeObj.diff.changed[node.id]) + ) { + conflicted = true; + } + } + 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.y || localNode.y !== remoteNode.x)) || + (!localChanged && remoteChanged && localNodeObj.diff.deleted[node.id]) || + (localChanged && !remoteChanged && remoteNodeObj.diff.deleted[node.id]) + ) { + conflicted = true; + conflict = true; + } + row = $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); - formatWireProperty(node.wires).appendTo(row.children()[1]); - formatWireProperty(newNode.wires).appendTo(row.children()[2]); + 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]) + ){ + conflicted = true; + conflict = true; + } + row = $("").appendTo(nodePropertiesTable); + $("').appendTo(nodePropertiesTable); - formattedProperty.appendTo(row.children()[1]); - newFormattedProperty.appendTo(row.children()[2]); + 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]) + ){ + conflicted = true; + conflict = true; + } + + row = $("").appendTo(nodePropertiesTable); + $("",{class:"node-diff-property-header"}).prependTo(nodePropertiesTable); + // $("').appendTo(row); + // } else if (localChanges > 0) { + // cell = $('').appendTo(row); + // if (conflicted) { + // $(' conflict ').appendTo(cell); + // } + // $(' changed').appendTo(cell); + // } else { + // $('').appendTo(row); + // } + // } else if (localNodeObj.diff.deleted[node.id]) { + // cell = $('').appendTo(row); + // if (conflicted) { + // $(' conflict ').appendTo(cell); + // } + // $(' deleted').appendTo(cell); + // } else { + // $('').appendTo(row); + // } + // if (remoteNode) { + // if (remoteNodeObj.diff.added[node.id]) { + // $('').appendTo(row); + // } else if (remoteChanges > 0) { + // cell = $('').appendTo(row); + // if (conflicted) { + // $(' conflict ').appendTo(cell); + // } + // $(' changed').appendTo(cell); + // } else { + // $('').appendTo(row); + // } + // } else if (remoteNodeObj.diff.deleted[node.id]) { + // cell = $('').appendTo(row); + // if (conflicted) { + // $(' conflict ').appendTo(cell); + // } + // $(' deleted').appendTo(cell); + // } else { + // $('').appendTo(row); + // } + if (conflicted) { + stats.conflicts++; + } return nodePropertiesDiv; } - - - - function showlocalDiff() { + function showLocalDiff() { var nns = RED.nodes.createCompleteNodeSet(); var originalFlow = RED.nodes.originalFlow(); var diff = generateDiff(originalFlow,nns); showDiff(diff); } - - + function showRemoteDiff() { + $.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); + showDiff(localDiff,remoteDiff); + } + }); + } function parseNodes(nodeList) { var tabOrder = []; var tabs = {}; @@ -355,7 +813,6 @@ RED.diff = (function() { globals: globals } } - function generateDiff(currentNodes,newNodes) { var currentConfig = parseNodes(currentNodes); var newConfig = parseNodes(newNodes); @@ -363,18 +820,15 @@ RED.diff = (function() { var added = {}; var deleted = {}; var changed = {}; - var conflicted = {}; 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)) { if (!pending.added.hasOwnProperty(id)) { deleted[id] = true; - conflicted[id] = node&&node.changed; } } else if (JSON.stringify(currentConfig.all[id]) !== JSON.stringify(newConfig.all[id])) { changed[id] = true; - conflicted[id] = node.changed; } }); Object.keys(newConfig.all).forEach(function(id) { @@ -383,45 +837,14 @@ RED.diff = (function() { } }); - // console.log("Added",added); - // console.log("Deleted",deleted); - // console.log("Changed",changed); - // console.log("Conflicted",conflicted); - // - // var formatString = function(id) { - // return conflicted[id]?"!":(added[id]?"+":(deleted[id]?"-":(changed[id]?"~":" "))); - // } - // newConfig.tabOrder.forEach(function(tabId) { - // var tab = newConfig.tabs[tabId]; - // console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")"); - // tab.nodes.forEach(function(node) { - // console.log(" ",formatString(node.id),node.type,node.name || node.id); - // }) - // if (currentConfig.tabs[tabId]) { - // currentConfig.tabs[tabId].nodes.forEach(function(node) { - // if (deleted[node.id]) { - // console.log(" ",formatString(node.id),node.type,node.name || node.id); - // } - // }) - // } - // }); - // currentConfig.tabOrder.forEach(function(tabId) { - // if (deleted[tabId]) { - // var tab = currentConfig.tabs[tabId]; - // console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")"); - // } - // }); - return { currentConfig: currentConfig, newConfig: newConfig, added: added, deleted: deleted, changed: changed, - conflicted: conflicted } } - function formatNodeProperty(prop) { var formattedProperty = prop; if (formattedProperty === null) { @@ -436,16 +859,14 @@ RED.diff = (function() { } return formattedProperty; } - - function showDiff(localDiff) { - var el; + function showDiff(localDiff,remoteDiff) { var list = $("#node-dialog-view-diff-diff"); list.editableList('empty'); + $("#node-dialog-view-diff-headers").empty(); var currentConfig = localDiff.currentConfig; var newConfig = localDiff.newConfig; - - list.editableList('addItem',{ + var el = { diff: localDiff, def: { category: 'config', @@ -459,7 +880,22 @@ RED.diff = (function() { n: {}, nodes: newConfig.globals } - }); + }; + + if (remoteDiff !== undefined) { + $('#node-dialog-view-diff').addClass('node-diff-three-way'); + + $('
            Local
            Remote
            ').appendTo("#node-dialog-view-diff-headers"); + el.remoteTab = { + n:{}, + nodes:remoteDiff.newConfig.globals + }; + el.remoteDiff = remoteDiff; + } else { + $('#node-dialog-view-diff').removeClass('node-diff-three-way'); + } + + list.editableList('addItem',el); var seenTabs = {}; @@ -473,20 +909,45 @@ RED.diff = (function() { 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; list.editableList('addItem',el) }); newConfig.tabOrder.forEach(function(tabId) { if (!seenTabs[tabId]) { + seenTabs[tabId] = true; var tab = newConfig.tabs[tabId]; var el = { diff: localDiff, def: {}, - tab:tab + tab:tab, + newTab: tab }; + if (remoteDiff !== undefined) { + el.remoteDiff = remoteDiff; + } list.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: {}, + tab:tab, + remoteTab:tab + }; + list.editableList('addItem',el) + } + }); + } var subflowId; for (subflowId in currentConfig.subflows) { if (currentConfig.subflows.hasOwnProperty(subflowId)) { @@ -504,6 +965,10 @@ RED.diff = (function() { if (newConfig.subflows.hasOwnProperty(subflowId)) { el.newTab = newConfig.subflows[subflowId]; } + if (remoteDiff !== undefined) { + el.remoteTab = remoteDiff.newConfig.subflows[subflowId]; + el.remoteDiff = remoteDiff; + } list.editableList('addItem',el) } } @@ -517,19 +982,42 @@ RED.diff = (function() { category: "subflows", color: "#da9" }, - tab:newConfig.subflows[subflowId] + tab:newConfig.subflows[subflowId], + newTab:newConfig.subflows[subflowId] + } + if (remoteDiff !== undefined) { + el.remoteDiff = remoteDiff; } list.editableList('addItem',el) } } + if (remoteDiff !== undefined) { + for (subflowId in remoteDiff.newConfig.subflows) { + if (remoteDiff.newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) { + // TODO how to recognise this is a remotely added flow + el = { + diff: localDiff, + remoteDiff: remoteDiff, + def: { + defaults:{}, + icon:"subflow.png", + category: "subflows", + color: "#da9" + }, + tab:remoteDiff.newConfig.subflows[subflowId], + remoteTab: remoteDiff.newConfig.subflows[subflowId] + } + list.editableList('addItem',el) + } + } + } + $("#node-diff-filter-changed").addClass("selected"); $("#node-diff-filter-all").removeClass("selected"); $("#node-dialog-view-diff").dialog("open"); } - - return { init: init } diff --git a/editor/sass/diff.scss b/editor/sass/diff.scss index 8f6febf00..e3de4c77d 100644 --- a/editor/sass/diff.scss +++ b/editor/sass/diff.scss @@ -21,14 +21,16 @@ .red-ui-editableList-container { border-radius:1px; padding:0; + background: #f9f9f9; } #node-dialog-view-diff-diff { position: absolute; - top:50px; + top:80px; bottom:10px; left:10px; right:10px; li { + background: #f9f9f9; padding: 0px; border: none; min-height: 0; @@ -37,14 +39,34 @@ } .red-ui-editableList-item-content { padding: 5px; - padding-bottom: 0; + // padding-bottom: 5px; } } +#node-dialog-view-diff-headers { + position: absolute; + left:17px; + right:32px; + top: 55px; + height: 25px; + .node-diff-node-entry-cell:not(:first-child) { + background: #f9f9f9; + text-align: center; + border-top: 1px solid $secondary-border-color; + border-color:$secondary-border-color; + } + .node-diff-node-entry-cell:last-child { + border-right: 1px solid $secondary-border-color; + + } +} + .node-diff-toolbar { position:absolute; top:0; left:0; right:0; + height: 43px; + box-sizing: border-box; color: #666; text-align: right; padding: 8px 10px; @@ -53,12 +75,13 @@ white-space: nowrap; } .node-diff-tab { - border: 1px solid $secondary-border-color; - border-radius: 3px; + background: #fff; + border: 1px solid #ddd; + border-radius: 1px; overflow: hidden; &.collapsed { - .node-diff-tab-title > .node-diff-chevron { + .node-diff-tab-title .node-diff-chevron { transform: rotate(-90deg); } .node-diff-node-entry { @@ -67,21 +90,27 @@ } } .node-diff-tab-stats { - position: absolute; - left: 50%; - top: 13px; + font-size: 0.9em; } .node-diff-chevron { display: inline-block; width: 15px; text-align: center; - margin: 3px 5px 3px 5px; + margin-left: 3px; transition: transform 0.1s ease-in-out; } .node-diff-node-entry { - border-top: 1px solid $secondary-border-color; + margin-left: 20px; + font-size: 0.9em; + + &:first-child { + border-top: 1px solid #eee; + } + &:not(:last-child) { + border-bottom: 1px solid #eee; + } &.collapsed { .node-diff-chevron { @@ -91,6 +120,14 @@ display: none; } } + &:not(.collapsed) { + .node-diff-node-entry-cell:not(:first-child) { + //display: none; + } + .node-diff-node-entry-cell:first-child { + width: 100% + } + } table { border-collapse: collapse; @@ -99,20 +136,57 @@ } td, th { border: 1px solid $secondary-border-color; - padding: 3px 5px; + padding: 0 0 0 3px; text-align: left; overflow-x: auto; } tr { vertical-align: top; + &:first-child td { + white-space:nowrap; + overflow:hidden; + } } - td:nth-child(1) { - width: 100px; + td:first-child { + width: 140px; } td:not(:first-child) { - width: calc(50% - 100px); + width: calc( 100% - 140px); + } + td { + .node-diff-status { + margin-left: 0; + } + } + tr:not(.node-diff-property-header) { + .node-diff-status { + width: 12px; + margin-left: 0; + margin-top: 0; + margin-bottom: 0; + margin-right: 5px; + } } } +.node-diff-three-way { + .node-diff-node-entry-cell { + width: 33.3333333% + } + td:not(:first-child) { + width: calc( (100% - 140px) / 2); + } + + .node-diff-node-entry { + .node-diff-node-entry-cell { + width: calc( ( 100% + 20px ) / 3 ); + + &:first-child { + width: calc( ( 100% + 20px ) / 3 - 20px ); + } + } + } +} + .node-diff-column { display:inline-block; height:100%; @@ -126,17 +200,24 @@ } .node-diff-tab-title { - padding: 3px 3px 3px 0; - background: #f6f6f6; + cursor: pointer; + padding: 0; + // background: #f6f6f6; +} +.node-diff-tab-title-meta { + vertical-align: middle; + display: inline-block; + padding-top: 2px; +} +.node-diff-node-entry-header { cursor: pointer; } - .node-diff-node-entry-node { vertical-align: middle; display: inline-block; margin: 5px; - width: 24px; - height: 20px; + width: 18px; + height: 15px; background: #ddd; border-radius: 2px; border: 1px solid #999; @@ -145,11 +226,12 @@ background-size: contain; position: relative; - .palette-icon { - width: 16px; + .palette_icon { + background-position: 49% 50%; + width: 15px; } .palette_icon_container { - width: 24px; + width: 18px; } } .node-diff-tab-empty { @@ -187,28 +269,39 @@ color: #f89406; } } +.node-diff-node-unchanged { + //background: #fff2ca; + .node-diff-status { + color: #bbb; + } +} +.node-diff-node-conflict { + .node-diff-status { + color: #9b45ce; + } +} .node-diff-node-entry-title { - cursor: pointer; + display: inline-block; .node-diff-status { margin-left: 15px; } } .node-diff-node-entry-properties { - margin: 6px 8px 6px 30px; + margin: 5px ; color: #666; } .node-diff-status { display: inline-block; - width: 15px; height: 20px; margin-left: 5px; - vertical-align: middle; + vertical-align: top; + margin-top: 6px; + margin-bottom: 6px; text-align: center; } .node-diff-node-description { color: $form-text-color; - margin-left: 5px; margin-right: 5px; padding-top: 5px; display: inline-block; @@ -220,7 +313,7 @@ } .node-diff-node-meta { float: right; - font-size: 0.9em; + //font-size: 0.9em; color: #999; margin-top: 7px; margin-right: 10px; @@ -231,3 +324,96 @@ .node-diff-deleted { color: #f80000} .node-diff-changed { color: #f89406} .node-diff-conflicted { color: purple} + + +.node-diff-node-entry-cell { + display: inline-block; + vertical-align: top; + box-sizing: border-box; + width: calc( (100% - 20px) / 2); + height: 32px; + border-left: 1px solid #eee; + padding-top: 2px; + white-space: nowrap; + overflow: hidden; +} +.node-diff-empty { + background: #f3f3f3; + background: repeating-linear-gradient( + 20deg, + #fff, #fff 5px, + #f9f9f9 5px, + #f9f9f9 10px + ); +} +.node-diff-node-entry-cell:first-child { + border-left: none; +} +.node-diff-property-cell-label { + margin-left: 20px; + vertical-align: top; + box-sizing: border-box; + padding-left: 8px; + width: 120px; +} +.node-diff-property-wires { + display: inline-block; + .node-diff-node-entry-node { + width: 18px; + height: 15px; + } + .palette_icon_container { + width: 18px; + } + .palette_icon { + width: 15px; + } + ul,li,ol { + background: none !important; + } + ul { + vertical-align: middle; + display: inline-block; + margin-left: 5px; + } + li { + list-style-type: none !important; + } + ol { + font-size: 0.9em; + margin: 0; + & > span { + vertical-align: middle; + display: inline-block; + width: 30px; + text-align: center; + } + & > li:not(:last-child) { + border-bottom: 1px solid #999; + } + } + +} +.node-diff-node-props .node-diff-node-entry-cell:first-child { + padding: 6px 0px; + span:not(.node-diff-chevron) { + margin-left: 5px; + } + +} +.node-diff-property-cell { + // vertical-align: top; + // display:inline-block; + // + // box-sizing: border-box; + // padding: 1px 5px; + //min-height: 30px; + + &.node-diff-node-changed { + background: #fff2e1; + } + &.node-diff-node-conflict { + background: #ffdad4; + } + +}
            position
            ",{class:"node-diff-property-cell-label"}).html("position").appendTo(row); + localCell = $("",{class:"node-diff-property-cell"}).appendTo(row); + if (localNode) { + localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged")); + $(''+(localChanged?'':'')+'').appendTo(localCell); + RED.utils.createObjectElement({x:localNode.x,y:localNode.y}).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell"}).appendTo(row); + remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged")); + if (remoteNode) { + $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); + RED.utils.createObjectElement({x:remoteNode.x,y:remoteNode.y}).appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-empty"); + } } } + // + localChanged = remoteChanged = conflict = false; if (node.hasOwnProperty('wires')) { - var localValue = JSON.stringify(node.wires); - var remoteValue = JSON.stringify(newNode.wires); - if (localValue !== remoteValue) { - row = $("
            wires
            ",{class:"node-diff-property-cell-label"}).html("wires").appendTo(row); + localCell = $("",{class:"node-diff-property-cell"}).appendTo(row); + if (localNode) { + if (!conflict) { + localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged")); + $(''+(localChanged?'':'')+'').appendTo(localCell); + } else { + localCell.addClass("node-diff-node-conflict"); + $('').appendTo(localCell); + } + formatWireProperty(localNode.wires,localNodeObj.all).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); } + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell"}).appendTo(row); + if (remoteNode) { + if (!conflict) { + remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged")); + $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-node-conflict"); + $('').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!='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)); } properties.forEach(function(d) { - var localValue = JSON.stringify(node[d]); - var remoteValue = JSON.stringify(newNode[d]); - - if (remoteValue !== localValue) { - var formattedProperty = RED.utils.createObjectElement(node[d]); - var newFormattedProperty = RED.utils.createObjectElement(newNode[d]); - var row = $("
            "+d+'
            ",{class:"node-diff-property-cell-label"}).html(d).appendTo(row); + localCell = $("",{class:"node-diff-property-cell"}).appendTo(row); + if (localNode) { + if (!conflict) { + localCell.addClass("node-diff-node-"+(localChanged?"changed":"unchanged")); + $(''+(localChanged?'':'')+'').appendTo(localCell); + } else { + localCell.addClass("node-diff-node-conflict"); + $('').appendTo(localCell); + } + RED.utils.createObjectElement(localNode[d]).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell"}).appendTo(row); + if (remoteNode) { + if (!conflict) { + remoteCell.addClass("node-diff-node-"+(remoteChanged?"changed":"unchanged")); + $(''+(remoteChanged?'':'')+'').appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-node-conflict"); + $('').appendTo(remoteCell); + } + RED.utils.createObjectElement(remoteNode[d]).appendTo(remoteCell); + } else { + remoteCell.addClass("node-diff-empty"); + } + } + }); + + // row = $("
            ").appendTo(row); + // var cell; + // if (localNode) { + // if (localNodeObj.diff.added[node.id]) { + // $(' added unchanged added unchanged