diff --git a/.travis.yml b/.travis.yml index 3d064cb65..04d4ab3ce 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,6 @@ node_js: - "7" - "6" - "4" - - "0.10" script: - istanbul cover ./node_modules/.bin/grunt --report lcovonly && istanbul report text && ( cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js || true ) && rm -rf coverage before_script: diff --git a/Gruntfile.js b/Gruntfile.js index ffb1df3c3..60b474103 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -117,7 +117,9 @@ module.exports = function(grunt) { "editor/js/ui/common/tabs.js", "editor/js/ui/common/typedInput.js", "editor/js/ui/utils.js", + "editor/js/ui/actions.js", "editor/js/ui/deploy.js", + "editor/js/ui/diff.js", "editor/js/ui/keyboard.js", "editor/js/ui/workspaces.js", "editor/js/ui/view.js", @@ -152,6 +154,10 @@ module.exports = function(grunt) { "public/vendor/vendor.css": [ // TODO: resolve relative resource paths in // bootstrap/FA/jquery + ], + "public/vendor/jsonata/jsonata.js": [ + "node_modules/jsonata/jsonata.js", + "editor/vendor/jsonata/formatter.js" ] } } @@ -160,7 +166,11 @@ module.exports = function(grunt) { build: { files: { 'public/red/red.min.js': 'public/red/red.js', - 'public/red/main.min.js': 'public/red/main.js' + 'public/red/main.min.js': 'public/red/main.js', + 'public/vendor/jsonata/jsonata.min.js': 'public/vendor/jsonata/jsonata.js', + 'public/vendor/ace/mode-jsonata.js': 'editor/vendor/jsonata/mode-jsonata.js', + 'public/vendor/ace/worker-jsonata.js': 'editor/vendor/jsonata/worker-jsonata.js', + 'public/vendor/ace/snippets/jsonata.js': 'editor/vendor/jsonata/snippets-jsonata.js' } } }, @@ -186,6 +196,11 @@ module.exports = function(grunt) { 'red/api/locales/en-US/editor.json', 'red/runtime/locales/en-US/runtime.json' ] + }, + keymaps: { + src: [ + 'editor/js/keymap.json' + ] } }, attachCopyright: { @@ -222,7 +237,7 @@ module.exports = function(grunt) { files: [ 'editor/js/**/*.js' ], - tasks: ['concat','uglify','attachCopyright:js'] + tasks: ['copy:build','concat','uglify','attachCopyright:js'] }, sass: { files: [ @@ -238,6 +253,12 @@ module.exports = function(grunt) { ], tasks: ['jsonlint:messages'] }, + keymaps: { + files: [ + 'editor/js/keymap.json' + ], + tasks: ['jsonlint:keymaps','copy:build'] + }, misc: { files: [ 'CHANGELOG.md' @@ -276,6 +297,10 @@ module.exports = function(grunt) { src: 'editor/js/main.js', dest: 'public/red/main.js' }, + { + src: 'editor/js/keymap.json', + dest: 'public/red/keymap.json' + }, { cwd: 'editor/images', src: '**', @@ -435,7 +460,7 @@ module.exports = function(grunt) { grunt.registerTask('build', 'Builds editor content', - ['clean:build','concat:build','concat:vendor','copy:build','uglify:build','sass:build','jsonlint:messages','attachCopyright']); + ['clean:build','jsonlint','concat:build','concat:vendor','copy:build','uglify:build','sass:build','attachCopyright']); grunt.registerTask('dev', 'Developer mode: run node-red, watch for source changes and build/restart', diff --git a/editor/images/typedInput/expr.png b/editor/images/typedInput/expr.png new file mode 100644 index 000000000..704105ce5 Binary files /dev/null and b/editor/images/typedInput/expr.png differ diff --git a/editor/js/history.js b/editor/js/history.js index c22c71912..169d05b3f 100644 --- a/editor/js/history.js +++ b/editor/js/history.js @@ -16,6 +16,289 @@ RED.history = (function() { var undo_history = []; + function undoEvent(ev) { + var i; + var len; + var node; + var subflow; + var modifiedTabs = {}; + if (ev) { + if (ev.t == 'multi') { + len = ev.events.length; + for (i=len-1;i>=0;i--) { + undoEvent(ev.events[i]); + } + } else if (ev.t == 'replace') { + RED.nodes.clear(); + var imported = RED.nodes.import(ev.config); + imported[0].forEach(function(n) { + if (ev.changed[n.id]) { + n.changed = true; + } + }) + + RED.nodes.version(ev.rev); + } else if (ev.t == 'add') { + if (ev.nodes) { + for (i=0;i 0) { + subflow = RED.nodes.subflow(ev.subflowInputs[0].z); + subflow.in.push(ev.subflowInputs[0]); + subflow.in[0].dirty = true; + } + if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { + subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); + ev.subflowOutputs.sort(function(a,b) { return a.i-b.i}); + for (i=0;i= output.i) { + l.sourcePort++; + } + } + }); + } + } + if (ev.subflow && ev.subflow.hasOwnProperty('instances')) { + ev.subflow.instances.forEach(function(n) { + var node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + if (subflow) { + RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { + n.inputs = subflow.in.length; + n.outputs = subflow.out.length; + while (n.outputs > n.ports.length) { + n.ports.push(n.ports.length); + } + n.resize = true; + n.dirty = true; + }); + } + if (ev.nodes) { + for (i=0;i ev.subflow.inputCount) { + ev.node.in.splice(ev.subflow.inputCount); + } else if (ev.subflow.inputs.length > 0) { + ev.node.in = ev.node.in.concat(ev.subflow.inputs); + } + } + if (ev.subflow.hasOwnProperty('outputCount')) { + if (ev.node.out.length > ev.subflow.outputCount) { + ev.node.out.splice(ev.subflow.outputCount); + } else if (ev.subflow.outputs.length > 0) { + ev.node.out = ev.node.out.concat(ev.subflow.outputs); + } + } + if (ev.subflow.hasOwnProperty('instances')) { + ev.subflow.instances.forEach(function(n) { + var node = RED.nodes.node(n.id); + if (node) { + node.changed = n.changed; + node.dirty = true; + } + }); + } + RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { + n.inputs = ev.node.in.length; + n.outputs = ev.node.out.length; + RED.editor.updateNodeProperties(n); + }); + } else { + var outputMap; + if (ev.outputMap) { + outputMap = {}; + for (var port in ev.outputMap) { + if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== -1) { + outputMap[ev.outputMap[port]] = port; + } + } + } + RED.editor.updateNodeProperties(ev.node,outputMap); + RED.editor.validateNode(ev.node); + } + if (ev.links) { + for (i=0;i 0) { - subflow = RED.nodes.subflow(ev.subflowInputs[0].z); - subflow.in.push(ev.subflowInputs[0]); - subflow.in[0].dirty = true; - } - if (ev.subflowOutputs && ev.subflowOutputs.length > 0) { - subflow = RED.nodes.subflow(ev.subflowOutputs[0].z); - ev.subflowOutputs.sort(function(a,b) { return a.i-b.i}); - for (i=0;i= output.i) { - l.sourcePort++; - } - } - }); - } - } - if (ev.subflow && ev.subflow.hasOwnProperty('instances')) { - ev.subflow.instances.forEach(function(n) { - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); - } - if (subflow) { - RED.nodes.filterNodes({type:"subflow:"+subflow.id}).forEach(function(n) { - n.inputs = subflow.in.length; - n.outputs = subflow.out.length; - while (n.outputs > n.ports.length) { - n.ports.push(n.ports.length); - } - n.resize = true; - n.dirty = true; - }); - } - if (ev.nodes) { - for (i=0;i ev.subflow.inputCount) { - ev.node.in.splice(ev.subflow.inputCount); - } else if (ev.subflow.inputs.length > 0) { - ev.node.in = ev.node.in.concat(ev.subflow.inputs); - } - } - if (ev.subflow.hasOwnProperty('outputCount')) { - if (ev.node.out.length > ev.subflow.outputCount) { - ev.node.out.splice(ev.subflow.outputCount); - } else if (ev.subflow.outputs.length > 0) { - ev.node.out = ev.node.out.concat(ev.subflow.outputs); - } - } - if (ev.subflow.hasOwnProperty('instances')) { - ev.subflow.instances.forEach(function(n) { - var node = RED.nodes.node(n.id); - if (node) { - node.changed = n.changed; - node.dirty = true; - } - }); - } - RED.nodes.filterNodes({type:"subflow:"+ev.node.id}).forEach(function(n) { - n.inputs = ev.node.in.length; - n.outputs = ev.node.out.length; - RED.editor.updateNodeProperties(n); - }); - } else { - var outputMap; - if (ev.outputMap) { - outputMap = {}; - for (var port in ev.outputMap) { - if (ev.outputMap.hasOwnProperty(port) && ev.outputMap[port] !== -1) { - outputMap[ev.outputMap[port]] = port; - } - } - } - RED.editor.updateNodeProperties(ev.node,outputMap); - RED.editor.validateNode(ev.node); - } - if (ev.links) { - for (i=0;i
  • ")+"
  • "; RED.notify(RED._("palette.event.nodeAdded", {count:addedTypes.length})+typeList,"success"); @@ -172,12 +186,11 @@ } function loadEditor() { - var menuOptions = []; menuOptions.push({id:"menu-item-view-menu",label:RED._("menu.label.view.view"),options:[ - {id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:RED.view.toggleShowGrid}, - {id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:RED.view.toggleSnapGrid}, - {id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:toggleStatus, selected: true}, + {id:"menu-item-view-show-grid",label:RED._("menu.label.view.showGrid"),toggle:true,onselect:"core:toggle-show-grid"}, + {id:"menu-item-view-snap-grid",label:RED._("menu.label.view.snapGrid"),toggle:true,onselect:"core:toggle-snap-grid"}, + {id:"menu-item-status",label:RED._("menu.label.displayStatus"),toggle:true,onselect:"core:toggle-status", selected: true}, null, // {id:"menu-item-bidi",label:RED._("menu.label.view.textDir"),options:[ // {id:"menu-item-bidi-default",toggle:"text-direction",label:RED._("menu.label.view.defaultDir"),selected: true, onselect:function(s) { if(s){RED.text.bidi.setTextDirection("")}}}, @@ -186,48 +199,46 @@ // {id:"menu-item-bidi-auto",toggle:"text-direction",label:RED._("menu.label.view.auto"), onselect:function(s) { if(s){RED.text.bidi.setTextDirection("auto")}}} // ]}, // null, - {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:RED.sidebar.toggleSidebar, selected: true} + {id:"menu-item-sidebar",label:RED._("menu.label.sidebar.show"),toggle:true,onselect:"core:toggle-sidebar", selected: true} ]}); menuOptions.push(null); menuOptions.push({id:"menu-item-import",label:RED._("menu.label.import"),options:[ - {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:RED.clipboard.import}, + {id:"menu-item-import-clipboard",label:RED._("menu.label.clipboard"),onselect:"core:import"}, {id:"menu-item-import-library",label:RED._("menu.label.library"),options:[]} ]}); menuOptions.push({id:"menu-item-export",label:RED._("menu.label.export"),disabled:true,options:[ - {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:RED.clipboard.export}, - {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export} + {id:"menu-item-export-clipboard",label:RED._("menu.label.clipboard"),disabled:true,onselect:"core:export"}, + {id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:"core:library-export"} ]}); menuOptions.push(null); - menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:RED.search.show}); + menuOptions.push({id:"menu-item-search",label:RED._("menu.label.search"),onselect:"core:search"}); menuOptions.push(null); - menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}}); + menuOptions.push({id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:"core:show-config-tab"}); menuOptions.push({id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[ - {id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add}, - {id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:RED.workspaces.edit}, - {id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:RED.workspaces.remove} + {id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:"core:add-flow"}, + {id:"menu-item-workspace-edit",label:RED._("menu.label.rename"),onselect:"core:edit-flow"}, + {id:"menu-item-workspace-delete",label:RED._("menu.label.delete"),onselect:"core:remove-flow"} ]}); menuOptions.push({id:"menu-item-subflow",label:RED._("menu.label.subflows"), options: [ - {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:RED.subflow.createSubflow}, - {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:RED.subflow.convertToSubflow}, + {id:"menu-item-subflow-create",label:RED._("menu.label.createSubflow"),onselect:"core:create-subflow"}, + {id:"menu-item-subflow-convert",label:RED._("menu.label.selectionToSubflow"),disabled:true,onselect:"core:convert-to-subflow"}, ]}); menuOptions.push(null); if (RED.settings.theme('palette.editable') !== false) { RED.palette.editor.init(); - menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:RED.palette.editor.show}); + menuOptions.push({id:"menu-item-edit-palette",label:RED._("menu.label.editPalette"),onselect:"core:manage-palette"}); menuOptions.push(null); } - menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:RED.keyboard.showHelp}); + menuOptions.push({id:"menu-item-keyboard-shortcuts",label:RED._("menu.label.keyboardShortcuts"),onselect:"core:show-help"}); menuOptions.push({id:"menu-item-help", label: RED.settings.theme("menu.menu-item-help.label","Node-RED website"), href: RED.settings.theme("menu.menu-item-help.url","http://nodered.org/docs") }); - menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: showAbout }); + menuOptions.push({id:"menu-item-node-red-version", label:"v"+RED.settings.version, onselect: "core:show-about" }); - RED.menu.init({id:"btn-sidemenu",options: menuOptions}); RED.user.init(); - RED.library.init(); RED.palette.init(); RED.sidebar.init(); @@ -237,15 +248,21 @@ RED.search.init(); RED.view.init(); RED.editor.init(); + RED.keyboard.init(); + RED.diff.init(); + + RED.menu.init({id:"btn-sidemenu",options: menuOptions}); RED.deploy.init(RED.settings.theme("deployButton",null)); - RED.keyboard.add("workspace", /* ? */ 191,{shift:true},function() {RED.keyboard.showHelp();d3.event.preventDefault();}); + RED.actions.add("core:show-about", showAbout); + RED.comms.connect(); $("#main-container").show(); $(".header-toolbar").show(); + loadNodeList(); } diff --git a/editor/js/nodes.js b/editor/js/nodes.js index 49bd29860..05e18cd7a 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -24,21 +24,13 @@ RED.nodes = (function() { var workspacesOrder =[]; var subflows = {}; var loadedFlowVersion = null; - var pending = { - deleted: {}, - added: {} - }; + + var initialLoad; var dirty = false; function setDirty(d) { dirty = d; - if (!d) { - pending = { - deleted: {}, - added: {} - }; - } RED.events.emit("nodes:change",{dirty:dirty}); } @@ -189,8 +181,6 @@ RED.nodes = (function() { } nodes.push(n); } - delete pending.deleted[n.id]; - pending.added[n.id] = true; RED.events.emit('nodes:add',n); } function addLink(l) { @@ -256,12 +246,6 @@ RED.nodes = (function() { if (node && node._def.onremove) { 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}; } @@ -274,8 +258,6 @@ RED.nodes = (function() { function addWorkspace(ws) { workspaces[ws.id] = ws; - pending.added[ws.id] = true; - delete pending.deleted[ws.id]; ws._def = { defaults: { label: {value:""} @@ -313,8 +295,6 @@ RED.nodes = (function() { var result = removeNode(removedNodes[n].id); removedLinks = removedLinks.concat(result.links); } - pending.deleted[id] = true; - delete pending.added[id] return {nodes:removedNodes,links:removedLinks}; } @@ -344,8 +324,6 @@ RED.nodes = (function() { outputs: sf.out.length } subflows[sf.id] = sf; - delete pending.deleted[sf.id]; - pending.added[sf.id] = true; RED.nodes.registerType("subflow:"+sf.id, { defaults:{name:{value:""}}, info: sf.info, @@ -369,8 +347,6 @@ RED.nodes = (function() { } function removeSubflow(sf) { delete subflows[sf.id]; - delete pending.added[sf.id]; - pending.deleted[sf.id] = true; registry.removeNodeType("subflow:"+sf.id); } @@ -697,6 +673,9 @@ RED.nodes = (function() { if (!$.isArray(newNodes)) { newNodes = [newNodes]; } + if (!initialLoad) { + initialLoad = JSON.parse(JSON.stringify(newNodes)); + } var unknownTypes = []; for (i=0;i 0) { + activateTab(previous.find("a")); + } + } + function activateNextTab() { + var next = ul.find("li.active").next(); + if (next.length > 0) { + activateTab(next.find("a")); + } + } function updateTabWidths() { var tabs = ul.find("li.red-ui-tab"); @@ -303,6 +318,8 @@ RED.tabs = (function() { }, removeTab: removeTab, activateTab: activateTab, + nextTab: activateNextTab, + previousTab: activatePreviousTab, resize: updateTabWidths, count: function() { return ul.find("li.red-ui-tab").size(); diff --git a/editor/js/ui/common/typedInput.js b/editor/js/ui/common/typedInput.js index a455efc66..e7a26cf3d 100644 --- a/editor/js/ui/common/typedInput.js +++ b/editor/js/ui/common/typedInput.js @@ -85,6 +85,7 @@ } return true; } + var allOptions = { msg: {value:"msg",label:"msg.",validate:validateExpression}, flow: {value:"flow",label:"flow.",validate:validateExpression}, @@ -94,7 +95,22 @@ bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]}, json: {value:"json",label:"JSON",icon:"red/images/typedInput/json.png", validate: function(v) { try{JSON.parse(v);return true;}catch(e){return false;}}}, re: {value:"re",label:"regular expression",icon:"red/images/typedInput/re.png"}, - date: {value:"date",label:"timestamp",hasValue:false} + date: {value:"date",label:"timestamp",hasValue:false}, + jsonata: { + value: "jsonata", + label: "expression", + icon: "red/images/typedInput/expr.png", + validate: function(v) { try{jsonata(v);return true;}catch(e){return false;}}, + expand:function() { + var that = this; + RED.editor.editExpression({ + value: this.value().replace(/\t/g,"\n"), + complete: function(v) { + that.value(v.replace(/\n/g,"\t")); + } + }) + } + } }; var nlsd = false; @@ -188,7 +204,7 @@ that.uiSelect.addClass('red-ui-typedInput-focus'); }); - + this.optionExpandButton = $('').appendTo(this.uiSelect); this.type(this.options.default||this.typeList[0].value); @@ -322,11 +338,16 @@ this.uiSelect.width(this.uiWidth); } if (this.typeMap[this.propertyType] && this.typeMap[this.propertyType].hasValue === false) { - this.selectTrigger.css('width',"100%"); + this.selectTrigger.addClass("red-ui-typedInput-full-width"); } else { - this.selectTrigger.width('auto'); + this.selectTrigger.removeClass("red-ui-typedInput-full-width"); var labelWidth = this._getLabelWidth(this.selectTrigger); this.elementDiv.css('left',labelWidth+"px"); + if (this.optionExpandButton.is(":visible")) { + this.elementDiv.css('right',"22px"); + } else { + this.elementDiv.css('right','0'); + } if (this.optionSelectTrigger) { this.optionSelectTrigger.css({'left':(labelWidth)+"px",'width':'calc( 100% - '+labelWidth+'px )'}); } @@ -396,6 +417,9 @@ this.selectLabel.text(opt.label); } if (opt.options) { + if (this.optionExpandButton) { + this.optionExpandButton.hide(); + } if (this.optionSelectTrigger) { this.optionSelectTrigger.show(); this.elementDiv.hide(); @@ -429,6 +453,16 @@ } this.elementDiv.show(); } + if (opt.expand && typeof opt.expand === 'function') { + this.optionExpandButton.show(); + this.optionExpandButton.off('click'); + this.optionExpandButton.on('click',function(evt) { + evt.preventDefault(); + opt.expand.call(that); + }) + } else { + this.optionExpandButton.hide(); + } this.element.trigger('change',this.propertyType,this.value()); } if (image) { diff --git a/editor/js/ui/deploy.js b/editor/js/ui/deploy.js index 55f3d8a7a..7e9ee350d 100644 --- a/editor/js/ui/deploy.js +++ b/editor/js/ui/deploy.js @@ -30,13 +30,13 @@ RED.deploy = (function() { var deploymentType = "full"; + var currentDiff = null; + function changeDeploymentType(type) { deploymentType = type; $("#btn-deploy-icon").attr("src",deploymentTypes[type].img); } - var currentDiff = null; - /** * options: * type: "default" - Button with drop-down options - no further customisation available @@ -98,21 +98,33 @@ RED.deploy = (function() { height: "auto", buttons: [ { - text: RED._("deploy.confirm.button.cancel"), + text: RED._("common.label.cancel"), click: function() { $( 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() { + RED.diff.mergeDiff(currentDiff); + $( this ).dialog( "close" ); + } + }, + { + id: "node-dialog-confirm-deploy-deploy", text: RED._("deploy.confirm.button.confirm"), class: "primary", click: function() { @@ -136,10 +148,37 @@ RED.deploy = (function() { }, open: function() { 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(1000 - (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(); } 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(); } } @@ -157,198 +196,7 @@ RED.deploy = (function() { } }); - // $("#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._("deploy.confirm.button.merge"), - // class: "primary", - // click: function() { - // $( this ).dialog( "close" ); - // } - // } - // ], - // open: function() { - // $(this).dialog({width:Math.min($(window).width(),900),height:Math.min($(window).height(),600)}); - // } - // }); - // $("#node-dialog-view-diff-diff").editableList({ - // addButton: false, - // scrollOnAdd: false, - // addItem: function(container,i,object) { - // var tab = object.tab.n; - // var tabDiv = $('
    ',{class:"node-diff-tab collapsed"}).appendTo(container); - // - // var titleRow = $('
    ',{class:"node-diff-tab-title"}).appendTo(tabDiv); - // titleRow.click(function(evt) { - // evt.preventDefault(); - // titleRow.parent().toggleClass('collapsed'); - // }) - // var chevron = $('').appendTo(titleRow); - // var title = $('').html(tab.label||tab.id).appendTo(titleRow); - // - // var stats = $('',{class:"node-diff-tab-stats"}).appendTo(titleRow); - // - // var addedCount = 0; - // var deletedCount = 0; - // var changedCount = 0; - // var conflictedCount = 0; - // - // object.tab.nodes.forEach(function(node) { - // var realNode = RED.nodes.node(node.id); - // var hasChanges = false; - // if (currentDiff.added[node.id]) { - // addedCount++; - // hasChanges = true; - // } - // if (currentDiff.deleted[node.id]) { - // deletedCount++; - // hasChanges = true; - // } - // if (currentDiff.changed[node.id]) { - // changedCount++; - // hasChanges = true; - // } - // if (currentDiff.conflicted[node.id]) { - // conflictedCount++; - // hasChanges = true; - // } - // - // if (hasChanges) { - // var def = RED.nodes.getType(node.type)||{}; - // var div = $("
    ",{class:"node-diff-node-entry collapsed"}).appendTo(tabDiv); - // var nodeTitleDiv = $("
    ",{class:"node-diff-node-entry-title"}).appendTo(div); - // nodeTitleDiv.click(function(evt) { - // evt.preventDefault(); - // $(this).parent().toggleClass('collapsed'); - // }) - // var newNode = currentDiff.newConfig.all[node.id]; - // var nodePropertiesDiv = $("
    ",{class:"node-diff-node-entry-properties"}).appendTo(div); - // - // var nodePropertiesTable = $("").appendTo(nodePropertiesDiv); - // - // if (node.hasOwnProperty('x')) { - // if (newNode.x !== node.x || newNode.y !== node.y) { - // var currentPosition = node.x+", "+node.y - // var newPosition = newNode.x+", "+newNode.y; - // $("").appendTo(nodePropertiesTable); - // } - // } - // 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]); - // var originalValue = realNode._config[d]; - // - // if (remoteValue !== originalValue) { - // var formattedProperty = formatNodeProperty(node[d]); - // var newFormattedProperty = formatNodeProperty(newNode[d]); - // if (localValue === originalValue) { - // // no conflict change - // } else { - // // conflicting change - // } - // $("").appendTo(nodePropertiesTable); - // } - // - // }) - // var nodeChevron = $('').appendTo(nodeTitleDiv); - // - // - // // var leftColumn = $('
    ',{class:"node-diff-column"}).appendTo(div); - // // var rightColumn = $('
    ',{class:"node-diff-column"}).appendTo(div); - // // rightColumn.html(" "); - // - // - // - // var nodeDiv = $("
    ",{class:"node-diff-node-entry-node"}).appendTo(nodeTitleDiv); - // 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); - // - // - // - // var contentDiv = $('
    ',{class:"node-diff-node-description"}).appendTo(nodeTitleDiv); - // - // $('',{class:"node-diff-node-label"}).html(node.label || node.name || node.id).appendTo(contentDiv); - // //$('
    ',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv); - // //$('
    ',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv); - // } - // - // }); - // - // var statsInfo = ''+object.tab.nodes.length+" nodes"+ - // (addedCount+deletedCount+changedCount+conflictedCount > 0 ? " : ":"")+ - // " "+ - // ((addedCount > 0)?''+addedCount+' added ':'')+ - // ((deletedCount > 0)?''+deletedCount+' deleted ':'')+ - // ((changedCount > 0)?''+changedCount+' changed ':'')+ - // ((conflictedCount > 0)?''+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 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 getNodeInfo(node) { @@ -393,145 +241,8 @@ RED.deploy = (function() { $( "#node-dialog-confirm-deploy-conflict" ).show(); $( "#node-dialog-confirm-deploy-type" ).val("conflict"); $( "#node-dialog-confirm-deploy" ).dialog( "open" ); - - // $("#node-dialog-confirm-deploy-review").append($('')); - // $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",0.4); - // $("#node-dialog-confirm-deploy-review").attr("disabled",true).addClass("disabled"); - // $.ajax({ - // headers: { - // "Accept":"application/json", - // }, - // cache: false, - // url: 'flows', - // success: function(nodes) { - // var newNodes = nodes.flows; - // var newRevision = nodes.rev; - // generateDiff(currentNodes,newNodes); - // $("#node-dialog-confirm-deploy-review").attr("disabled",false).removeClass("disabled"); - // $("#node-dialog-confirm-deploy-review img").remove(); - // $("#node-dialog-confirm-deploy-review .ui-button-text").css("opacity",1); - // } - // }); } - // 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.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]) { - // console.log(formatString(tabId),"Flow:",tab.n.label, "("+tab.n.id+")"); - // } - // }); - // - // currentDiff = { - // currentConfig: currentConfig, - // newConfig: newConfig, - // added: added, - // deleted: deleted, - // changed: changed, - // conflicted: conflicted - // } - // } - - // function showDiff() { - // if (currentDiff) { - // var list = $("#node-dialog-view-diff-diff"); - // list.editableList('empty'); - // var currentConfig = currentDiff.currentConfig; - // currentConfig.tabOrder.forEach(function(tabId) { - // var tab = currentConfig.tabs[tabId]; - // list.editableList('addItem',{tab:tab}) - // }); - // } - // $("#node-dialog-view-diff").dialog("open"); - // } - - function save(skipValidation,force) { if (!$("#btn-deploy").hasClass("disabled")) { if (!skipValidation) { @@ -624,6 +335,7 @@ RED.deploy = (function() { }).done(function(data,textStatus,xhr) { RED.nodes.dirty(false); RED.nodes.version(data.rev); + RED.nodes.originalFlow(nns); if (hasUnusedConfig) { RED.notify( '

    '+RED._("deploy.successfulDeploy")+'

    '+ diff --git a/editor/js/ui/diff.js b/editor/js/ui/diff.js new file mode 100644 index 000000000..553e73cd0 --- /dev/null +++ b/editor/js/ui/diff.js @@ -0,0 +1,1278 @@ +RED.diff = (function() { + + var currentDiff = {}; + + 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"); + + + var dialog = $('
      ').appendTo(document.body); + + var toolbar = $('
      '+ + ' '+ + '
      ').prependTo(dialog); + + $("#node-dialog-view-diff").dialog({ + title: RED._('deploy.confirm.button.review'), + modal: true, + autoOpen: false, + buttons: [ + { + text: RED._("common.label.cancel"), + click: function() { + $( this ).dialog( "close" ); + } + }, + { + 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); + $( 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 remoteDiff = object.remoteDiff; + var tab = object.tab.n; + var def = object.def; + var conflicts = currentDiff.conflicts; + + var tabDiv = $('
      ',{class:"node-diff-tab"}).appendTo(container); + tabDiv.addClass('collapsed'); + var titleRow = $('
      ',{class:"node-diff-tab-title"}).appendTo(tabDiv); + var nodesDiv = $('
      ').appendTo(tabDiv); + var originalCell = $('
      ',{class:"node-diff-node-entry-cell"}).appendTo(titleRow); + var localCell = $('
      ',{class:"node-diff-node-entry-cell node-diff-node-local"}).appendTo(titleRow); + var remoteCell; + var selectState; + + if (remoteDiff) { + remoteCell = $('
      ',{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(titleRow); + } + $('').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') { + titleSpan.html(tabForLabel.label||tabForLabel.id); + } else if (tab.type === 'subflow') { + titleSpan.html((tabForLabel.name||tabForLabel.id)); + } else { + titleSpan.html("Global 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 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; + $(' ').appendTo(localNodeDiv); + } else if (localDiff.changed[tab.id]) { + localNodeDiv.addClass("node-diff-node-changed"); + localChanged = true; + $(' ').appendTo(localNodeDiv); + } else { + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(localNodeDiv); + } + + var remoteNodeDiv; + if (remoteDiff) { + remoteNodeDiv = $("
      ",{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; + $(' ').appendTo(remoteNodeDiv); + } else if (remoteDiff.changed[tab.id]) { + remoteNodeDiv.addClass("node-diff-node-changed"); + remoteChanged = true; + $(' ').appendTo(remoteNodeDiv); + } else { + remoteNodeDiv.addClass("node-diff-node-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,conflicts).appendTo(div); + selectState = ""; + if (conflicts[tab.id]) { + flowStats.conflicts++; + + if (!localNodeDiv.hasClass("node-diff-empty")) { + $('').prependTo(localNodeDiv); + } + if (!remoteNodeDiv.hasClass("node-diff-empty")) { + $('').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 = $('',{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]) { + $(' ').appendTo(localCell); + } else if (object.newTab) { + if (localDiff.added[tab.id]) { + $(' ').appendTo(localCell); + } else { + if (tab.id) { + if (localDiff.changed[tab.id]) { + flowStats.local.changedCount++; + } else { + flowStats.local.unchangedCount++; + } + } + var localStats = $('',{class:"node-diff-tab-stats"}).appendTo(localCell); + $('').html(RED._('diff.nodeCount',{count:localNodeCount})).appendTo(localStats); + + if (flowStats.conflicts + flowStats.local.addedCount + flowStats.local.changedCount + flowStats.local.deletedCount > 0) { + $(' [ ').appendTo(localStats); + if (flowStats.conflicts > 0) { + $(' '+flowStats.conflicts+'').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); + } + $(' ] ').appendTo(localStats); + } + + } + } else { + localCell.addClass("node-diff-empty"); + } + + if (remoteDiff) { + if (remoteDiff.deleted[tab.id]) { + $(' ').appendTo(remoteCell); + } else if (object.remoteTab) { + if (remoteDiff.added[tab.id]) { + $(' ').appendTo(remoteCell); + } else { + if (tab.id) { + if (remoteDiff.changed[tab.id]) { + flowStats.remote.changedCount++; + } else { + flowStats.remote.unchangedCount++; + } + } + var remoteStats = $('',{class:"node-diff-tab-stats"}).appendTo(remoteCell); + $('').html(RED._('diff.nodeCount',{count:remoteNodeCount})).appendTo(remoteStats); + if (flowStats.conflicts + flowStats.remote.addedCount + flowStats.remote.changedCount + flowStats.remote.deletedCount > 0) { + $(' [ ').appendTo(remoteStats); + if (flowStats.conflicts > 0) { + $(' '+flowStats.conflicts+'').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); + } + $(' ] ').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(); + } + }); + } + 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(list); + if (p && p.length > 0) { + $("").html(i+1).appendTo(port); + var links = $("
          ").appendTo(port); + p.forEach(function(d) { + 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) { + 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 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) { + 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 = $("
          ",{class:"node-diff-node-entry collapsed"}); + 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 node-diff-node-local"}).appendTo(row); + var remoteNodeDiv; + var chevron; + if (remoteDiff) { + remoteNodeDiv = $("
          ",{class:"node-diff-node-entry-cell node-diff-node-remote"}).appendTo(row); + } + $('').appendTo(originalNodeDiv); + + if (unChanged) { + stats.local.unchangedCount++; + createNode(node,def).appendTo(originalNodeDiv); + localNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').appendTo(localNodeDiv); + if (remoteDiff) { + stats.remote.unchangedCount++; + remoteNodeDiv.addClass("node-diff-node-unchanged"); + $(' ').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"); + } + $(' ').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"); + $(' ').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')}); + } + $(' '+localMovedMessage+'').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"); + $(' ').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"); + $(' ').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"); + $(' ').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')}); + } + $(' '+remoteMovedMessage+'').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"); + $(' ').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"); + $(' ').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"); + $(' ').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")) { + $('').prependTo(localNodeDiv); + } + if (!remoteNodeDiv.hasClass("node-diff-empty")) { + $('').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 localNode = localNodeObj.node; + var remoteNode; + if (remoteNodeObj) { + remoteNode = remoteNodeObj.node; + } + + var nodePropertiesDiv = $("
          ",{class:"node-diff-node-entry-properties"}); + var nodePropertiesTable = $("
      2. position"+currentPosition+""+newPosition+"
        "+d+''+formattedProperty+''+newFormattedProperty+"
        ").appendTo(nodePropertiesDiv); + var row; + var localCell, remoteCell; + var currentValue, localValue, remoteValue; + var localChanged = false; + var remoteChanged = false; + var localChanges = 0; + var remoteChanges = 0; + var conflict = false; + var status; + + row = $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); + $("").appendTo(nodePropertiesTable); + $("
        ",{class:"node-diff-property-cell-label"}).html("id").appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).appendTo(row); + if (localNode) { + localCell.addClass("node-diff-node-unchanged"); + $('').appendTo(localCell); + RED.utils.createObjectElement(localNode.id).appendTo(localCell); + } else { + localCell.addClass("node-diff-empty"); + } + if (remoteNode !== undefined) { + remoteCell = $("",{class:"node-diff-property-cell node-diff-node-remote"}).appendTo(row); + remoteCell.addClass("node-diff-node-unchanged"); + if (remoteNode) { + $('').appendTo(remoteCell); + RED.utils.createObjectElement(remoteNode.id).appendTo(remoteCell); + } 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 = $("
        ",{class:"node-diff-property-cell-label"}).html("position").appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).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 node-diff-node-remote"}).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')) { + 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 = $("
        ",{class:"node-diff-property-cell-label"}).html("wires").appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).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 node-diff-node-remote"}).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) { + 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 = $("
        ",{class:"node-diff-property-cell-label"}).html(d).appendTo(row); + localCell = $("",{class:"node-diff-property-cell node-diff-node-local"}).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 node-diff-node-remote"}).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"); + } + } + }); + 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 = $('