diff --git a/editor/js/ui/diff.js b/editor/js/ui/diff.js new file mode 100644 index 000000000..14a153c16 --- /dev/null +++ b/editor/js/ui/diff.js @@ -0,0 +1,536 @@ +RED.diff = (function() { + + function init() { + + RED.actions.add("core:show-current-diff",showlocalDiff); + RED.keyboard.add("*","ctrl-shift-l","core:show-current-diff"); + + + var dialog = $('
    ').appendTo(document.body); + + var toolbar = $('
    '+ + ''+ + 'all nodes'+ + 'changed nodes'+ + ''+ + '
    ').prependTo(dialog); + + toolbar.find(".node-diff-filter").click(function(evt) { + evt.preventDefault(); + if (!$(this).hasClass('selected')) { + $(this).siblings().removeClass('selected'); + $(this).addClass('selected'); + } + if ($(this).attr('id') === 'node-diff-filter-all') { + diffList.find('.node-diff-node-unchanged').parent().removeClass('hide'); + diffList.find('.node-diff-tab-unchanged').parent().removeClass('hide'); + } else { + diffList.find('.node-diff-node-unchanged').parent().addClass('hide'); + diffList.find('.node-diff-tab-unchanged').parent().addClass('hide'); + $(".node-diff-tab.node-diff-tab-unchanged").addClass("collapsed"); + } + }) + + $("#node-dialog-view-diff").dialog({ + title: RED._('deploy.confirm.button.review'), + modal: true, + autoOpen: false, + buttons: [ + // { + // text: RED._("deploy.confirm.button.cancel"), + // click: function() { + // $( this ).dialog( "close" ); + // } + // }, + { + text: RED._("common.label.close"), + class: "primary", + click: function() { + $( this ).dialog( "close" ); + } + } + ], + open: function() { + $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)}); + } + }); + + var diffList = $("#node-dialog-view-diff-diff").editableList({ + addButton: false, + scrollOnAdd: false, + addItem: function(container,i,object) { + var localDiff = object.diff; + 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 status = $('').appendTo(titleRow); + + $('').appendTo(titleRow); + createNodeIcon(tab,def).appendTo(titleRow); + var tabForLabel = (object.newTab || object.tab).n; + if (tabForLabel.type === 'tab') { + $('').html(tabForLabel.label||tabForLabel.id).appendTo(titleRow); + } else if (tab.type === 'subflow') { + $('').html((tabForLabel.name||tabForLabel.id)).appendTo(titleRow); + } else { + $('').html("Global configuration nodes").appendTo(titleRow); + } + + 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); + } + } + + 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) + }); + if (object.newTab) { + object.newTab.nodes.forEach(function(node) { + if (!seen[node.id]) { + createNodeDiffRow(node,flowStats,localDiff).appendTo(tabDiv) + } + }); + } + titleRow.click(function(evt) { + evt.preventDefault(); + + if (titleRow.parent().find(".node-diff-node-entry:not(.hide)").length > 0) { + titleRow.parent().toggleClass('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"); + } + } + if (localDiff.deleted[tab.id]) { + $('').appendTo(status); + } else if (localDiff.added[tab.id]) { + $('').appendTo(status); + } else if (localDiff.changed[tab.id]) { + $('').appendTo(status); + } + 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); + } + }); + } + + function formatWireProperty(wires) { + var result = $("
      "); + wires.forEach(function(p,i) { + var port = $("
    1. ").appendTo(result); + if (p && p.length > 0) { + var links = $("
        ").appendTo(port); + p.forEach(function(d) { + var entry = $("
      • ").text(d).appendTo(links); + }) + } else { + port.html('none'); + } + }) + return result; + } + function createNodeIcon(node,def) { + var nodeDiv = $("
        ",{class:"node-diff-node-entry-node"}); + var colour = def.color; + var icon_url = "arrow-in.png"; + if (node.type === 'tab') { + colour = "#C0DEED"; + icon_url = "subflow.png"; + } else if (def.category === 'config') { + icon_url = "cog.png"; + } else if (node.type === 'unknown') { + icon_url = "alert.png"; + } else { + icon_url = def.icon; + } + nodeDiv.css('backgroundColor',colour); + + var iconContainer = $('
        ',{class:"palette_icon_container"}).appendTo(nodeDiv); + $('
        ',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer); + + return nodeDiv; + } + function createNodeDiffRow(node,stats,localDiff) { + var realNode = RED.nodes.node(node.id); + var hasChanges = false; + if (localDiff.added[node.id]) { + stats.addedCount++; + } + if (localDiff.deleted[node.id]) { + stats.deletedCount++; + } + if (localDiff.changed[node.id]) { + stats.changedCount++; + hasChanges = true; + } + + 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); + } + } 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"); + } + + createNodeIcon(node,def).appendTo(nodeTitleDiv); + + var contentDiv = $('
        ',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv); + + $('',{class:"node-diff-node-label"}).html(nodeLabel).appendTo(contentDiv); + $('',{class:"node-diff-node-meta"}).html(node.id).appendTo(nodeTitleDiv); + + //$('
        ',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv); + //$('
        ',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv); + + return div; + } + function createNodePropertiesTable(node,newNode,def) { + var nodePropertiesDiv = $("
        ",{class:"node-diff-node-entry-properties"}); + + 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]); + + } + } + if (node.hasOwnProperty('wires')) { + var localValue = JSON.stringify(node.wires); + var remoteValue = JSON.stringify(newNode.wires); + if (localValue !== remoteValue) { + row = $("").appendTo(nodePropertiesTable); + formatWireProperty(node.wires).appendTo(row.children()[1]); + formatWireProperty(newNode.wires).appendTo(row.children()[2]); + } + + } + 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 = $("').appendTo(nodePropertiesTable); + formattedProperty.appendTo(row.children()[1]); + newFormattedProperty.appendTo(row.children()[2]); + } + }) + return nodePropertiesDiv; + } + + + + function showlocalDiff() { + var nns = RED.nodes.createCompleteNodeSet(); + var originalFlow = RED.nodes.originalFlow(); + var diff = generateDiff(originalFlow,nns); + showDiff(diff); + } + + + 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 pending = RED.nodes.pending(); + 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) { + if (!currentConfig.all.hasOwnProperty(id) && !pending.deleted.hasOwnProperty(id)) { + added[id] = true; + } + }); + + // 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) { + formattedProperty = 'null'; + } else if (formattedProperty === undefined) { + formattedProperty = 'undefined'; + } else if (typeof formattedProperty === 'object') { + formattedProperty = JSON.stringify(formattedProperty); + } + if (/\n/.test(formattedProperty)) { + formattedProperty = "
        "+formattedProperty+"
        " + } + return formattedProperty; + } + + function showDiff(localDiff) { + var el; + var list = $("#node-dialog-view-diff-diff"); + list.editableList('empty'); + + var currentConfig = localDiff.currentConfig; + var newConfig = localDiff.newConfig; + + list.editableList('addItem',{ + diff: localDiff, + def: { + category: 'config', + color: '#f0f0f0' + }, + tab: { + n: {}, + nodes: currentConfig.globals + }, + newTab: { + n: {}, + nodes: newConfig.globals + } + }); + + var seenTabs = {}; + + currentConfig.tabOrder.forEach(function(tabId) { + var tab = currentConfig.tabs[tabId]; + var el = { + diff: localDiff, + def: {}, + tab:tab + }; + if (newConfig.tabs.hasOwnProperty(tabId)) { + el.newTab = newConfig.tabs[tabId]; + } + seenTabs[tabId] = true; + list.editableList('addItem',el) + }); + newConfig.tabOrder.forEach(function(tabId) { + if (!seenTabs[tabId]) { + var tab = newConfig.tabs[tabId]; + var el = { + diff: localDiff, + def: {}, + tab:tab + }; + list.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]; + } + list.editableList('addItem',el) + } + } + for (subflowId in newConfig.subflows) { + if (newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) { + el = { + diff: localDiff, + def: { + defaults:{}, + icon:"subflow.png", + category: "subflows", + color: "#da9" + }, + tab: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 + } +})();
        position
        wires
        "+d+'