mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Add initial version control sidebar with commit function
This commit is contained in:
parent
522f7e6844
commit
9a2fd0e2b2
@ -155,6 +155,7 @@ module.exports = function(grunt) {
|
|||||||
"editor/js/ui/userSettings.js",
|
"editor/js/ui/userSettings.js",
|
||||||
"editor/js/ui/projects.js",
|
"editor/js/ui/projects.js",
|
||||||
"editor/js/ui/projectSettings.js",
|
"editor/js/ui/projectSettings.js",
|
||||||
|
"editor/js/ui/tab-versionControl.js",
|
||||||
"editor/js/ui/touch/radialMenu.js"
|
"editor/js/ui/touch/radialMenu.js"
|
||||||
],
|
],
|
||||||
dest: "public/red/red.js"
|
dest: "public/red/red.js"
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
"ctrl-alt-n": "core:new-project",
|
"ctrl-alt-n": "core:new-project",
|
||||||
"ctrl-alt-o": "core:open-project",
|
"ctrl-alt-o": "core:open-project",
|
||||||
"ctrl-g p": "core:show-projects-tab"
|
"ctrl-g v": "core:show-version-control-tab"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"backspace": "core:delete-selection",
|
"backspace": "core:delete-selection",
|
||||||
|
@ -482,7 +482,7 @@ RED.projects = (function() {
|
|||||||
RED.actions.add("core:open-project",RED.projects.selectProject);
|
RED.actions.add("core:open-project",RED.projects.selectProject);
|
||||||
|
|
||||||
RED.projects.settings.init({sendRequest:sendRequest});
|
RED.projects.settings.init({sendRequest:sendRequest});
|
||||||
|
RED.sidebar.versionControl.init({sendRequest:sendRequest});
|
||||||
initScreens();
|
initScreens();
|
||||||
// initSidebar();
|
// initSidebar();
|
||||||
}
|
}
|
||||||
|
436
editor/js/ui/tab-versionControl.js
Normal file
436
editor/js/ui/tab-versionControl.js
Normal file
@ -0,0 +1,436 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
RED.sidebar.versionControl = (function() {
|
||||||
|
|
||||||
|
var content;
|
||||||
|
var sections;
|
||||||
|
|
||||||
|
var allChanges = {};
|
||||||
|
|
||||||
|
var unstagedChangesList;
|
||||||
|
var stageAllButton;
|
||||||
|
var stagedChangesList;
|
||||||
|
var unstageAllButton;
|
||||||
|
var unstagedChanges;
|
||||||
|
var stagedChanges;
|
||||||
|
var bulkChangeSpinner;
|
||||||
|
var commitButton;
|
||||||
|
|
||||||
|
// TODO: DRY projectSummary.js
|
||||||
|
function addSpinnerOverlay(container) {
|
||||||
|
var spinner = $('<div class="projects-dialog-spinner"><img src="red/images/spin.svg"/></div>').appendTo(container);
|
||||||
|
return spinner;
|
||||||
|
}
|
||||||
|
function createChangeEntry(row, entry, status, unstaged) {
|
||||||
|
row.addClass("sidebar-version-control-change-entry");
|
||||||
|
var container = $('<div>').appendTo(row);
|
||||||
|
var icon = $('<i class=""></i>').appendTo(container);
|
||||||
|
var label = $('<span>').appendTo(container);
|
||||||
|
|
||||||
|
var bg = $('<div class="button-group"></div>').appendTo(row);
|
||||||
|
$('<button class="editor-button editor-button-small"><i class="fa fa-eye"></i></button>').appendTo(bg);
|
||||||
|
$('<button class="editor-button editor-button-small"><i class="fa fa-'+(unstaged?"plus":"minus")+'"></i></button>')
|
||||||
|
.appendTo(bg)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var activeProject = RED.projects.getActiveProject();
|
||||||
|
entry.spinner = addSpinnerOverlay(row).addClass('projects-version-control-spinner-sidebar');
|
||||||
|
utils.sendRequest({
|
||||||
|
url: "projects/"+activeProject.name+"/stage/"+encodeURIComponent(entry.file),
|
||||||
|
type: unstaged?"POST":"DELETE",
|
||||||
|
responses: {
|
||||||
|
0: function(error) {
|
||||||
|
console.log(error);
|
||||||
|
// done(error,null);
|
||||||
|
},
|
||||||
|
200: function(data) {
|
||||||
|
refreshFiles(data);
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'unexpected_error': function(error) {
|
||||||
|
console.log(error);
|
||||||
|
// done(error,null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},{});
|
||||||
|
});
|
||||||
|
entry["update"+(unstaged?"Unstaged":"Staged")] = function(entry,status) {
|
||||||
|
container.removeClass();
|
||||||
|
var iconClass = "";
|
||||||
|
if (status === 'A') {
|
||||||
|
container.addClass("node-diff-added");
|
||||||
|
iconClass = "fa-plus-square";
|
||||||
|
} else if (status === '?') {
|
||||||
|
container.addClass("node-diff-unchanged");
|
||||||
|
iconClass = "fa-question-circle-o";
|
||||||
|
} else if (status === 'D') {
|
||||||
|
container.addClass("node-diff-deleted");
|
||||||
|
iconClass = "fa-minus-square";
|
||||||
|
} else if (status === 'M') {
|
||||||
|
container.addClass("node-diff-changed");
|
||||||
|
iconClass = "fa-square";
|
||||||
|
} else if (status === 'R') {
|
||||||
|
container.addClass("node-diff-changed");
|
||||||
|
iconClass = "fa-toggle-right";
|
||||||
|
} else {
|
||||||
|
iconClass = "fa-exclamation-triangle"
|
||||||
|
}
|
||||||
|
label.empty();
|
||||||
|
$('<span>').text(entry.file.replace(/\\(.)/g,"$1")).appendTo(label);
|
||||||
|
|
||||||
|
if (entry.oldName) {
|
||||||
|
$('<i class="fa fa-long-arrow-right"></i>').prependTo(label);
|
||||||
|
$('<span>').text(entry.oldName.replace(/\\(.)/g,"$1")).prependTo(label);
|
||||||
|
// label.text(entry.oldName+" -> "+entry.file);
|
||||||
|
}
|
||||||
|
// console.log(entry.file,status,iconClass);
|
||||||
|
|
||||||
|
icon.removeClass();
|
||||||
|
icon.addClass("fa "+iconClass);
|
||||||
|
if (entry.spinner) {
|
||||||
|
entry.spinner.remove();
|
||||||
|
delete entry.spinner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry["update"+(unstaged?"Unstaged":"Staged")](entry, status);
|
||||||
|
}
|
||||||
|
var utils;
|
||||||
|
function init(_utils) {
|
||||||
|
utils = _utils;
|
||||||
|
|
||||||
|
RED.actions.add("core:show-version-control-tab",show);
|
||||||
|
|
||||||
|
content = $('<div>', {class:"sidebar-version-control"});
|
||||||
|
var stackContainer = $("<div>",{class:"sidebar-version-control-stack"}).appendTo(content);
|
||||||
|
sections = RED.stack.create({
|
||||||
|
container: stackContainer,
|
||||||
|
fill: true,
|
||||||
|
singleExpanded: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var localChanges = sections.add({
|
||||||
|
title: "Local Changes",
|
||||||
|
collapsible: true
|
||||||
|
});
|
||||||
|
localChanges.expand();
|
||||||
|
localChanges.content.css({height:"100%"});
|
||||||
|
|
||||||
|
var bg = $('<div style="float: right"></div>').appendTo(localChanges.header);
|
||||||
|
$('<button class="sidebar-header-button"><i class="fa fa-refresh"></i></button>')
|
||||||
|
.appendTo(bg)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
refresh(true);
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
var unstagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
|
||||||
|
var header = $('<div class="sidebar-version-control-change-header">Unstaged Changes</div>').appendTo(unstagedContent);
|
||||||
|
stageAllButton = $('<button class="editor-button editor-button-small" style="float: right"><i class="fa fa-plus"></i> all</button>')
|
||||||
|
.appendTo(header)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
var toStage = Object.keys(allChanges).filter(function(fn) {
|
||||||
|
return allChanges[fn].treeStatus !== ' ';
|
||||||
|
});
|
||||||
|
updateBulk(toStage,true);
|
||||||
|
});
|
||||||
|
unstagedChangesList = $("<ol>",{style:"position: absolute; top: 30px; bottom: 0; right:0; left:0;"}).appendTo(unstagedContent);
|
||||||
|
unstagedChangesList.editableList({
|
||||||
|
addButton: false,
|
||||||
|
scrollOnAdd: false,
|
||||||
|
addItem: function(row,index,entry) {
|
||||||
|
createChangeEntry(row,entry,entry.treeStatus,true);
|
||||||
|
},
|
||||||
|
sort: function(A,B) {
|
||||||
|
if (A.treeStatus === '?' && B.treeStatus !== '?') {
|
||||||
|
return 1;
|
||||||
|
} else if (A.treeStatus !== '?' && B.treeStatus === '?') {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return A.file.localeCompare(B.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
var stagedContent = $('<div class="sidebar-version-control-change-container"></div>').appendTo(localChanges.content);
|
||||||
|
|
||||||
|
var header = $('<div class="sidebar-version-control-change-header">Staged Changes</div>').appendTo(stagedContent);
|
||||||
|
|
||||||
|
bg = $('<div style="float: right"></div>').appendTo(header);
|
||||||
|
commitButton = $('<button class="editor-button editor-button-small" style="margin-right: 5px;">commit</button>')
|
||||||
|
.appendTo(bg)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
commitMessage.val("");
|
||||||
|
submitCommitButton.attr("disabled",true);
|
||||||
|
unstagedContent.css("height","30px");
|
||||||
|
stagedContent.css("height","calc(100% - 30px - 175px)");
|
||||||
|
commitBox.css("height","175px");
|
||||||
|
stageAllButton.attr("disabled",true);
|
||||||
|
unstageAllButton.attr("disabled",true);
|
||||||
|
commitButton.attr("disabled",true);
|
||||||
|
commitMessage.focus();
|
||||||
|
});
|
||||||
|
unstageAllButton = $('<button class="editor-button editor-button-small"><i class="fa fa-minus"></i> all</button>')
|
||||||
|
.appendTo(bg)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
evt.stopPropagation();
|
||||||
|
var toUnstage = Object.keys(allChanges).filter(function(fn) {
|
||||||
|
return allChanges[fn].indexStatus !== ' ' && allChanges[fn].indexStatus !== '?';
|
||||||
|
});
|
||||||
|
updateBulk(toUnstage,false);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
stagedChangesList = $("<ol>",{style:"position: absolute; top: 30px; bottom: 0; right:0; left:0;"}).appendTo(stagedContent);
|
||||||
|
stagedChangesList.editableList({
|
||||||
|
addButton: false,
|
||||||
|
scrollOnAdd: false,
|
||||||
|
addItem: function(row,index,entry) {
|
||||||
|
createChangeEntry(row,entry,entry.indexStatus,false);
|
||||||
|
},
|
||||||
|
sort: function(A,B) {
|
||||||
|
return A.file.localeCompare(B.file);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
commitBox = $('<div class="sidebar-version-control-change-commit-box"></div>').appendTo(localChanges.content);
|
||||||
|
|
||||||
|
var commitMessage = $('<textarea>')
|
||||||
|
.appendTo(commitBox)
|
||||||
|
.on("change keyup paste",function() {
|
||||||
|
submitCommitButton.attr('disabled',$(this).val().trim()==="");
|
||||||
|
});
|
||||||
|
var commitToolbar = $('<div class="sidebar-version-control-change-commit-toolbar button-group">').appendTo(commitBox);
|
||||||
|
|
||||||
|
var cancelCommitButton = $('<button class="editor-button">Cancel</button>')
|
||||||
|
.appendTo(commitToolbar)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
commitMessage.val("");
|
||||||
|
unstagedContent.css("height","");
|
||||||
|
stagedContent.css("height","");
|
||||||
|
commitBox.css("height","");
|
||||||
|
stageAllButton.attr("disabled",false);
|
||||||
|
unstageAllButton.attr("disabled",false);
|
||||||
|
commitButton.attr("disabled",false);
|
||||||
|
})
|
||||||
|
var submitCommitButton = $('<button class="editor-button">Commit</button>')
|
||||||
|
.appendTo(commitToolbar)
|
||||||
|
.click(function(evt) {
|
||||||
|
evt.preventDefault();
|
||||||
|
var spinner = addSpinnerOverlay(submitCommitButton).addClass('projects-dialog-spinner-sidebar');
|
||||||
|
var activeProject = RED.projects.getActiveProject();
|
||||||
|
utils.sendRequest({
|
||||||
|
url: "projects/"+activeProject.name+"/commit",
|
||||||
|
type: "POST",
|
||||||
|
responses: {
|
||||||
|
0: function(error) {
|
||||||
|
console.log(error);
|
||||||
|
},
|
||||||
|
200: function(data) {
|
||||||
|
spinner.remove();
|
||||||
|
cancelCommitButton.click();
|
||||||
|
refreshFiles(data);
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'unexpected_error': function(error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},{
|
||||||
|
message:commitMessage.val()
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
var localHistory = sections.add({
|
||||||
|
title: "Commit History",
|
||||||
|
collapsible: true
|
||||||
|
});
|
||||||
|
|
||||||
|
var remoteHistory = sections.add({
|
||||||
|
title: "Remote History",
|
||||||
|
collapsible: true
|
||||||
|
});
|
||||||
|
|
||||||
|
RED.sidebar.addTab({
|
||||||
|
id: "version-control",
|
||||||
|
label: "version control",
|
||||||
|
name: "Version Control",
|
||||||
|
content: content,
|
||||||
|
enableOnEdit: false,
|
||||||
|
onchange: function() {
|
||||||
|
setTimeout(function() {
|
||||||
|
sections.resize();
|
||||||
|
},10);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateBulk(files,unstaged) {
|
||||||
|
var activeProject = RED.projects.getActiveProject();
|
||||||
|
if (unstaged) {
|
||||||
|
bulkChangeSpinner = addSpinnerOverlay(unstagedChangesList.parent());
|
||||||
|
} else {
|
||||||
|
bulkChangeSpinner = addSpinnerOverlay(stagedChangesList.parent());
|
||||||
|
}
|
||||||
|
bulkChangeSpinner.addClass('projects-dialog-spinner-sidebar');
|
||||||
|
var body = unstaged?{files:files}:undefined;
|
||||||
|
utils.sendRequest({
|
||||||
|
url: "projects/"+activeProject.name+"/stage",
|
||||||
|
type: unstaged?"POST":"DELETE",
|
||||||
|
responses: {
|
||||||
|
0: function(error) {
|
||||||
|
console.log(error);
|
||||||
|
// done(error,null);
|
||||||
|
},
|
||||||
|
200: function(data) {
|
||||||
|
refreshFiles(data);
|
||||||
|
},
|
||||||
|
400: {
|
||||||
|
'unexpected_error': function(error) {
|
||||||
|
console.log(error);
|
||||||
|
// done(error,null);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},body);
|
||||||
|
}
|
||||||
|
|
||||||
|
var refreshInProgress = false;
|
||||||
|
|
||||||
|
function refreshFiles(result) {
|
||||||
|
if (bulkChangeSpinner) {
|
||||||
|
bulkChangeSpinner.remove();
|
||||||
|
bulkChangeSpinner = null;
|
||||||
|
}
|
||||||
|
// unstagedChangesList.editableList('empty');
|
||||||
|
// stagedChangesList.editableList('empty');
|
||||||
|
var fileNames = Object.keys(result).filter(function(f) { return result[f].type === 'f'})
|
||||||
|
fileNames.sort();
|
||||||
|
var updateIndex = Date.now()+Math.floor(Math.random()*100);
|
||||||
|
fileNames.forEach(function(fn) {
|
||||||
|
var entry = result[fn];
|
||||||
|
var addEntry = false;
|
||||||
|
if (entry.status) {
|
||||||
|
entry.file = fn;
|
||||||
|
entry.indexStatus = entry.status[0];
|
||||||
|
entry.treeStatus = entry.status[1];
|
||||||
|
if (allChanges[fn]) {
|
||||||
|
// Known file
|
||||||
|
if (allChanges[fn].status !== entry.status) {
|
||||||
|
// Status changed.
|
||||||
|
if (allChanges[fn].treeStatus !== ' ') {
|
||||||
|
// Already in the unstaged list
|
||||||
|
if (entry.treeStatus === ' ') {
|
||||||
|
unstagedChangesList.editableList('removeItem', allChanges[fn])
|
||||||
|
} else if (entry.treeStatus !== allChanges[fn].treeStatus) {
|
||||||
|
allChanges[fn].updateUnstaged(entry,entry.treeStatus);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEntry = true;
|
||||||
|
}
|
||||||
|
if (allChanges[fn].indexStatus !== ' ' && allChanges[fn].indexStatus !== '?') {
|
||||||
|
// Already in the staged list
|
||||||
|
if (entry.indexStatus === ' '||entry.indexStatus === '?') {
|
||||||
|
stagedChangesList.editableList('removeItem', allChanges[fn])
|
||||||
|
} else if (entry.indexStatus !== allChanges[fn].indexStatus) {
|
||||||
|
allChanges[fn].updateStaged(entry,entry.indexStatus);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEntry = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
allChanges[fn].status = entry.status;
|
||||||
|
allChanges[fn].indexStatus = entry.indexStatus;
|
||||||
|
allChanges[fn].treeStatus = entry.treeStatus;
|
||||||
|
allChanges[fn].oldName = entry.oldName;
|
||||||
|
} else {
|
||||||
|
addEntry = true;
|
||||||
|
allChanges[fn] = entry;
|
||||||
|
}
|
||||||
|
allChanges[fn].updateIndex = updateIndex;
|
||||||
|
if (addEntry) {
|
||||||
|
if (entry.treeStatus !== ' ') {
|
||||||
|
unstagedChangesList.editableList('addItem', allChanges[fn])
|
||||||
|
}
|
||||||
|
if (entry.indexStatus !== ' ' && entry.indexStatus !== '?') {
|
||||||
|
stagedChangesList.editableList('addItem', allChanges[fn])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.keys(allChanges).forEach(function(fn) {
|
||||||
|
if (allChanges[fn].updateIndex !== updateIndex) {
|
||||||
|
unstagedChangesList.editableList('removeItem', allChanges[fn]);
|
||||||
|
stagedChangesList.editableList('removeItem', allChanges[fn]);
|
||||||
|
delete allChanges[fn];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var stagedCount = stagedChangesList.editableList('length');
|
||||||
|
var unstagedCount = unstagedChangesList.editableList('length');
|
||||||
|
commitButton.attr('disabled',stagedCount === 0);
|
||||||
|
stageAllButton.attr('disabled',unstagedCount === 0);
|
||||||
|
unstageAllButton.attr('disabled',stagedCount === 0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh(full) {
|
||||||
|
if (refreshInProgress) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (full) {
|
||||||
|
allChanges = {};
|
||||||
|
unstagedChangesList.editableList('empty');
|
||||||
|
stagedChangesList.editableList('empty');
|
||||||
|
}
|
||||||
|
refreshInProgress = true;
|
||||||
|
|
||||||
|
var activeProject = RED.projects.getActiveProject();
|
||||||
|
if (activeProject) {
|
||||||
|
$.getJSON("/projects/"+activeProject.name+"/files",function(result) {
|
||||||
|
refreshFiles(result);
|
||||||
|
refreshInProgress = false;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
unstagedChangesList.editableList('empty');
|
||||||
|
stagedChangesList.editableList('empty');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
refresh();
|
||||||
|
RED.sidebar.show("version-control");
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
init: init,
|
||||||
|
show: show,
|
||||||
|
refresh: refresh
|
||||||
|
}
|
||||||
|
})();
|
@ -357,6 +357,7 @@
|
|||||||
.node-diff-added { color: #009900}
|
.node-diff-added { color: #009900}
|
||||||
.node-diff-deleted { color: #f80000}
|
.node-diff-deleted { color: #f80000}
|
||||||
.node-diff-changed { color: #f89406}
|
.node-diff-changed { color: #f89406}
|
||||||
|
.node-diff-unchanged { color: #bbb}
|
||||||
.node-diff-conflicted { color: purple}
|
.node-diff-conflicted { color: purple}
|
||||||
|
|
||||||
|
|
||||||
|
@ -213,7 +213,7 @@
|
|||||||
height: 34px;
|
height: 34px;
|
||||||
line-height: 32px;
|
line-height: 32px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
border-radius: 4px;
|
border-radius: 2px;
|
||||||
padding: 0 10px;
|
padding: 0 10px;
|
||||||
&.toggle {
|
&.toggle {
|
||||||
@include workspace-button-toggle;
|
@include workspace-button-toggle;
|
||||||
|
@ -97,6 +97,23 @@
|
|||||||
&:focus {
|
&:focus {
|
||||||
outline: 1px solid $workspace-button-color-focus-outline;
|
outline: 1px solid $workspace-button-color-focus-outline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.primary {
|
||||||
|
border-color: $editor-button-background-primary;
|
||||||
|
color: $editor-button-color-primary !important;
|
||||||
|
background: $editor-button-background-primary;
|
||||||
|
&.disabled, &.ui-state-disabled {
|
||||||
|
background: none;
|
||||||
|
color: $editor-button-color !important;
|
||||||
|
border-color: $form-input-border-color;
|
||||||
|
}
|
||||||
|
&:not(.disabled):not(.ui-button-disabled):hover {
|
||||||
|
border-color: $editor-button-background-primary-hover;
|
||||||
|
background: $editor-button-background-primary-hover;
|
||||||
|
color: $editor-button-color-primary !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.button-group-vertical {
|
.button-group-vertical {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@ -132,21 +149,21 @@
|
|||||||
color: $editor-button-color !important;
|
color: $editor-button-color !important;
|
||||||
background: $editor-button-background;
|
background: $editor-button-background;
|
||||||
|
|
||||||
&.primary {
|
// &.primary {
|
||||||
border-color: $editor-button-background-primary;
|
// border-color: $editor-button-background-primary;
|
||||||
color: $editor-button-color-primary !important;
|
// color: $editor-button-color-primary !important;
|
||||||
background: $editor-button-background-primary;
|
// background: $editor-button-background-primary;
|
||||||
&.disabled, &.ui-state-disabled {
|
// &.disabled, &.ui-state-disabled {
|
||||||
background: none;
|
// background: none;
|
||||||
color: $editor-button-color !important;
|
// color: $editor-button-color !important;
|
||||||
border-color: $form-input-border-color;
|
// border-color: $form-input-border-color;
|
||||||
}
|
// }
|
||||||
&:not(.disabled):not(.ui-button-disabled):hover {
|
// &:not(.disabled):not(.ui-button-disabled):hover {
|
||||||
border-color: $editor-button-background-primary-hover;
|
// border-color: $editor-button-background-primary-hover;
|
||||||
background: $editor-button-background-primary-hover;
|
// background: $editor-button-background-primary-hover;
|
||||||
color: $editor-button-color-primary !important;
|
// color: $editor-button-color-primary !important;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
&:not(.disabled):hover {
|
&:not(.disabled):hover {
|
||||||
//color: $editor-button-color;
|
//color: $editor-button-color;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,7 @@
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
}
|
}
|
||||||
.palette-header i {
|
.palette-header > i {
|
||||||
margin: 3px 10px 3px 3px;
|
margin: 3px 10px 3px 3px;
|
||||||
-webkit-transition: all 0.2s ease-in-out;
|
-webkit-transition: all 0.2s ease-in-out;
|
||||||
-moz-transition: all 0.2s ease-in-out;
|
-moz-transition: all 0.2s ease-in-out;
|
||||||
|
@ -65,6 +65,13 @@
|
|||||||
width: 40px;
|
width: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
&.projects-version-control-spinner-sidebar {
|
||||||
|
background: white;
|
||||||
|
padding:0;
|
||||||
|
img {
|
||||||
|
width: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -72,7 +79,7 @@
|
|||||||
button.editor-button {
|
button.editor-button {
|
||||||
width: calc(50% - 40px);
|
width: calc(50% - 40px);
|
||||||
margin: 20px;
|
margin: 20px;
|
||||||
height: 200px;
|
height: 175px;
|
||||||
line-height: 2em;
|
line-height: 2em;
|
||||||
font-size: 1.5em !important;
|
font-size: 1.5em !important;
|
||||||
i {
|
i {
|
||||||
@ -173,10 +180,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.sidebar-projects {
|
.sidebar-version-control {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
.sidebar-projects-stack-info {
|
.sidebar-version-control-stack-info {
|
||||||
height: 100px;
|
height: 100px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
border-bottom: 1px solid $secondary-border-color;
|
border-bottom: 1px solid $secondary-border-color;
|
||||||
@ -185,13 +192,13 @@
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.sidebar-projects-stack {
|
.sidebar-version-control-stack {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100px;
|
top: 0px;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
overflow-y: scroll;
|
overflow: hidden;
|
||||||
|
|
||||||
.palette-category {
|
.palette-category {
|
||||||
&:not(.palette-category-expanded) button {
|
&:not(.palette-category-expanded) button {
|
||||||
@ -240,3 +247,86 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 8px 20px 20px;
|
padding: 8px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sidebar-version-control-change-container {
|
||||||
|
position: relative;
|
||||||
|
height: 50%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
transition: height 0.2s ease-in-out;
|
||||||
|
&:first-child {
|
||||||
|
// border-bottom: 1px solid $primary-border-color;
|
||||||
|
}
|
||||||
|
.red-ui-editableList-container {
|
||||||
|
background: #f9f9f9;
|
||||||
|
padding: 0;
|
||||||
|
li {
|
||||||
|
padding:0;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.red-ui-editableList-border {
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-change-commit-box {
|
||||||
|
position:absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left:0;
|
||||||
|
right:0;
|
||||||
|
height:0;
|
||||||
|
transition: height 0.2s ease-in-out;
|
||||||
|
background: #f6f6f6;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
border-top: 1px solid $secondary-border-color;
|
||||||
|
textarea {
|
||||||
|
height: 110px;
|
||||||
|
margin: 10px;
|
||||||
|
width: calc(100% - 20px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 1px;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-change-commit-toolbar {
|
||||||
|
padding: 0 20px;
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
}
|
||||||
|
.sidebar-version-control-change-entry {
|
||||||
|
height: 20px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
position: relative;
|
||||||
|
white-space: nowrap;
|
||||||
|
span {
|
||||||
|
margin: 0 6px;
|
||||||
|
}
|
||||||
|
.button-group {
|
||||||
|
position: absolute;
|
||||||
|
top: 4px;
|
||||||
|
right: 4px;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.button-group {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sidebar-version-control-change-header {
|
||||||
|
color: #666;
|
||||||
|
background: #f6f6f6;
|
||||||
|
padding: 4px 10px;
|
||||||
|
height: 30px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: 1px solid $secondary-border-color;
|
||||||
|
i {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -18,5 +18,9 @@
|
|||||||
background: white;
|
background: white;
|
||||||
.palette-category {
|
.palette-category {
|
||||||
background: white;
|
background: white;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,7 @@
|
|||||||
"express": "4.15.3",
|
"express": "4.15.3",
|
||||||
"express-session": "1.15.2",
|
"express-session": "1.15.2",
|
||||||
"follow-redirects":"1.2.4",
|
"follow-redirects":"1.2.4",
|
||||||
"fs-extra": "1.0.0",
|
"fs-extra": "4.0.2",
|
||||||
"fs.notify":"0.0.4",
|
"fs.notify":"0.0.4",
|
||||||
"hash-sum":"1.0.2",
|
"hash-sum":"1.0.2",
|
||||||
"i18next":"1.10.6",
|
"i18next":"1.10.6",
|
||||||
|
@ -37,7 +37,8 @@ module.exports = {
|
|||||||
projects: list
|
projects: list
|
||||||
};
|
};
|
||||||
res.json(response);
|
res.json(response);
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
if (err.code) {
|
if (err.code) {
|
||||||
res.status(400).json({error:err.code, message: err.message});
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
} else {
|
} else {
|
||||||
@ -52,7 +53,8 @@ module.exports = {
|
|||||||
runtime.storage.projects.getProject(name).then(function(data) {
|
runtime.storage.projects.getProject(name).then(function(data) {
|
||||||
res.json(data);
|
res.json(data);
|
||||||
});
|
});
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
if (err.code) {
|
if (err.code) {
|
||||||
res.status(400).json({error:err.code, message: err.message});
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
} else {
|
} else {
|
||||||
@ -69,7 +71,7 @@ module.exports = {
|
|||||||
if (req.params.id !== currentProject) {
|
if (req.params.id !== currentProject) {
|
||||||
runtime.storage.projects.setActiveProject(req.params.id).then(function() {
|
runtime.storage.projects.setActiveProject(req.params.id).then(function() {
|
||||||
res.redirect(303,req.baseUrl + '/');
|
res.redirect(303,req.baseUrl + '/');
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
if (err.code) {
|
if (err.code) {
|
||||||
res.status(400).json({error:err.code, message: err.message});
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
} else {
|
} else {
|
||||||
@ -85,7 +87,7 @@ module.exports = {
|
|||||||
req.body.hasOwnProperty('summary')) {
|
req.body.hasOwnProperty('summary')) {
|
||||||
runtime.storage.projects.updateProject(req.params.id, req.body).then(function() {
|
runtime.storage.projects.updateProject(req.params.id, req.body).then(function() {
|
||||||
res.redirect(303,req.baseUrl + '/');
|
res.redirect(303,req.baseUrl + '/');
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
if (err.code) {
|
if (err.code) {
|
||||||
res.status(400).json({error:err.code, message: err.message});
|
res.status(400).json({error:err.code, message: err.message});
|
||||||
} else {
|
} else {
|
||||||
@ -104,7 +106,7 @@ module.exports = {
|
|||||||
} else {
|
} else {
|
||||||
res.status(404).end();
|
res.status(404).end();
|
||||||
}
|
}
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
})
|
})
|
||||||
@ -120,7 +122,67 @@ module.exports = {
|
|||||||
runtime.storage.projects.getFiles(req.params.id).then(function(data) {
|
runtime.storage.projects.getFiles(req.params.id).then(function(data) {
|
||||||
res.json(data);
|
res.json(data);
|
||||||
})
|
})
|
||||||
.otherwise(function(err) {
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post(/([^\/]+)\/stage\/(.+)$/, function(req,res) {
|
||||||
|
var projectName = req.params[0];
|
||||||
|
var file = req.params[1];
|
||||||
|
|
||||||
|
runtime.storage.projects.stageFile(projectName,file).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
|
||||||
|
})
|
||||||
|
.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 files = req.body.files;
|
||||||
|
|
||||||
|
runtime.storage.projects.stageFile(projectName,files).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/:id/commit", function(req,res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
|
||||||
|
runtime.storage.projects.commit(projectName,req.body).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
app.delete(/([^\/]+)\/stage\/(.+)$/, function(req,res) {
|
||||||
|
var projectName = req.params[0];
|
||||||
|
var file = req.params[1];
|
||||||
|
|
||||||
|
runtime.storage.projects.unstageFile(projectName,file).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
app.delete("/:id/stage", function(req, res) {
|
||||||
|
var projectName = req.params.id;
|
||||||
|
runtime.storage.projects.unstageFile(projectName).then(function(data) {
|
||||||
|
res.redirect(303,req.baseUrl+"/"+projectName+"/files");
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
res.status(400).json({error:"unexpected_error", message:err.toString()});
|
||||||
})
|
})
|
||||||
|
@ -55,7 +55,7 @@ var localfilesystem = {
|
|||||||
if (!settings.userDir) {
|
if (!settings.userDir) {
|
||||||
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
settings.userDir = fspath.join(process.env.HOME || process.env.USERPROFILE || process.env.HOMEPATH || process.env.NODE_RED_HOME,".node-red");
|
||||||
if (!settings.readOnly) {
|
if (!settings.readOnly) {
|
||||||
promises.push(util.promiseDir(fspath.join(settings.userDir,"node_modules")));
|
promises.push(fs.ensureDir(fspath.join(settings.userDir,"node_modules")));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,7 @@ function getLibraryEntry(type,path) {
|
|||||||
});
|
});
|
||||||
return dirs.concat(files);
|
return dirs.concat(files);
|
||||||
});
|
});
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
// if path is empty, then assume it was a folder, return empty
|
// if path is empty, then assume it was a folder, return empty
|
||||||
if (path === ""){
|
if (path === ""){
|
||||||
return [];
|
return [];
|
||||||
@ -137,7 +137,7 @@ function getLibraryEntry(type,path) {
|
|||||||
// check for path.json as an alternative if flows
|
// check for path.json as an alternative if flows
|
||||||
if (type === "flows" && !/\.json$/.test(path)) {
|
if (type === "flows" && !/\.json$/.test(path)) {
|
||||||
return getLibraryEntry(type,path+".json")
|
return getLibraryEntry(type,path+".json")
|
||||||
.otherwise(function(e) {
|
.catch(function(e) {
|
||||||
throw err;
|
throw err;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -152,7 +152,7 @@ module.exports = {
|
|||||||
libDir = fspath.join(settings.userDir,"lib");
|
libDir = fspath.join(settings.userDir,"lib");
|
||||||
libFlowsDir = fspath.join(libDir,"flows");
|
libFlowsDir = fspath.join(libDir,"flows");
|
||||||
if (!settings.readOnly) {
|
if (!settings.readOnly) {
|
||||||
return util.promiseDir(libFlowsDir);
|
return fs.ensureDir(libFlowsDir);
|
||||||
} else {
|
} else {
|
||||||
return when.resolve();
|
return when.resolve();
|
||||||
}
|
}
|
||||||
@ -176,7 +176,7 @@ module.exports = {
|
|||||||
if (type === "flows" && settings.flowFilePretty) {
|
if (type === "flows" && settings.flowFilePretty) {
|
||||||
body = JSON.stringify(JSON.parse(body),null,4);
|
body = JSON.stringify(JSON.parse(body),null,4);
|
||||||
}
|
}
|
||||||
return util.promiseDir(fspath.dirname(fn)).then(function () {
|
return fs.ensureDir(fspath.dirname(fn)).then(function () {
|
||||||
util.writeFile(fn,headers+body);
|
util.writeFile(fn,headers+body);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
/**
|
||||||
|
* Copyright JS Foundation and other contributors, http://js.foundation
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
**/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
"package.json": function(project) {
|
||||||
|
return JSON.stringify({
|
||||||
|
"name": project.name,
|
||||||
|
"description": project.summary||"A Node-RED Project",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"dependencies": {}
|
||||||
|
},"",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 "{}" },
|
||||||
|
".gitignore": function() { return "*.backup" ;}
|
||||||
|
}
|
@ -67,6 +67,112 @@ function isAuthError(err) {
|
|||||||
// lines.forEach(console.log);
|
// lines.forEach(console.log);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function cleanFilename(name) {
|
||||||
|
if (name[0] !== '"') {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return name.substring(1,name.length-1);
|
||||||
|
}
|
||||||
|
function parseFilenames(name) {
|
||||||
|
var re = /([^ "]+|(".*?"))($| -> ([^ ]+|(".*"))$)/;
|
||||||
|
var m = re.exec(name);
|
||||||
|
var result = [];
|
||||||
|
if (m) {
|
||||||
|
result.push(cleanFilename(m[1]));
|
||||||
|
if (m[4]) {
|
||||||
|
result.push(cleanFilename(m[4]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFiles(localRepo) {
|
||||||
|
// parseFilename('"test with space"');
|
||||||
|
// parseFilename('"test with space" -> knownFile.txt');
|
||||||
|
// parseFilename('"test with space" -> "un -> knownFile.txt"');
|
||||||
|
var files = {};
|
||||||
|
return runCommand(gitCommand,["ls-files","--cached","--others","--exclude-standard"],localRepo).then(function(output) {
|
||||||
|
var lines = output.split("\n");
|
||||||
|
lines.forEach(function(l) {
|
||||||
|
if (l==="") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var fullName = cleanFilename(l);
|
||||||
|
// parseFilename(l);
|
||||||
|
var parts = fullName.split("/");
|
||||||
|
var p = files;
|
||||||
|
var name;
|
||||||
|
for (var i = 0;i<parts.length-1;i++) {
|
||||||
|
var name = parts.slice(0,i+1).join("/")+"/";
|
||||||
|
if (!p.hasOwnProperty(name)) {
|
||||||
|
p[name] = {
|
||||||
|
type:"d"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
files[fullName] = {
|
||||||
|
type: "f"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return runCommand(gitCommand,["status","--porcelain"],localRepo).then(function(output) {
|
||||||
|
var lines = output.split("\n");
|
||||||
|
var unknownDirs = [];
|
||||||
|
lines.forEach(function(line) {
|
||||||
|
if (line==="") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (line[0] === "#") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var status = line.substring(0,2);
|
||||||
|
var fileName;
|
||||||
|
var names;
|
||||||
|
if (status !== '??') {
|
||||||
|
names = parseFilenames(line.substring(3));
|
||||||
|
} else {
|
||||||
|
names = [cleanFilename(line.substring(3))];
|
||||||
|
}
|
||||||
|
fileName = names[0];
|
||||||
|
if (names.length > 1) {
|
||||||
|
fileName = names[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseFilename(fileName);
|
||||||
|
if (fileName.charCodeAt(0) === 34) {
|
||||||
|
fileName = fileName.substring(1,fileName.length-1);
|
||||||
|
}
|
||||||
|
if (files.hasOwnProperty(fileName)) {
|
||||||
|
files[fileName].status = status;
|
||||||
|
} else {
|
||||||
|
files[fileName] = {
|
||||||
|
type: "f",
|
||||||
|
status: status
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (names.length > 1) {
|
||||||
|
files[fileName].oldName = names[0];
|
||||||
|
}
|
||||||
|
if (status === "??" && fileName[fileName.length-1] === '/') {
|
||||||
|
unknownDirs.push(fileName);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
var allFilenames = Object.keys(files);
|
||||||
|
allFilenames.forEach(function(f) {
|
||||||
|
var entry = files[f];
|
||||||
|
if (!entry.hasOwnProperty('status')) {
|
||||||
|
unknownDirs.forEach(function(uf) {
|
||||||
|
if (f.startsWith(uf)) {
|
||||||
|
entry.status = "??"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// console.log(files);
|
||||||
|
return files;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
var gitCommand = "git";
|
var gitCommand = "git";
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initRepo: function(cwd) {
|
initRepo: function(cwd) {
|
||||||
@ -82,5 +188,26 @@ module.exports = {
|
|||||||
clone: function(repo, cwd) {
|
clone: function(repo, cwd) {
|
||||||
var args = ["clone",repo,"."];
|
var args = ["clone",repo,"."];
|
||||||
return runCommand(gitCommand,args,cwd);
|
return runCommand(gitCommand,args,cwd);
|
||||||
|
},
|
||||||
|
getFiles: getFiles,
|
||||||
|
stageFile: function(cwd,file) {
|
||||||
|
var args = ["add"];
|
||||||
|
if (Array.isArray(file)) {
|
||||||
|
args = args.concat(file);
|
||||||
|
} else {
|
||||||
|
args.push(file);
|
||||||
|
}
|
||||||
|
return runCommand(gitCommand,args,cwd);
|
||||||
|
},
|
||||||
|
unstageFile: function(cwd, file) {
|
||||||
|
var args = ["reset","--"];
|
||||||
|
if (file) {
|
||||||
|
args.push(file);
|
||||||
|
}
|
||||||
|
return runCommand(gitCommand,args,cwd);
|
||||||
|
},
|
||||||
|
commit: function(cwd, message) {
|
||||||
|
var args = ["commit","-m",message];
|
||||||
|
return runCommand(gitCommand,args,cwd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ var runtime;
|
|||||||
|
|
||||||
var projectsDir;
|
var projectsDir;
|
||||||
|
|
||||||
|
var defaultFileSet = require("./defaultFileSet");
|
||||||
|
|
||||||
function init(_settings, _runtime) {
|
function init(_settings, _runtime) {
|
||||||
settings = _settings;
|
settings = _settings;
|
||||||
@ -69,7 +70,7 @@ function init(_settings, _runtime) {
|
|||||||
credentialsFileBackup = getBackupFilename(credentialsFile)
|
credentialsFileBackup = getBackupFilename(credentialsFile)
|
||||||
|
|
||||||
if (!settings.readOnly) {
|
if (!settings.readOnly) {
|
||||||
return util.promiseDir(projectsDir)
|
return fs.ensureDir(projectsDir)
|
||||||
//TODO: this is accessing settings from storage directly as settings
|
//TODO: this is accessing settings from storage directly as settings
|
||||||
// has not yet been initialised. That isn't ideal - can this be deferred?
|
// has not yet been initialised. That isn't ideal - can this be deferred?
|
||||||
.then(storageSettings.getSettings)
|
.then(storageSettings.getSettings)
|
||||||
@ -103,7 +104,7 @@ function getBackupFilename(filename) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function listProjects() {
|
function listProjects() {
|
||||||
return nodeFn.call(fs.readdir, projectsDir).then(function(fns) {
|
return fs.readdir(projectsDir).then(function(fns) {
|
||||||
var dirs = [];
|
var dirs = [];
|
||||||
fns.sort().filter(function(fn) {
|
fns.sort().filter(function(fn) {
|
||||||
var fullPath = fspath.join(projectsDir,fn);
|
var fullPath = fspath.join(projectsDir,fn);
|
||||||
@ -151,14 +152,14 @@ function getProject(project) {
|
|||||||
projectData.missingFiles = missingFiles;
|
projectData.missingFiles = missingFiles;
|
||||||
}
|
}
|
||||||
if (missingFiles.indexOf('package.json') === -1) {
|
if (missingFiles.indexOf('package.json') === -1) {
|
||||||
promises.push(nodeFn.call(fs.readFile,fspath.join(projectPath,"package.json"),"utf8").then(function(content) {
|
promises.push(fs.readFile(fspath.join(projectPath,"package.json"),"utf8").then(function(content) {
|
||||||
var package = util.parseJSON(content);
|
var package = util.parseJSON(content);
|
||||||
projectData.summary = package.description||"";
|
projectData.summary = package.description||"";
|
||||||
projectData.dependencies = package.dependencies||{};
|
projectData.dependencies = package.dependencies||{};
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (missingFiles.indexOf('README.md') === -1) {
|
if (missingFiles.indexOf('README.md') === -1) {
|
||||||
promises.push(nodeFn.call(fs.readFile,fspath.join(projectPath,"README.md"),"utf8").then(function(content) {
|
promises.push(fs.readFile(fspath.join(projectPath,"README.md"),"utf8").then(function(content) {
|
||||||
projectData.description = content;
|
projectData.description = content;
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
@ -174,9 +175,9 @@ function getProject(project) {
|
|||||||
// if (err) {
|
// if (err) {
|
||||||
// return resolve(null);
|
// return resolve(null);
|
||||||
// }
|
// }
|
||||||
// resolve(nodeFn.call(fs.readFile,projectPackage,'utf8').then(util.parseJSON));
|
// resolve(fs.readFile(projectPackage,'utf8').then(util.parseJSON));
|
||||||
// })
|
// })
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
var e = new Error("NLD: project not found");
|
var e = new Error("NLD: project not found");
|
||||||
e.code = "project_not_found";
|
e.code = "project_not_found";
|
||||||
@ -191,7 +192,6 @@ function setCredentialSecret(project,data) { //existingSecret,secret) {
|
|||||||
var wasInvalid = false;
|
var wasInvalid = false;
|
||||||
|
|
||||||
var globalProjectSettings = settings.get("projects");
|
var globalProjectSettings = settings.get("projects");
|
||||||
globalProjectSettings.projects = globalProjectSettings.projects || {};
|
|
||||||
if (globalProjectSettings.projects.hasOwnProperty(project)) {
|
if (globalProjectSettings.projects.hasOwnProperty(project)) {
|
||||||
if (!isReset &&
|
if (!isReset &&
|
||||||
globalProjectSettings.projects[project].credentialSecret &&
|
globalProjectSettings.projects[project].credentialSecret &&
|
||||||
@ -239,10 +239,12 @@ function createProject(metadata) {
|
|||||||
return reject(e);
|
return reject(e);
|
||||||
}
|
}
|
||||||
createProjectDirectory(project).then(function() {
|
createProjectDirectory(project).then(function() {
|
||||||
|
var projects = settings.get('projects');
|
||||||
|
projects[project] = {};
|
||||||
if (metadata.credentialSecret) {
|
if (metadata.credentialSecret) {
|
||||||
return setCredentialSecret(project,{credentialSecret: credentialSecret});
|
projects[project].credentialSecret = metadata.credentialSecret;
|
||||||
}
|
}
|
||||||
return when.resolve();
|
return settings.set('projects',projects);
|
||||||
}).then(function() {
|
}).then(function() {
|
||||||
if (metadata.remote) {
|
if (metadata.remote) {
|
||||||
return gitTools.pull(metadata.remote,projectPath).then(function(result) {
|
return gitTools.pull(metadata.remote,projectPath).then(function(result) {
|
||||||
@ -259,43 +261,26 @@ function createProject(metadata) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
resolve(project);
|
resolve(project);
|
||||||
}).otherwise(function(error) {
|
}).catch(function(error) {
|
||||||
fs.remove(projectPath,function() {
|
fs.remove(projectPath,function() {
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
createDefaultProject(metadata).then(function() { resolve(project)}).otherwise(reject);
|
createDefaultProject(metadata).then(function() { resolve(project)}).catch(reject);
|
||||||
}
|
}
|
||||||
}).otherwise(reject);
|
}).catch(reject);
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function createProjectDirectory(project) {
|
function createProjectDirectory(project) {
|
||||||
var projectPath = fspath.join(projectsDir,project);
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
return util.promiseDir(projectPath).then(function() {
|
return fs.ensureDir(projectPath).then(function() {
|
||||||
return gitTools.initRepo(projectPath)
|
return gitTools.initRepo(projectPath)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultFileSet = {
|
|
||||||
"package.json": function(project) {
|
|
||||||
return JSON.stringify({
|
|
||||||
"name": project.name,
|
|
||||||
"description": project.summary||"A Node-RED Project",
|
|
||||||
"version": "0.0.1",
|
|
||||||
"dependencies": {}
|
|
||||||
},"",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 "{}" }
|
|
||||||
}
|
|
||||||
|
|
||||||
function createDefaultProject(project) {
|
function createDefaultProject(project) {
|
||||||
var projectPath = fspath.join(projectsDir,project.name);
|
var projectPath = fspath.join(projectsDir,project.name);
|
||||||
// Create a basic skeleton of a project
|
// Create a basic skeleton of a project
|
||||||
@ -309,10 +294,13 @@ function createDefaultProject(project) {
|
|||||||
}
|
}
|
||||||
function checkProjectExists(project) {
|
function checkProjectExists(project) {
|
||||||
var projectPath = fspath.join(projectsDir,project);
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
return nodeFn.call(fs.stat,projectPath).otherwise(function(err) {
|
return fs.pathExists(projectPath).then(function(exists) {
|
||||||
|
console.log(projectPath,exists);
|
||||||
|
if (!exists) {
|
||||||
var e = new Error("NLD: project not found");
|
var e = new Error("NLD: project not found");
|
||||||
e.code = "project_not_found";
|
e.code = "project_not_found";
|
||||||
throw e;
|
throw e;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function checkProjectFiles(project) {
|
function checkProjectFiles(project) {
|
||||||
@ -322,7 +310,7 @@ function checkProjectFiles(project) {
|
|||||||
for (var file in defaultFileSet) {
|
for (var file in defaultFileSet) {
|
||||||
if (defaultFileSet.hasOwnProperty(file)) {
|
if (defaultFileSet.hasOwnProperty(file)) {
|
||||||
paths.push(file);
|
paths.push(file);
|
||||||
promises.push(nodeFn.call(fs.stat,fspath.join(projectPath,file)));
|
promises.push(fs.stat(fspath.join(projectPath,file)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return when.settle(promises).then(function(results) {
|
return when.settle(promises).then(function(results) {
|
||||||
@ -349,51 +337,24 @@ function checkProjectFiles(project) {
|
|||||||
|
|
||||||
function getFiles(project) {
|
function getFiles(project) {
|
||||||
var projectPath = fspath.join(projectsDir,project);
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
return nodeFn.call(listFiles,projectPath,"/");
|
return gitTools.getFiles(projectPath);
|
||||||
|
}
|
||||||
|
function stageFile(project,file) {
|
||||||
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
|
return gitTools.stageFile(projectPath,file);
|
||||||
|
}
|
||||||
|
function unstageFile(project,file) {
|
||||||
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
|
return gitTools.unstageFile(projectPath,file);
|
||||||
|
}
|
||||||
|
function commit(project,options) {
|
||||||
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
|
return gitTools.commit(projectPath,options.message);
|
||||||
}
|
}
|
||||||
function getFile(project,path) {
|
function getFile(project,path) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function listFiles(root,path,done) {
|
|
||||||
var entries = [];
|
|
||||||
var fullPath = fspath.join(root,path);
|
|
||||||
fs.readdir(fullPath, function(err,fns) {
|
|
||||||
var childCount = fns.length;
|
|
||||||
fns.sort().forEach(function(fn) {
|
|
||||||
if (fn === ".git") {
|
|
||||||
childCount--;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var child = {
|
|
||||||
path: fspath.join(path,fn),
|
|
||||||
name: fn
|
|
||||||
};
|
|
||||||
entries.push(child);
|
|
||||||
var childFullPath = fspath.join(fullPath,fn);
|
|
||||||
fs.lstat(childFullPath, function(err, stats) {
|
|
||||||
if (stats.isDirectory()) {
|
|
||||||
child.type = 'd';
|
|
||||||
listFiles(root,child.path,function(err,children) {
|
|
||||||
child.children = children;
|
|
||||||
childCount--;
|
|
||||||
if (childCount === 0) {
|
|
||||||
done(null,entries);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
child.type = 'f';
|
|
||||||
childCount--;
|
|
||||||
console.log(child,childCount)
|
|
||||||
if (childCount === 0) {
|
|
||||||
done(null,entries);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var activeProject
|
var activeProject
|
||||||
function getActiveProject() {
|
function getActiveProject() {
|
||||||
return activeProject;
|
return activeProject;
|
||||||
@ -403,7 +364,7 @@ function reloadActiveProject(project) {
|
|||||||
return runtime.nodes.stopFlows().then(function() {
|
return runtime.nodes.stopFlows().then(function() {
|
||||||
return runtime.nodes.loadFlows(true).then(function() {
|
return runtime.nodes.loadFlows(true).then(function() {
|
||||||
runtime.events.emit("runtime-event",{id:"project-change",payload:{ project: project}});
|
runtime.events.emit("runtime-event",{id:"project-change",payload:{ project: project}});
|
||||||
}).otherwise(function(err) {
|
}).catch(function(err) {
|
||||||
// We're committed to the project change now, so notify editors
|
// We're committed to the project change now, so notify editors
|
||||||
// that it has changed.
|
// that it has changed.
|
||||||
runtime.events.emit("runtime-event",{id:"project-change",payload:{ project: project}});
|
runtime.events.emit("runtime-event",{id:"project-change",payload:{ project: project}});
|
||||||
@ -456,7 +417,7 @@ function updateProject(project,data) {
|
|||||||
} else if (data.hasOwnProperty('dependencies') || data.hasOwnProperty('summary')) {
|
} else if (data.hasOwnProperty('dependencies') || data.hasOwnProperty('summary')) {
|
||||||
var projectPath = fspath.join(projectsDir,project);
|
var projectPath = fspath.join(projectsDir,project);
|
||||||
var packageJSON = fspath.join(projectPath,"package.json");
|
var packageJSON = fspath.join(projectPath,"package.json");
|
||||||
return nodeFn.call(fs.readFile,packageJSON,"utf8").then(function(content) {
|
return fs.readFile(packageJSON,"utf8").then(function(content) {
|
||||||
var package = util.parseJSON(content);
|
var package = util.parseJSON(content);
|
||||||
if (data.dependencies) {
|
if (data.dependencies) {
|
||||||
package.dependencies = data.dependencies;
|
package.dependencies = data.dependencies;
|
||||||
@ -483,9 +444,11 @@ function getFlows() {
|
|||||||
if (!initialFlowLoadComplete) {
|
if (!initialFlowLoadComplete) {
|
||||||
initialFlowLoadComplete = true;
|
initialFlowLoadComplete = true;
|
||||||
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
log.info(log._("storage.localfilesystem.user-dir",{path:settings.userDir}));
|
||||||
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
if (activeProject) {
|
||||||
log.info(log._("storage.localfilesystem.active-project",{project:activeProject||"none"}));
|
log.info(log._("storage.localfilesystem.active-project",{project:activeProject||"none"}));
|
||||||
}
|
}
|
||||||
|
log.info(log._("storage.localfilesystem.flows-file",{path:flowsFullPath}));
|
||||||
|
}
|
||||||
return util.readFile(flowsFullPath,flowsFileBackup,[],'flow');
|
return util.readFile(flowsFullPath,flowsFileBackup,[],'flow');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -541,6 +504,9 @@ module.exports = {
|
|||||||
createProject: createProject,
|
createProject: createProject,
|
||||||
updateProject: updateProject,
|
updateProject: updateProject,
|
||||||
getFiles: getFiles,
|
getFiles: getFiles,
|
||||||
|
stageFile: stageFile,
|
||||||
|
unstageFile: unstageFile,
|
||||||
|
commit: commit,
|
||||||
|
|
||||||
getFlows: getFlows,
|
getFlows: getFlows,
|
||||||
saveFlows: saveFlows,
|
saveFlows: saveFlows,
|
||||||
|
@ -73,7 +73,6 @@ function readFile(path,backupPath,emptyResponse,type) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
promiseDir: nodeFn.lift(fs.mkdirs),
|
|
||||||
/**
|
/**
|
||||||
* Write content to a file using UTF8 encoding.
|
* Write content to a file using UTF8 encoding.
|
||||||
* This forces a fsync before completing to ensure
|
* This forces a fsync before completing to ensure
|
||||||
|
Loading…
Reference in New Issue
Block a user