From 57533fd8310cdc0321b5a6915057cbca83349267 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Oct 2017 15:26:24 +0100 Subject: [PATCH] Add commit-diff view --- editor/js/ui/diff.js | 542 +++++++++++------- editor/js/ui/editor.js | 7 +- editor/js/ui/projectSettings.js | 3 - editor/js/ui/projects.js | 116 +++- editor/js/ui/tab-versionControl.js | 19 +- editor/js/ui/tray.js | 6 +- editor/sass/diff.scss | 53 +- editor/sass/projects.scss | 18 +- red/api/editor/projects/index.js | 43 +- red/runtime/storage/localfilesystem/index.js | 5 - .../localfilesystem/projects/Project.js | 35 +- .../projects/defaultFileSet.js | 33 +- .../localfilesystem/projects/git/index.js | 4 + .../storage/localfilesystem/projects/index.js | 6 +- 14 files changed, 617 insertions(+), 273 deletions(-) diff --git a/editor/js/ui/diff.js b/editor/js/ui/diff.js index 1f9a64f4a..171db8637 100644 --- a/editor/js/ui/diff.js +++ b/editor/js/ui/diff.js @@ -21,15 +21,9 @@ RED.diff = (function() { RED.keyboard.add("*","ctrl-shift-f 3","core:show-test-flow-diff-3"); } - - function buildDiffPanel(container) { - var diffPanel = $('
    ').appendTo(container); - - var toolbar = $('
    '+ - ' '+ - '
    ').prependTo(diffPanel); - - diffList = diffPanel.find("#node-dialog-view-diff-diff").editableList({ + function createDiffTable(container) { + var diffList = $('
      ').appendTo(container); + diffList.editableList({ addButton: false, scrollOnAdd: false, addItem: function(container,i,object) { @@ -290,7 +284,180 @@ RED.diff = (function() { container.i18n(); } }); - return diffPanel; + return diffList; + } + function buildDiffPanel(container,diff,options) { + var diffPanel = $('
      ').appendTo(container); + var diffHeaders = $('
      ').appendTo(diffPanel); + if (options.mode === "merge") { + diffPanel.addClass("node-dialog-view-diff-panel-merge"); + var toolbar = $('
      '+ + ' '+ + '
      ').prependTo(diffPanel); + } + var diffList = createDiffTable(diffPanel); + + var localDiff = diff.localDiff; + var remoteDiff = diff.remoteDiff; + var conflicts = diff.conflicts; + + var currentConfig = localDiff.currentConfig; + var newConfig = localDiff.newConfig; + + + if (remoteDiff !== undefined) { + diffPanel.addClass('node-diff-three-way'); + var localTitle = options.oldRevTitle || RED._('diff.local'); + var remoteTitle = options.newRevTitle || RED._('diff.remote'); + $('
      ').text(localTitle).appendTo(diffHeaders); + $('
      ').text(remoteTitle).appendTo(diffHeaders); + } else { + diffPanel.removeClass('node-diff-three-way'); + } + + return { + list: diffList, + finish: function() { + var el = { + diff: localDiff, + def: { + category: 'config', + color: '#f0f0f0' + }, + tab: { + n: {}, + nodes: currentConfig.globals + }, + newTab: { + n: {}, + nodes: newConfig.globals + } + }; + if (remoteDiff !== undefined) { + el.remoteTab = { + n:{}, + nodes:remoteDiff.newConfig.globals + }; + el.remoteDiff = remoteDiff; + } + diffList.editableList('addItem',el); + + var seenTabs = {}; + + currentConfig.tabOrder.forEach(function(tabId) { + var tab = currentConfig.tabs[tabId]; + var el = { + diff: localDiff, + def: RED.nodes.getType('tab'), + tab:tab + }; + if (newConfig.tabs.hasOwnProperty(tabId)) { + el.newTab = newConfig.tabs[tabId]; + } + if (remoteDiff !== undefined) { + el.remoteTab = remoteDiff.newConfig.tabs[tabId]; + el.remoteDiff = remoteDiff; + } + seenTabs[tabId] = true; + diffList.editableList('addItem',el) + }); + newConfig.tabOrder.forEach(function(tabId) { + if (!seenTabs[tabId]) { + seenTabs[tabId] = true; + var tab = newConfig.tabs[tabId]; + var el = { + diff: localDiff, + def: RED.nodes.getType('tab'), + tab:tab, + newTab: tab + }; + if (remoteDiff !== undefined) { + el.remoteDiff = remoteDiff; + } + diffList.editableList('addItem',el) + } + }); + if (remoteDiff !== undefined) { + remoteDiff.newConfig.tabOrder.forEach(function(tabId) { + if (!seenTabs[tabId]) { + var tab = remoteDiff.newConfig.tabs[tabId]; + // TODO how to recognise this is a remotely added flow + var el = { + diff: localDiff, + remoteDiff: remoteDiff, + def: RED.nodes.getType('tab'), + tab:tab, + remoteTab:tab + }; + diffList.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]; + } + if (remoteDiff !== undefined) { + el.remoteTab = remoteDiff.newConfig.subflows[subflowId]; + el.remoteDiff = remoteDiff; + } + diffList.editableList('addItem',el) + } + } + for (subflowId in newConfig.subflows) { + if (newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) { + seenTabs[subflowId] = true; + el = { + diff: localDiff, + def: { + defaults:{}, + icon:"subflow.png", + category: "subflows", + color: "#da9" + }, + tab:newConfig.subflows[subflowId], + newTab:newConfig.subflows[subflowId] + } + if (remoteDiff !== undefined) { + el.remoteDiff = remoteDiff; + } + diffList.editableList('addItem',el) + } + } + if (remoteDiff !== undefined) { + for (subflowId in remoteDiff.newConfig.subflows) { + if (remoteDiff.newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) { + el = { + diff: localDiff, + remoteDiff: remoteDiff, + def: { + defaults:{}, + icon:"subflow.png", + category: "subflows", + color: "#da9" + }, + tab:remoteDiff.newConfig.subflows[subflowId], + remoteTab: remoteDiff.newConfig.subflows[subflowId] + } + diffList.editableList('addItem',el) + } + } + } + } + }; } function formatWireProperty(wires,allNodes) { var result = $("
      ",{class:"node-diff-property-wires"}) @@ -917,7 +1084,7 @@ RED.diff = (function() { if (diff === undefined) { getRemoteDiff(showRemoteDiff); } else { - showDiff(diff); + showDiff(diff,{mode:'merge'}); } } function parseNodes(nodeList) { @@ -1054,10 +1221,12 @@ RED.diff = (function() { return diff; } - function showDiff(diff) { + function showDiff(diff,options) { if (diffVisible) { return; } + options = options || {}; + var mode = options.mode || 'merge'; var localDiff = diff.localDiff; var remoteDiff = diff.remoteDiff; @@ -1065,15 +1234,56 @@ RED.diff = (function() { currentDiff = diff; var trayOptions = { - title: "Review Changes", //TODO: nls + title: options.title||"Review Changes", //TODO: nls width: Infinity, + overlay: true, buttons: [ { - text: RED._("common.label.cancel"), + text: RED._((options.mode === 'merge')?"common.label.cancel":"common.label.close"), click: function() { RED.tray.close(); } - }, + } + ], + resize: function(dimensions) { + // trayWidth = dimensions.width; + }, + open: function(tray) { + var trayBody = tray.find('.editor-tray-body'); + var diffTable = buildDiffPanel(trayBody,diff,options); + diffTable.list.hide(); + if (remoteDiff) { + $("#node-diff-view-diff-merge").show(); + if (Object.keys(conflicts).length === 0) { + $("#node-diff-view-diff-merge").removeClass('disabled'); + } else { + $("#node-diff-view-diff-merge").addClass('disabled'); + } + } else { + $("#node-diff-view-diff-merge").hide(); + } + refreshConflictHeader(); + // console.log("--------------"); + // console.log(localDiff); + // console.log(remoteDiff); + + setTimeout(function() { + diffTable.finish(); + diffTable.list.show(); + },300); + $("#sidebar-shade").show(); + }, + close: function() { + diffVisible = false; + $("#sidebar-shade").hide(); + + }, + show: function() { + + } + } + if (options.mode === 'merge') { + trayOptions.buttons.push( { id: "node-diff-view-diff-merge", text: RED._("deploy.confirm.button.merge"), @@ -1086,189 +1296,9 @@ RED.diff = (function() { } } } - ], - resize: function(dimensions) { - // trayWidth = dimensions.width; - }, - open: function(tray) { - var trayBody = tray.find('.editor-tray-body'); - var diffPanel = buildDiffPanel(trayBody); - if (remoteDiff) { - $("#node-diff-view-diff-merge").show(); - if (Object.keys(conflicts).length === 0) { - $("#node-diff-view-diff-merge").removeClass('disabled'); - } else { - $("#node-diff-view-diff-merge").addClass('disabled'); - } - } else { - $("#node-diff-view-diff-merge").hide(); - } - refreshConflictHeader(); - - $("#node-dialog-view-diff-headers").empty(); - // console.log("--------------"); - // console.log(localDiff); - // console.log(remoteDiff); - var currentConfig = localDiff.currentConfig; - var newConfig = localDiff.newConfig; - conflicts = conflicts || {}; - - var el = { - diff: localDiff, - def: { - category: 'config', - color: '#f0f0f0' - }, - tab: { - n: {}, - nodes: currentConfig.globals - }, - newTab: { - n: {}, - nodes: newConfig.globals - } - }; - - if (remoteDiff !== undefined) { - diffPanel.addClass('node-diff-three-way'); - - $('
      ').i18n().appendTo("#node-dialog-view-diff-headers"); - el.remoteTab = { - n:{}, - nodes:remoteDiff.newConfig.globals - }; - el.remoteDiff = remoteDiff; - } else { - diffPanel.removeClass('node-diff-three-way'); - } - - diffList.editableList('addItem',el); - - var seenTabs = {}; - - currentConfig.tabOrder.forEach(function(tabId) { - var tab = currentConfig.tabs[tabId]; - var el = { - diff: localDiff, - def: RED.nodes.getType('tab'), - tab:tab - }; - if (newConfig.tabs.hasOwnProperty(tabId)) { - el.newTab = newConfig.tabs[tabId]; - } - if (remoteDiff !== undefined) { - el.remoteTab = remoteDiff.newConfig.tabs[tabId]; - el.remoteDiff = remoteDiff; - } - seenTabs[tabId] = true; - diffList.editableList('addItem',el) - }); - newConfig.tabOrder.forEach(function(tabId) { - if (!seenTabs[tabId]) { - seenTabs[tabId] = true; - var tab = newConfig.tabs[tabId]; - var el = { - diff: localDiff, - def: RED.nodes.getType('tab'), - tab:tab, - newTab: tab - }; - if (remoteDiff !== undefined) { - el.remoteDiff = remoteDiff; - } - diffList.editableList('addItem',el) - } - }); - if (remoteDiff !== undefined) { - remoteDiff.newConfig.tabOrder.forEach(function(tabId) { - if (!seenTabs[tabId]) { - var tab = remoteDiff.newConfig.tabs[tabId]; - // TODO how to recognise this is a remotely added flow - var el = { - diff: localDiff, - remoteDiff: remoteDiff, - def: RED.nodes.getType('tab'), - tab:tab, - remoteTab:tab - }; - diffList.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]; - } - if (remoteDiff !== undefined) { - el.remoteTab = remoteDiff.newConfig.subflows[subflowId]; - el.remoteDiff = remoteDiff; - } - diffList.editableList('addItem',el) - } - } - for (subflowId in newConfig.subflows) { - if (newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) { - seenTabs[subflowId] = true; - el = { - diff: localDiff, - def: { - defaults:{}, - icon:"subflow.png", - category: "subflows", - color: "#da9" - }, - tab:newConfig.subflows[subflowId], - newTab:newConfig.subflows[subflowId] - } - if (remoteDiff !== undefined) { - el.remoteDiff = remoteDiff; - } - diffList.editableList('addItem',el) - } - } - if (remoteDiff !== undefined) { - for (subflowId in remoteDiff.newConfig.subflows) { - if (remoteDiff.newConfig.subflows.hasOwnProperty(subflowId) && !seenTabs[subflowId]) { - el = { - diff: localDiff, - remoteDiff: remoteDiff, - def: { - defaults:{}, - icon:"subflow.png", - category: "subflows", - color: "#da9" - }, - tab:remoteDiff.newConfig.subflows[subflowId], - remoteTab: remoteDiff.newConfig.subflows[subflowId] - } - diffList.editableList('addItem',el) - } - } - } - $("#sidebar-shade").show(); - }, - close: function() { - diffVisible = false; - $("#sidebar-shade").hide(); - - }, - show: function() { - - } + ); } + RED.tray.show(trayOptions); } @@ -1374,7 +1404,7 @@ RED.diff = (function() { overlay: true, buttons: [ { - text: RED._("common.label.done"), + text: RED._("common.label.close"), click: function() { RED.tray.close(); } @@ -1646,7 +1676,7 @@ RED.diff = (function() { return string1 === string2 ? 0 : 1; } - function createUnifiedDiffTable(files) { + function createUnifiedDiffTable(files,commitOptions) { var diffPanel = $('
      '); files.forEach(function(file) { var hunks = file.hunks; @@ -1655,10 +1685,61 @@ RED.diff = (function() { $('').appendTo(codeTable); var codeBody = $('').appendTo(codeTable); - var diffRow = $('').appendTo(codeBody); - var content = $('').appendTo(diffRow); + var diffFileRow = $('').appendTo(codeBody); + var content = $('').appendTo(diffFileRow); + + var chevron = $('').appendTo(content); + diffFileRow.click(function(e) { + diffFileRow.toggleClass("collapsed"); + var isCollapsed = diffFileRow.hasClass("collapsed"); + diffFileRow.nextUntil(".node-text-diff-file-header").toggle(!isCollapsed); + }) var label = $('').text(file.file).appendTo(content); + if (commitOptions.project.files && commitOptions.project.files.flow === file.file) { + var tools = $('').appendTo(content); + $('').appendTo(tools).click(function(e) { + e.preventDefault(); + e.stopPropagation(); + var projectName = commitOptions.project.name; + var filename = commitOptions.project.files.flow; + var oldVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.oldRev+"/"+filename; + var newVersionUrl = "/projects/"+projectName+"/files/"+commitOptions.newRev+"/"+filename; + $.when($.getJSON(oldVersionUrl),$.getJSON(newVersionUrl)).done(function(oldVersion,newVersion) { + var oldFlow; + var newFlow; + try { + oldFlow = JSON.parse(oldVersion[0].content||"[]"); + } catch(err) { + console.log("Old Version doesn't contain valid JSON:",oldVersionUrl); + console.log(err); + return; + } + try { + newFlow = JSON.parse(newVersion[0].content||"[]"); + } catch(err) { + console.log("New Version doesn't contain valid JSON:",newFlow); + console.log(err); + return; + } + var localDiff = generateDiff(oldFlow,oldFlow); + var remoteDiff = generateDiff(oldFlow,newFlow); + var diff = resolveDiffs(localDiff,remoteDiff); + showDiff(diff,{ + title: filename, + mode: 'view', + oldRevTitle: commitOptions.oldRevTitle, + newRevTitle: commitOptions.newRevTitle + }); + // var flowDiffRow = $("").insertAfter(diffRow); + // var content = $('').appendTo(flowDiffRow); + // currentDiff = diff; + // var diffTable = buildDiffPanel(content,diff,{mode:"view"}).finish(); + }); + }) + } + + for (var i=0;i').appendTo(codeBody); var content = $('').appendTo(diffRow); @@ -1681,9 +1762,13 @@ RED.diff = (function() { $('').text(lineText[0]).appendTo(line); $('').text(lineText.substring(1)).appendTo(line); if (lineText[0] === '+') { + localLineNo.addClass("added"); + remoteLineNo.addClass("added"); line.addClass("added"); remoteLineNo.text(remoteLine++); } else if (lineText[0] === '-') { + localLineNo.addClass("removed"); + remoteLineNo.addClass("removed"); line.addClass("removed"); localLineNo.text(localLine++); } else { @@ -1701,15 +1786,15 @@ RED.diff = (function() { return diffPanel; } - function showCommitDiff(diff,title) { - var commit = parseCommitDiff(diff); + function showCommitDiff(options) { + var commit = parseCommitDiff(options.commit); var trayOptions = { - title: title||"Compare Changes", //TODO: nls + title: "View Commit Changes", //TODO: nls width: Infinity, overlay: true, buttons: [ { - text: RED._("common.label.done"), + text: RED._("common.label.close"), click: function() { RED.tray.close(); } @@ -1726,12 +1811,17 @@ RED.diff = (function() { $('').appendTo(codeTable); var codeBody = $('').appendTo(codeTable); - var diffRow = $('').appendTo(codeBody); + var diffRow = $('').appendTo(codeBody); var content = $('').appendTo(diffRow); - var label = $('
      ').text(commit.preamble).appendTo(content);
      +
      +                $("

      ").text(commit.title).appendTo(content); + $('
      ').text(commit.comment).appendTo(content); + var summary = $('
      ').appendTo(content); + $('
      ').text("Commit "+commit.sha).appendTo(summary); + $('
      ').text((commit.authorName||commit.author)+" - "+options.date).appendTo(summary); - createUnifiedDiffTable(commit.files).appendTo(diffPanel); + createUnifiedDiffTable(commit.files,options).appendTo(diffPanel); }, @@ -1744,7 +1834,10 @@ RED.diff = (function() { } RED.tray.show(trayOptions); } - function showUnifiedDiff(diff,title) { + function showUnifiedDiff(options) { + var diff = options.diff; + var title = options.title; + var files = parseUnifiedDiff(diff); var trayOptions = { title: title||"Compare Changes", //TODO: nls @@ -1752,7 +1845,7 @@ RED.diff = (function() { overlay: true, buttons: [ { - text: RED._("common.label.done"), + text: RED._("common.label.close"), click: function() { RED.tray.close(); } @@ -1764,7 +1857,7 @@ RED.diff = (function() { open: function(tray) { var trayBody = tray.find('.editor-tray-body'); var diffPanel = $('
      ').appendTo(trayBody); - createUnifiedDiffTable(files).appendTo(diffPanel); + createUnifiedDiffTable(files,options).appendTo(diffPanel); }, @@ -1779,16 +1872,35 @@ RED.diff = (function() { } function parseCommitDiff(diff) { - var result = { - }; + var result = {}; var lines = diff.split("\n"); + var comment = []; for (var i=0;i$/.exec(result.author); + if (m) { + result.authorName = m[1]; + result.authorEmail = m[2]; + } + } else if (/^Date: /.test(lines[i])) { + result.date = lines[i].substring(8); + } else if (/^ /.test(lines[i])) { + if (!result.title) { + result.title = lines[i].substring(4); + } else { + if (lines[i].length !== 4 || comment.length > 0) { + comment.push(lines[i].substring(4)); + } + } + } else if (/^diff /.test(lines[i])) { result.files = parseUnifiedDiff(lines.slice(i)); break; } } - result.preamble = lines.slice(0,i).join("\n"); + result.comment = comment.join("\n"); return result; } function parseUnifiedDiff(diff) { diff --git a/editor/js/ui/editor.js b/editor/js/ui/editor.js index c24af56fc..1ee025817 100644 --- a/editor/js/ui/editor.js +++ b/editor/js/ui/editor.js @@ -489,9 +489,10 @@ RED.editor = (function() { function getEditStackTitle() { var title = '
        '; - for (var i=0;i'; } title += '
      '; - return title; + return label; } function buildEditForm(container,formId,type,ns) { diff --git a/editor/js/ui/projectSettings.js b/editor/js/ui/projectSettings.js index 08015664e..55c6486e6 100644 --- a/editor/js/ui/projectSettings.js +++ b/editor/js/ui/projectSettings.js @@ -908,9 +908,6 @@ RED.projects.settings = (function() { },payload).always(function() { RED.deploy.setDeployInflight(false); }); - - - }); var updateForm = function() { if (activeProject.settings.credentialSecretInvalid) { diff --git a/editor/js/ui/projects.js b/editor/js/ui/projects.js index 4e946c4db..864bb6ba3 100644 --- a/editor/js/ui/projects.js +++ b/editor/js/ui/projects.js @@ -44,11 +44,13 @@ RED.projects = (function() { }, 'create': (function() { var projectNameInput; - var projectSummaryEditor; + var projectSummaryInput; + var projectFlowFileInput; var projectSecretInput; var projectSecretSelect; var copyProject; var projectRepoInput; + var emptyProjectCredentialInput; return { title: "Create a new project", // TODO: NLS @@ -84,6 +86,16 @@ RED.projects = (function() { projectRepoInput.removeClass("input-error"); } + } else if (projectType === 'empty') { + projectFlowFileInput.toggleClass("input-error",projectFlowFileInput.val()==='') + valid = valid && projectFlowFileInput.val()!==''; + var encryptionState = $("input[name=projects-encryption-type]:checked").val(); + if (encryptionState === 'enabled') { + var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val(); + if (encryptionKeyType === 'custom') { + valid = valid && emptyProjectCredentialInput.val()!=='' + } + } } $("#projects-dialog-create").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid); @@ -108,12 +120,79 @@ RED.projects = (function() { projectNameInput = $('').appendTo(row); var projectNameInputChanged = false; - projectNameInput.on("change keyup paste",function() { validateForm(); }); + projectNameInput.on("change keyup paste",function() { projectNameInputChanged = true; validateForm(); }); + $('').appendTo(row); // Empty Project row = $('
      ').appendTo(container); - $('').appendTo(row); - projectSummaryEditor = $('').appendTo(row); + $('').appendTo(row); + projectSummaryInput = $('').appendTo(row); + $('').appendTo(row); + + row = $('
      ').appendTo(container); + $('').appendTo(row); + projectFlowFileInput = $('').val("flow.json") + .on("change keyup paste",validateForm) + .appendTo(row); + $('').appendTo(row); + + row = $('
      ').appendTo(container); + $('').appendTo(row); + + var credentialsBox = $('
      ').appendTo(row); + var credentialsRightBox = $('
      ').appendTo(credentialsBox); + var credentialsLeftBox = $('
      ').appendTo(credentialsBox); + + var credentialsEnabledBox = $('
      ').appendTo(credentialsLeftBox); + $('').appendTo(credentialsEnabledBox); + var credentialsDisabledBox = $('
      ').appendTo(credentialsLeftBox); + $('').appendTo(credentialsDisabledBox); + + credentialsLeftBox.find("input[name=projects-encryption-type]").click(function(e) { + var val = $(this).val(); + var toEnable; + var toDisable; + if (val === 'enabled') { + toEnable = credentialsEnabledBox; + toDisable = credentialsDisabledBox; + $(".projects-encryption-enabled-row").show(); + $(".projects-encryption-disabled-row").hide(); + } else { + toDisable = credentialsEnabledBox; + toEnable = credentialsDisabledBox; + $(".projects-encryption-enabled-row").hide(); + $(".projects-encryption-disabled-row").show(); + + } + + toEnable.css({ + borderColor: "#ccc", + borderRightColor: "white" + }); + toDisable.css({ + borderColor: "white", + borderRightColor: "#ccc" + }) + validateForm(); + }) + + row = $('
      ').appendTo(credentialsRightBox); + $('').appendTo(row); + row = $('
      ').appendTo(credentialsRightBox); + $('').appendTo(row); + row = $('
      ').appendTo(credentialsRightBox); + emptyProjectCredentialInput = $('').appendTo(row); + emptyProjectCredentialInput.on("change keyup paste", validateForm); + + row = $('
      ').hide().appendTo(credentialsRightBox); + $('
      The credentials file will not be encrypted and its contents easily read
      ').appendTo(row); + + credentialsRightBox.find("input[name=projects-encryption-key]").click(function() { + var val = $(this).val(); + emptyProjectCredentialInput.attr("disabled",val === 'default'); + validateForm(); + }) + // Copy Project row = $('
      ').appendTo(container); @@ -151,11 +230,13 @@ RED.projects = (function() { validateForm(); }); - // Secret - empty/clone - row = $('
      ').appendTo(container); - $('').appendTo(row); + // Secret - clone + row = $('
      ').appendTo(container); + $('').appendTo(row); projectSecretInput = $('').appendTo(row); + createAsEmpty.click(); + return container; }, buttons: [ @@ -177,8 +258,25 @@ RED.projects = (function() { name: projectNameInput.val(), } if (projectType === 'empty') { - projectData.summary = projectSummaryEditor.val(); - projectData.credentialSecret = projectSecretInput.val(); + projectData.summary = projectSummaryInput.val(); + projectData.files = { + flow: projectFlowFileInput.val() + }; + var encryptionState = $("input[name=projects-encryption-type]:checked").val(); + if (encryptionState === 'enabled') { + var encryptionKeyType = $("input[name=projects-encryption-key]:checked").val(); + if (encryptionKeyType === 'custom') { + projectData.credentialSecret = emptyProjectCredentialInput.val(); + } else { + // If 'use default', leave projectData.credentialSecret blank - as that will trigger + // it to use the default (TODO: if its set...) + } + } else { + // Disabled encryption by explicitly setting credSec to false + projectData.credentialSecret = false; + } + + } else if (projectType === 'copy') { projectData.copy = copyProject.name; } else if (projectType === 'clone') { diff --git a/editor/js/ui/tab-versionControl.js b/editor/js/ui/tab-versionControl.js index a156ddf97..627ed1459 100644 --- a/editor/js/ui/tab-versionControl.js +++ b/editor/js/ui/tab-versionControl.js @@ -65,7 +65,16 @@ RED.sidebar.versionControl = (function() { // done(error,null); }, 200: function(data) { - RED.diff.showUnifiedDiff(data.diff,(unstaged?"Unstaged":"Staged")+" changes : "+entry.file); + var options = { + diff: data.diff, + title: (unstaged?"Unstaged":"Staged")+" changes : "+entry.file, + oldRevTitle: unstaged?(entry.indexStatus === " "?"HEAD":"Staged"):"HEAD", + newRevTitle: unstaged?"Unstaged":"Staged", + oldRev: unstaged?(entry.indexStatus === " "?"@":":0"):"@", + newRev: unstaged?"_":":0", + project: activeProject + } + RED.diff.showUnifiedDiff(options); // console.log(data.diff); }, 400: { @@ -336,7 +345,13 @@ RED.sidebar.versionControl = (function() { var activeProject = RED.projects.getActiveProject(); if (activeProject) { $.getJSON("/projects/"+activeProject.name+"/commits/"+entry.sha,function(result) { - RED.diff.showCommitDiff(result.commit); + result.project = activeProject; + result.oldRev = entry.sha+"~1"; + result.newRev = entry.sha; + result.oldRevTitle = "Commit "+entry.sha.substring(0,7)+"~1"; + result.newRevTitle = "Commit "+entry.sha.substring(0,7); + result.date = humanizeSinceDate(parseInt(entry.date)); + RED.diff.showCommitDiff(result); }); } }); diff --git a/editor/js/ui/tray.js b/editor/js/ui/tray.js index d8fa9f14b..f6351906e 100644 --- a/editor/js/ui/tray.js +++ b/editor/js/ui/tray.js @@ -32,7 +32,11 @@ RED.tray = (function() { // var growButton = $('').appendTo(resizer); // var shrinkButton = $('').appendTo(resizer); if (options.title) { - $('
      '+options.title+'
      ').appendTo(header); + var titles = stack.map(function(e) { return e.options.title }); + titles.push(options.title); + var title = '
      • '+titles.join("
      • ")+'
      '; + + $('
      '+title+'
      ').appendTo(header); } if (options.width === Infinity) { options.maximized = true; diff --git a/editor/sass/diff.scss b/editor/sass/diff.scss index a925f65bf..5429df6a4 100644 --- a/editor/sass/diff.scss +++ b/editor/sass/diff.scss @@ -15,15 +15,15 @@ **/ -#node-dialog-view-diff { +.node-dialog-view-diff-panel { .red-ui-editableList-container { border-radius:1px; padding:0; background: #f9f9f9; } - #node-dialog-view-diff-diff { + .node-dialog-view-diff-diff { position: absolute; - top:80px; + top:30px; bottom:10px; left:10px; right:10px; @@ -38,12 +38,22 @@ padding: 5px; // padding-bottom: 5px; } + &.node-dialog-view-diff-panel-merge { + .node-dialog-view-diff-diff { + top: 80px + } + .node-dialog-view-diff-headers { + top: 55px; + } + + } } -#node-dialog-view-diff-headers { + +.node-dialog-view-diff-headers { position: absolute; left:237px; right:18px; - top: 55px; + top: 5px; height: 25px; div { height: 25px; @@ -553,7 +563,6 @@ margin: 10px; border: 1px solid $secondary-border-color; border-radius: 3px; - font-family: monospace; table-layout: fixed; width: calc(100% - 20px); } @@ -562,6 +571,7 @@ word-wrap: break-word; } td.lineno { + font-family: monospace; text-align: right; color: #999; background: #fafafa; @@ -571,6 +581,7 @@ border-left: 1px solid $secondary-border-color; } td.linetext { + font-family: monospace; white-space: pre-wrap; padding: 1px 5px; span.prefix { @@ -602,12 +613,42 @@ border-bottom: 1px solid #f0f0f0; } tr.node-text-diff-file-header td { + font-family: monospace; + background: #f3f3f3; + padding: 5px 10px 5px 0; + color: #333; + cursor: pointer; + i.node-diff-chevron { + width: 30px; + } + } + tr.node-text-diff-file-header.collapsed { + td i.node-diff-chevron { + transform: rotate(-90deg); + } + } + tr.node-text-diff-commit-header td { background: #f3f3f3; padding: 5px 10px; color: #333; + h3 { + font-size: 1.4em; + margin: 0; + } + .commit-summary { + border-top: 1px solid $secondary-border-color; + padding-top: 5px; + color: #999; + } + .commit-body { + margin-bottom:15px; + white-space: pre; + line-height: 1.2em; + } } tr.node-text-diff-header td { + font-family: monospace; padding: 5px 10px; text-align: left; color: #666; diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss index d7467cca6..d2cecce0b 100644 --- a/editor/sass/projects.scss +++ b/editor/sass/projects.scss @@ -23,10 +23,23 @@ .projects-edit-form form { margin: 0; .form-row { - margin-bottom: 20px; + margin-bottom: 15px; label { - width: auto; + color: #555; + width: 100%; display: block; + font-weight: 500; + &.projects-edit-form-sublabel { + color: #999; + text-align: right; + margin-bottom: -15px; + font-weight: normal; + } + &.projects-edit-form-inline-label { + font-weight: normal; + color: inherit; + width: auto; + } } input[type=text], input[type=password],textarea { width: 100%; @@ -36,6 +49,7 @@ vertical-align: top; } } + } .projects-dialog-spinner { position: absolute; diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js index 59f83db9a..47c547b61 100644 --- a/red/api/editor/projects/index.js +++ b/red/api/editor/projects/index.js @@ -129,9 +129,24 @@ module.exports = { }) }); - app.post(/([^\/]+)\/stage\/(.+)$/, function(req,res) { - var projectName = req.params[0]; - var file = req.params[1]; + // /:project/files/:treeish/file-path + app.get("/:id/files/:treeish/*", function(req,res) { + var projectId = req.params.id; + var treeish = req.params.treeish; + var filePath = req.params[0]; + + runtime.storage.projects.getFile(projectId,filePath,treeish).then(function(data) { + res.json({content:data}); + }) + .catch(function(err) { + console.log(err.stack); + res.status(400).json({error:"unexpected_error", message:err.toString()}); + }) + }); + + app.post("/:id/stage/*", function(req,res) { + var projectName = req.params.id; + var file = req.params[0]; runtime.storage.projects.stageFile(projectName,file).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/files"); @@ -166,9 +181,9 @@ module.exports = { }) }); - app.delete(/([^\/]+)\/stage\/(.+)$/, function(req,res) { - var projectName = req.params[0]; - var file = req.params[1]; + app.delete("/:id/stage/*", function(req,res) { + var projectName = req.params.id; + var file = req.params[0]; runtime.storage.projects.unstageFile(projectName,file).then(function(data) { res.redirect(303,req.baseUrl+"/"+projectName+"/files"); @@ -189,10 +204,10 @@ module.exports = { }) }); - app.get(/([^\/]+)\/diff\/([^\/]+)\/(.+)$/, function(req,res) { - var projectName = req.params[0]; - var type = req.params[1]; - var file = req.params[2]; + app.get("/:id/diff/:type/*", function(req,res) { + var projectName = req.params.id; + var type = req.params.type; + var file = req.params[0]; runtime.storage.projects.getFileDiff(projectName,file,type).then(function(data) { res.json({ diff: data @@ -229,14 +244,6 @@ module.exports = { }) }); - app.get(new RegExp("/([^\/]+)\/files\/(.*)"), function(req,res) { - // Get project file - }); - - app.post(new RegExp("/([^\/]+)\/files\/(.*)"), function(req,res) { - // Update project file - }); - return app; } } diff --git a/red/runtime/storage/localfilesystem/index.js b/red/runtime/storage/localfilesystem/index.js index 3e2e6261e..2a57c7323 100644 --- a/red/runtime/storage/localfilesystem/index.js +++ b/red/runtime/storage/localfilesystem/index.js @@ -27,11 +27,6 @@ var projects = require("./projects"); var initialFlowLoadComplete = false; var settings; -var flowsFile; -var flowsFullPath; -var flowsFileBackup; -var credentialsFile; -var credentialsFileBackup; var localfilesystem = { init: function(_settings, runtime) { diff --git a/red/runtime/storage/localfilesystem/projects/Project.js b/red/runtime/storage/localfilesystem/projects/Project.js index 3c203ea4e..302da201d 100644 --- a/red/runtime/storage/localfilesystem/projects/Project.js +++ b/red/runtime/storage/localfilesystem/projects/Project.js @@ -212,6 +212,13 @@ Project.prototype.getCommits = function(options) { Project.prototype.getCommit = function(sha) { return gitTools.getCommit(this.path,sha); } +Project.prototype.getFile = function (filePath,treeish) { + if (treeish !== "_") { + return gitTools.getFile(this.path, filePath, treeish); + } else { + return fs.readFile(fspath.join(this.path,filePath),"utf8"); + } +}; Project.prototype.getFlowFile = function() { console.log("Project.getFlowFile = ",this.paths.flowFile); @@ -255,7 +262,16 @@ Project.prototype.toJSON = function () { } }; + +function getCredentialsFilename(filename) { + // TODO: DRY - ./index.js + var ffDir = fspath.dirname(filename); + var ffExt = fspath.extname(filename); + var ffBase = fspath.basename(filename,ffExt); + return fspath.join(ffDir,ffBase+"_cred"+ffExt); +} function getBackupFilename(filename) { + // TODO: DRY - ./index.js var ffName = fspath.basename(filename); var ffDir = fspath.dirname(filename); return fspath.join(ffDir,"."+ffName+".backup"); @@ -287,8 +303,23 @@ function createDefaultProject(project) { promises.push(util.writeFile(fspath.join(projectPath,file),defaultFileSet[file](project))); } } + if (project.files) { + if (project.files.flow && !/\.\./.test(project.files.flow)) { + var flowFilePath = fspath.join(projectPath,project.files.flow); + promises.push(util.writeFile(flowFilePath,"[]")); + var credsFilePath = getCredentialsFilename(flowFilePath); + promises.push(util.writeFile(credsFilePath,"{}")); + } + } return when.all(promises).then(function() { - return gitTools.stageFile(projectPath,Object.keys(defaultFileSet)); + var files = Object.keys(defaultFileSet); + if (project.files) { + if (project.files.flow && !/\.\./.test(project.files.flow)) { + files.push(project.files.flow); + files.push(getCredentialsFilename(flowFilePath)) + } + } + return gitTools.stageFile(projectPath,files); }).then(function() { return gitTools.commit(projectPath,"Create project"); }) @@ -339,7 +370,7 @@ function createProject(metadata) { createProjectDirectory(project).then(function() { var projects = settings.get('projects'); projects.projects[project] = {}; - if (metadata.credentialSecret) { + if (metadata.hasOwnProperty('credentialSecret')) { projects.projects[project].credentialSecret = metadata.credentialSecret; } if (metadata.remote) { diff --git a/red/runtime/storage/localfilesystem/projects/defaultFileSet.js b/red/runtime/storage/localfilesystem/projects/defaultFileSet.js index 0edd215b4..73a1d21c4 100644 --- a/red/runtime/storage/localfilesystem/projects/defaultFileSet.js +++ b/red/runtime/storage/localfilesystem/projects/defaultFileSet.js @@ -14,20 +14,43 @@ * limitations under the License. **/ + +var fspath = require("path"); + +function getCredentialsFilename(filename) { + // TODO: DRY - ./index.js + var ffDir = fspath.dirname(filename); + var ffExt = fspath.extname(filename); + var ffBase = fspath.basename(filename,ffExt); + return fspath.join(ffDir,ffBase+"_cred"+ffExt); +} + + module.exports = { "package.json": function(project) { - return JSON.stringify({ + var package = { "name": project.name, "description": project.summary||"A Node-RED Project", "version": "0.0.1", - "dependencies": {} - },"",4); + "dependencies": {}, + "node-red": { + "settings": { + } + } + }; + if (project.files) { + if (project.files.flow) { + package['node-red'].settings.flowFile = project.files.flow; + package['node-red'].settings.credentialsFile = getCredentialsFilename(project.files.flow); + } + } + return JSON.stringify(package,"",4); }, "README.md": function(project) { return project.name+"\n"+("=".repeat(project.name.length))+"\n\n"+(project.summary||"A Node-RED Project")+"\n\n"; }, "settings.json": function() { return "{}" }, - "flow.json": function() { return "[]" }, - "flow_cred.json": function() { return "{}" }, + // "flow.json": function() { return "[]" }, + // "flow_cred.json": function() { return "{}" }, ".gitignore": function() { return "*.backup" ;} } diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index bdc69ca94..370338fa1 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -215,6 +215,10 @@ module.exports = { return runCommand(gitCommand,args,cwd); }, getFiles: getFiles, + getFile: function(cwd, filePath, treeish) { + var args = ["show",treeish+":"+filePath]; + return runCommand(gitCommand,args,cwd); + }, stageFile: function(cwd,file) { var args = ["add"]; if (Array.isArray(file)) { diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index cb676393b..7dede9742 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -153,8 +153,9 @@ function getCommit(project,sha) { return activeProject.getCommit(sha); } -function getFile(project,path) { - +function getFile(project,filePath,sha) { + checkActiveProject(project); + return activeProject.getFile(filePath,sha); } function getActiveProject() { @@ -339,6 +340,7 @@ module.exports = { createProject: createProject, updateProject: updateProject, getFiles: getFiles, + getFile: getFile, stageFile: stageFile, unstageFile: unstageFile, commit: commit,