From b4be1184fda9c72f7a1cb9a693c3c19124464844 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Sun, 9 Oct 2016 22:02:24 +0100 Subject: [PATCH] Add v2 /flows api and deploy-overwrite protection --- editor/js/main.js | 5 +- editor/js/nodes.js | 50 ++- editor/js/settings.js | 1 + editor/js/ui/deploy.js | 388 ++++++++++++++++++++- editor/js/ui/workspaces.js | 1 + editor/sass/diff.scss | 165 +++++++++ editor/sass/jquery.scss | 5 + editor/sass/style.scss | 1 + editor/templates/index.mst | 7 +- red/api/flows.js | 42 ++- red/api/locales/en-US/editor.json | 7 +- red/runtime/nodes/flows/index.js | 70 ++-- red/runtime/storage/index.js | 13 +- test/red/api/flows_spec.js | 153 +++++++- test/red/runtime/nodes/flows/index_spec.js | 35 +- test/red/runtime/nodes/index_spec.js | 13 +- test/red/runtime/storage/index_spec.js | 1 + 17 files changed, 876 insertions(+), 81 deletions(-) create mode 100644 editor/sass/diff.scss diff --git a/editor/js/main.js b/editor/js/main.js index 58f6172bc..79fff2996 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -67,13 +67,14 @@ var RED = (function() { function loadFlows() { $.ajax({ headers: { - "Accept":"application/json" + "Accept":"application/json", }, cache: false, url: 'flows', success: function(nodes) { var currentHash = window.location.hash; - RED.nodes.import(nodes); + RED.nodes.version(nodes.rev); + RED.nodes.import(nodes.flows); RED.nodes.dirty(false); RED.view.redraw(true); if (/^#flow\/.+$/.test(currentHash)) { diff --git a/editor/js/nodes.js b/editor/js/nodes.js index 0dd69b32f..6f94ea9c0 100644 --- a/editor/js/nodes.js +++ b/editor/js/nodes.js @@ -23,11 +23,22 @@ RED.nodes = (function() { var workspaces = {}; var workspacesOrder =[]; var subflows = {}; + var loadedFlowVersion = null; + var pending = { + deleted: {}, + added: {} + }; var dirty = false; function setDirty(d) { dirty = d; + if (!d) { + pending = { + deleted: {}, + added: {} + }; + } RED.events.emit("nodes:change",{dirty:dirty}); } @@ -175,6 +186,8 @@ 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) { @@ -240,6 +253,12 @@ 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}; } @@ -252,6 +271,8 @@ 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:""} @@ -289,6 +310,8 @@ 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}; } @@ -318,6 +341,8 @@ 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, @@ -341,6 +366,8 @@ 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); } @@ -831,10 +858,11 @@ RED.nodes = (function() { } if (!existingConfigNode) { //} || !compareNodes(existingConfigNode,n,true) || existingConfigNode._def.exclusive || existingConfigNode.z !== n.z) { - configNode = {id:n.id, z:n.z, type:n.type, users:[]}; + configNode = {id:n.id, z:n.z, type:n.type, users:[], _config:{}}; for (d in def.defaults) { if (def.defaults.hasOwnProperty(d)) { configNode[d] = n[d]; + configNode._config[d] = JSON.stringify(n[d]); } } if (def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) { @@ -864,7 +892,7 @@ RED.nodes = (function() { if (n.type !== "workspace" && n.type !== "tab" && n.type !== "subflow") { def = registry.getNodeType(n.type); if (!def || def.category != "config") { - var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false}; + var node = {x:n.x,y:n.y,z:n.z,type:0,wires:n.wires,changed:false,_config:{}}; if (createNewIds) { if (subflow_blacklist[n.z]) { continue; @@ -947,8 +975,11 @@ RED.nodes = (function() { for (d in node._def.defaults) { if (node._def.defaults.hasOwnProperty(d)) { node[d] = n[d]; + node._config[d] = JSON.stringify(n[d]); } } + node._config.x = node.x; + node._config.y = node.y; if (node._def.hasOwnProperty('credentials') && n.hasOwnProperty('credentials')) { node.credentials = {}; for (d in node._def.credentials) { @@ -959,9 +990,6 @@ RED.nodes = (function() { } } } - if (node.credentials) { - console.log(node); - } addNode(node); RED.editor.validateNode(node); node_map[n.id] = node; @@ -1122,6 +1150,14 @@ RED.nodes = (function() { } } + function flowVersion(version) { + if (version !== undefined) { + loadedFlowVersion = version; + } else { + return loadedFlowVersion; + } + } + return { registry:registry, setNodeList: registry.setNodeList, @@ -1185,11 +1221,15 @@ RED.nodes = (function() { node: getNode, + version: flowVersion, + filterNodes: filterNodes, filterLinks: filterLinks, import: importNodes, + pending: function() { return pending }, + getAllFlowNodes: getAllFlowNodes, createExportableNodeSet: createExportableNodeSet, createCompleteNodeSet: createCompleteNodeSet, diff --git a/editor/js/settings.js b/editor/js/settings.js index f41dcb4e2..30aebba49 100644 --- a/editor/js/settings.js +++ b/editor/js/settings.js @@ -84,6 +84,7 @@ RED.settings = (function () { if (auth_tokens) { jqXHR.setRequestHeader("Authorization","Bearer "+auth_tokens.access_token); } + jqXHR.setRequestHeader("Node-RED-API-Version","v2"); } } }); diff --git a/editor/js/ui/deploy.js b/editor/js/ui/deploy.js index 310a0b880..9ac340fcd 100644 --- a/editor/js/ui/deploy.js +++ b/editor/js/ui/deploy.js @@ -35,6 +35,7 @@ RED.deploy = (function() { $("#btn-deploy img").attr("src",deploymentTypes[type].img); } + var currentDiff = null; /** * options: @@ -76,7 +77,7 @@ RED.deploy = (function() { $('#btn-deploy').click(function() { save(); }); $( "#node-dialog-confirm-deploy" ).dialog({ - title: "Confirm deploy", + title: RED._('deploy.confirm.button.confirm'), modal: true, autoOpen: false, width: 550, @@ -88,6 +89,15 @@ RED.deploy = (function() { $( this ).dialog( "close" ); } }, + // { + // id: "node-dialog-confirm-deploy-review", + // text: RED._("deploy.confirm.button.review"), + // class: "primary", + // click: function() { + // showDiff(); + // $( this ).dialog( "close" ); + // } + // }, { text: RED._("deploy.confirm.button.confirm"), class: "primary", @@ -97,7 +107,7 @@ RED.deploy = (function() { if (ignoreChecked) { ignoreDeployWarnings[$( "#node-dialog-confirm-deploy-type" ).val()] = true; } - save(true); + save(true,$( "#node-dialog-confirm-deploy-type" ).val() === "conflict"); $( this ).dialog( "close" ); } } @@ -109,6 +119,15 @@ RED.deploy = (function() { ''+ ''+ ''); + }, + open: function() { + if ($( "#node-dialog-confirm-deploy-type" ).val() === "conflict") { + // $("#node-dialog-confirm-deploy-review").show(); + $("#node-dialog-confirm-deploy-hide").parent().hide(); + } else { + // $("#node-dialog-confirm-deploy-review").hide(); + $("#node-dialog-confirm-deploy-hide").parent().show(); + } } }); @@ -123,6 +142,199 @@ RED.deploy = (function() { $("#btn-deploy").addClass("disabled"); } }); + + // $("#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) { @@ -160,11 +372,157 @@ RED.deploy = (function() { return 0; } - function save(force) { + function resolveConflict(currentNodes) { + $( "#node-dialog-confirm-deploy-config" ).hide(); + $( "#node-dialog-confirm-deploy-unknown" ).hide(); + $( "#node-dialog-confirm-deploy-unused" ).hide(); + $( "#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 (RED.nodes.dirty()) { //$("#debug-tab-clear").click(); // uncomment this to auto clear debug on deploy - if (!force) { + if (!skipValidation) { var hasUnknown = false; var hasInvalid = false; var hasUnusedConfig = false; @@ -196,6 +554,7 @@ RED.deploy = (function() { $( "#node-dialog-confirm-deploy-config" ).hide(); $( "#node-dialog-confirm-deploy-unknown" ).hide(); $( "#node-dialog-confirm-deploy-unused" ).hide(); + $( "#node-dialog-confirm-deploy-conflict" ).hide(); var showWarning = false; @@ -229,24 +588,28 @@ RED.deploy = (function() { } } - - - var nns = RED.nodes.createCompleteNodeSet(); $("#btn-deploy-icon").removeClass('fa-download'); $("#btn-deploy-icon").addClass('spinner'); - RED.nodes.dirty(false); + + var data = {flows:nns}; + + if (!force) { + data.rev = RED.nodes.version(); + } $.ajax({ url:"flows", type: "POST", - data: JSON.stringify(nns), + data: JSON.stringify(data), contentType: "application/json; charset=utf-8", headers: { "Node-RED-Deployment-Type":deploymentType } }).done(function(data,textStatus,xhr) { + RED.nodes.dirty(false); + RED.nodes.version(data.rev); if (hasUnusedConfig) { RED.notify( '

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

'+ @@ -264,10 +627,14 @@ RED.deploy = (function() { } }); RED.nodes.eachConfig(function (confNode) { + confNode.changed = false; if (confNode.credentials) { delete confNode.credentials; } }); + RED.nodes.eachWorkspace(function(ws) { + ws.changed = false; + }) // Once deployed, cannot undo back to a clean state RED.history.markAllDirty(); RED.view.redraw(); @@ -276,6 +643,8 @@ RED.deploy = (function() { RED.nodes.dirty(true); if (xhr.status === 401) { RED.notify(RED._("deploy.deployFailed",{message:RED._("user.notAuthorized")}),"error"); + } else if (xhr.status === 409) { + resolveConflict(nns); } else if (xhr.responseText) { RED.notify(RED._("deploy.deployFailed",{message:xhr.responseText}),"error"); } else { @@ -287,7 +656,6 @@ RED.deploy = (function() { }); } } - return { init: init } diff --git a/editor/js/ui/workspaces.js b/editor/js/ui/workspaces.js index e3947784c..f19e49543 100644 --- a/editor/js/ui/workspaces.js +++ b/editor/js/ui/workspaces.js @@ -93,6 +93,7 @@ RED.workspaces = (function() { node: workspace, dirty: RED.nodes.dirty() } + workspace.changed = true; RED.history.push(historyEvent); workspace_tabs.renameTab(workspace.id,label); RED.nodes.dirty(true); diff --git a/editor/sass/diff.scss b/editor/sass/diff.scss new file mode 100644 index 000000000..8c27f4ad4 --- /dev/null +++ b/editor/sass/diff.scss @@ -0,0 +1,165 @@ +/** + * Copyright 2016 IBM Corp. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + + +#node-dialog-view-diff { + height: 600px; + + .red-ui-editableList-container { + border-radius:1px; + padding:0; + } + ol { + position: absolute; + top:10px; + bottom:10px; + left:10px; + right:10px; + li { + padding: 0px; + border: none; + } + + } + .red-ui-editableList-item-content { + padding: 5px; + } + +} +.node-diff-tab { + border: 1px solid $secondary-border-color; + border-radius: 3px; + + &.collapsed { + .node-diff-tab-title > .node-diff-chevron { + transform: rotate(-90deg); + } + .node-diff-node-entry { + display: none; + } + } +} +.node-diff-tab-stats { + position: absolute; + left: 50%; +} + +.node-diff-chevron { + width: 15px; + text-align: center; + margin: 3px 5px 3px 5px; + transition: transform 0.1s ease-in-out; + +} +.node-diff-node-entry { + padding: 0 0 0 5px; + &:not(:last-child) { + border-bottom: 1px solid $secondary-border-color; + } + + &.collapsed { + .node-diff-chevron { + transform: rotate(-90deg); + } + .node-diff-node-entry-properties { + display: none; + } + } + + table { + border-collapse: collapse; + width: 100%; + table-layout:fixed; + } + td, th { + border: 1px solid $secondary-border-color; + padding: 3px 5px; + text-align: left; + } + + td:nth-child(1) { + width: 150px; + } + td:not(:first-child) { + width: calc(50% - 150px); + } +} +.node-diff-column { + display:inline-block; + height:100%; + width:50%; + box-sizing: border-box; + white-space:nowrap; + overflow: hidden; + &:first-child { + border-right: 1px solid $secondary-border-color + } +} +.node-diff-tab-title { + padding: 3px 3px 3px 0; + background: #f6f6f6; + cursor: pointer; +} + +.node-diff-node-entry-node { + vertical-align: middle; + display: inline-block; + margin: 5px; + width: 24px; + height: 20px; + background: #ddd; + border-radius: 2px; + border: 1px solid #999; + background-position: 5% 50%; + background-repeat: no-repeat; + background-size: contain; + position: relative; + + .palette-icon { + width: 16px; + } + .palette_icon_container { + width: 24px; + } +} +.node-diff-node-entry-title { + cursor: pointer; +} +.node-diff-node-entry-properties { + margin-left: 30px; + margin-right: 8px; + margin-bottom:8px; + color: #666; +} +.node-diff-node-description { + color: $form-text-color; + margin-left: 5px; + margin-right: 5px; + padding-top: 5px; + display: inline-block; + &:after { + content: ""; + display: table; + clear: both; + } +} + + +.node-diff-count { color: #999} +.node-diff-added { color: #009900} +.node-diff-deleted { color: #f80000} +.node-diff-changed { color: #f89406} +.node-diff-conflicted { color: purple} diff --git a/editor/sass/jquery.scss b/editor/sass/jquery.scss index 67aa22ac7..02356bf4b 100644 --- a/editor/sass/jquery.scss +++ b/editor/sass/jquery.scss @@ -99,6 +99,11 @@ background: $editor-button-background-primary-hover; color: $editor-button-color-primary !important; } + &.disabled { + border-color: $form-input-border-color; + color: $workspace-button-color-disabled !important; + background: $editor-button-background; + } } &.disabled { background: none; diff --git a/editor/sass/style.scss b/editor/sass/style.scss index 6b2e738be..a21bf4c5a 100644 --- a/editor/sass/style.scss +++ b/editor/sass/style.scss @@ -42,6 +42,7 @@ @import "popover"; @import "flow"; @import "palette-editor"; +@import "diff"; @import "ui/common/editableList"; diff --git a/editor/templates/index.mst b/editor/templates/index.mst index c66b03845..6fa473c93 100644 --- a/editor/templates/index.mst +++ b/editor/templates/index.mst @@ -6,7 +6,7 @@
position"+currentPosition+""+newPosition+"
"+d+''+formattedProperty+''+newFormattedProperty+"