From edc2310599344f7a874822caae4df3363772cffb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 20 Sep 2017 22:51:28 +0100 Subject: [PATCH] Move project sidebar to project settings dialog --- Gruntfile.js | 1 + editor/js/main.js | 9 +- editor/js/ui/projectSettings.js | 262 ++++++++++++++++++ editor/js/ui/projects.js | 56 ++-- editor/js/ui/tab-info.js | 42 ++- editor/js/ui/view.js | 1 - editor/js/ui/workspaces.js | 3 + editor/sass/projects.scss | 20 +- editor/sass/tab-info.scss | 1 + red/api/editor/projects/index.js | 9 +- .../storage/localfilesystem/projects/index.js | 13 +- 11 files changed, 365 insertions(+), 52 deletions(-) create mode 100644 editor/js/ui/projectSettings.js diff --git a/Gruntfile.js b/Gruntfile.js index 472d70c2d..254585d7b 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -154,6 +154,7 @@ module.exports = function(grunt) { "editor/js/ui/subflow.js", "editor/js/ui/userSettings.js", "editor/js/ui/projects.js", + "editor/js/ui/projectSettings.js", "editor/js/ui/touch/radialMenu.js" ], dest: "public/red/red.js" diff --git a/editor/js/main.js b/editor/js/main.js index 1ca25546a..13d031312 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -43,7 +43,7 @@ $(".palette-scroll").removeClass("hide"); $("#palette-search").removeClass("hide"); loadFlows(function() { - RED.projects.refreshSidebar(); + RED.projects.refresh(); var persistentNotifications = {}; RED.comms.subscribe("notification/#",function(topic,msg) { @@ -60,10 +60,11 @@ if (notificationId === "project-change") { RED.nodes.clear(); RED.history.clear(); - RED.projects.refreshSidebar(); - RED.projects.showSidebar(); + RED.view.redraw(true); + RED.projects.refresh(); loadFlows(function() { RED.notify("NLS: Project changed to "+msg.project); + RED.sidebar.info.refresh() }); return; } @@ -74,8 +75,6 @@ text += '

'+'Setup credentials'+'

'; } } - - if (!persistentNotifications.hasOwnProperty(notificationId)) { persistentNotifications[notificationId] = RED.notify(text,msg.type,msg.timeout === undefined,msg.timeout); } else { diff --git a/editor/js/ui/projectSettings.js b/editor/js/ui/projectSettings.js new file mode 100644 index 000000000..ba2ff6fa2 --- /dev/null +++ b/editor/js/ui/projectSettings.js @@ -0,0 +1,262 @@ +/** + * 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.projects.settings = (function() { + + var trayWidth = 700; + var settingsVisible = false; + + var panes = []; + + function addPane(options) { + panes.push(options); + } + + // TODO: DRY - tab-info.js + function addTargetToExternalLinks(el) { + $(el).find("a").each(function(el) { + var href = $(this).attr('href'); + if (/^https?:/.test(href)) { + $(this).attr('target','_blank'); + } + }); + return el; + } + + function show(initialTab) { + if (settingsVisible) { + return; + } + settingsVisible = true; + var tabContainer; + + var trayOptions = { + title: "NLS: Project Information",// RED._("menu.label.userSettings"), + buttons: [ + { + id: "node-dialog-ok", + text: RED._("common.label.close"), + class: "primary", + click: function() { + RED.tray.close(); + } + } + ], + resize: function(dimensions) { + trayWidth = dimensions.width; + }, + open: function(tray) { + var project = RED.projects.getActiveProject(); + var trayBody = tray.find('.editor-tray-body'); + var settingsContent = $('
').appendTo(trayBody); + var tabContainer = $('
',{id:"user-settings-tabs-container"}).appendTo(settingsContent); + + $('',{id:"user-settings-tabs"}).appendTo(tabContainer); + var settingsTabs = RED.tabs.create({ + id: "user-settings-tabs", + vertical: true, + onchange: function(tab) { + setTimeout(function() { + $("#user-settings-tabs-content").children().hide(); + $("#" + tab.id).show(); + if (tab.pane.focus) { + tab.pane.focus(); + } + },50); + } + }); + var tabContents = $('
',{id:"user-settings-tabs-content"}).appendTo(settingsContent); + + panes.forEach(function(pane) { + settingsTabs.addTab({ + id: "project-settings-tab-"+pane.id, + label: pane.title, + pane: pane + }); + pane.get(project).hide().appendTo(tabContents); + }); + settingsContent.i18n(); + settingsTabs.activateTab("project-settings-tab-"+(initialTab||'view')) + $("#sidebar-shade").show(); + }, + close: function() { + settingsVisible = false; + panes.forEach(function(pane) { + if (pane.close) { + pane.close(); + } + }); + $("#sidebar-shade").hide(); + + }, + show: function() {} + } + if (trayWidth !== null) { + trayOptions.width = trayWidth; + } + RED.tray.show(trayOptions); + } + + function addSpinnerOverlay(container) { + var spinner = $('
').appendTo(container); + return spinner; + } + function editDescription(activeProject, container) { + RED.editor.editMarkdown({ + title: RED._('sidebar.project.editDescription'), + value: activeProject.description, + complete: function(v) { + container.empty(); + var spinner = addSpinnerOverlay(container); + var done = function(err,res) { + if (err) { + return editDescription(activeProject, container); + } + activeProject.description = v; + updateProjectDescription(activeProject, container); + } + utils.sendRequest({ + url: "projects/"+activeProject.name, + type: "PUT", + responses: { + 0: function(error) { + done(error,null); + }, + 200: function(data) { + done(null,data); + }, + 400: { + 'unexpected_error': function(error) { + done(error,null); + } + }, + } + },{description:v}).always(function() { + spinner.remove(); + }); + } + }); + } + function updateProjectDescription(activeProject, container) { + container.empty(); + var desc = marked(activeProject.description||""); + var description = addTargetToExternalLinks($(''+desc+'')).appendTo(container); + description.find(".bidiAware").contents().filter(function() { return this.nodeType === 3 && this.textContent.trim() !== "" }).wrap( "" ); + } + + function editSummary(activeProject, summary, container) { + var editButton = container.prev(); + editButton.hide(); + container.empty(); + var bg = $('').appendTo(container); + var input = $('').val(summary||"").appendTo(container); + $('') + .appendTo(bg) + .click(function(evt) { + evt.preventDefault(); + updateProjectSummary(activeProject.summary, container); + editButton.show(); + }); + $('') + .appendTo(bg) + .click(function(evt) { + evt.preventDefault(); + var v = input.val(); + updateProjectSummary(v, container); + var spinner = addSpinnerOverlay(container); + var done = function(err,res) { + if (err) { + spinner.remove(); + return editSummary(activeProject, summary, container); + } + activeProject.summary = v; + spinner.remove(); + updateProjectSummary(activeProject.summary, container); + editButton.show(); + } + utils.sendRequest({ + url: "projects/"+activeProject.name, + type: "PUT", + responses: { + 0: function(error) { + done(error,null); + }, + 200: function(data) { + done(null,data); + }, + 400: { + 'unexpected_error': function(error) { + done(error,null); + } + }, + } + },{summary:v}); + }); + } + function updateProjectSummary(summary, container) { + container.empty(); + if (summary) { + container.text(summary).removeClass('node-info-node'); + } else { + container.text("NLS: No summary available").addClass('node-info-none'); + } + } + + function createViewPane(activeProject) { + + var pane = $('
'); + $('

').text(activeProject.name).appendTo(pane); + var summary = $('
').appendTo(pane); + var summaryContent = $('
',{style:"color: #999"}).appendTo(summary); + updateProjectSummary(activeProject.summary, summaryContent); + $('') + .prependTo(summary) + .click(function(evt) { + evt.preventDefault(); + editSummary(activeProject, activeProject.summary, summaryContent); + }); + $('
').appendTo(pane); + + var description = $('
').appendTo(pane); + var descriptionContent = $('
',{style:"min-height: 200px"}).appendTo(description); + + updateProjectDescription(activeProject, descriptionContent); + + $('') + .prependTo(description) + .click(function(evt) { + evt.preventDefault(); + editDescription(activeProject, descriptionContent); + }); + + return pane; + } + + var utils; + function init(_utils) { + utils = _utils; + addPane({ + id:'main', + title: "NLS: Project", + get: createViewPane, + close: function() { } + }) + } + return { + init: init, + show: show + }; +})(); diff --git a/editor/js/ui/projects.js b/editor/js/ui/projects.js index bc53f84bc..8254deeb4 100644 --- a/editor/js/ui/projects.js +++ b/editor/js/ui/projects.js @@ -464,6 +464,7 @@ RED.projects = (function() { // dialogBody.hide(); console.log(options.url); var start = Date.now(); + // TODO: this is specific to the dialog-based requests $(".projects-dialog-spinner").show(); $("#projects-dialog").parent().find(".ui-dialog-buttonset").children().css("visibility","hidden") if (body) { @@ -535,8 +536,10 @@ RED.projects = (function() { RED.actions.add("core:new-project",RED.projects.newProject); RED.actions.add("core:open-project",RED.projects.selectProject); + RED.projects.settings.init({sendRequest:sendRequest}); + initScreens(); - initSidebar(); + // initSidebar(); } // function getRepoAuthDetails(req) { @@ -598,6 +601,7 @@ RED.projects = (function() { // ]) // } +/* var sidebarContent; var sidebarSections; @@ -876,7 +880,6 @@ RED.projects = (function() { function addSpinnerOverlay(container) { var spinner = $('
').appendTo(container); return spinner; - } function updateProjectSummary() { sidebarSectionsInfo.empty(); @@ -928,18 +931,6 @@ RED.projects = (function() { } } - function refreshSidebar() { - $.getJSON("projects",function(data) { - if (data.active) { - $.getJSON("projects/"+data.active, function(project) { - activeProject = project; - updateProjectSummary(); - updateProjectDescription(); - updateProjectDependencies(); - }); - } - }); - } // function getUsedModules() { // var inuseModules = {}; @@ -966,6 +957,24 @@ RED.projects = (function() { }); return el; } +*/ + +function refresh() { + $.getJSON("projects",function(data) { + if (data.active) { + $.getJSON("projects/"+data.active, function(project) { + activeProject = project; + // updateProjectSummary(); + // updateProjectDescription(); + // updateProjectDependencies(); + console.log("project triggering a info refresh for ",activeProject) + RED.sidebar.info.refresh(); + }); + } + }); +} + + return { init: init, @@ -981,15 +990,16 @@ RED.projects = (function() { showCredentialsPrompt: function() { show('credentialSecret'); }, - showSidebar: showSidebar, - refreshSidebar: refreshSidebar, - showProjectEditor: function() { - RED.editor.editProject({ - project: activeProject, - complete: function(result) { - console.log(result); - } - }); + // showSidebar: showSidebar, + refresh: refresh, + editProject: function() { + RED.projects.settings.show(); + // RED.editor.editProject({ + // project: activeProject, + // complete: function(result) { + // console.log(result); + // } + // }); }, getActiveProject: function() { return activeProject; diff --git a/editor/js/ui/tab-info.js b/editor/js/ui/tab-info.js index 9cfb72cc2..998d687ea 100644 --- a/editor/js/ui/tab-info.js +++ b/editor/js/ui/tab-info.js @@ -109,6 +109,11 @@ RED.sidebar.info = (function() { return el; } function refresh(node) { + console.log('sidebar.info.refresh'); + if (node === undefined) { + refreshSelection(); + return; + } sections.show(); $(nodeSection.content).empty(); $(infoSection.content).empty(); @@ -123,17 +128,22 @@ RED.sidebar.info = (function() { var activeProject = RED.projects.getActiveProject(); if (activeProject) { propRow = $('Project').appendTo(tableBody); - $(propRow.children()[1]).html(' '+(activeProject.name||"")) + $(propRow.children()[1]).text(activeProject.name||""); $('').appendTo(tableBody); + $('') + .appendTo(propRow.children()[1]) + .click(function(evt) { + evt.preventDefault(); + RED.projects.editProject(); + }); } infoSection.container.show(); - if (node === undefined) { + if (node === null) { return; } else if (Array.isArray(node)) { infoSection.container.hide(); propRow = $(''+RED._("sidebar.info.selection")+"").appendTo(tableBody); - $(propRow.children()[1]).html(' '+RED._("sidebar.info.nodes",{count:node.length})) - + $(propRow.children()[1]).text(RED._("sidebar.info.nodes",{count:node.length})) } else { var m = /^subflow(:(.+))?$/.exec(node.type); if (m) { @@ -155,7 +165,7 @@ RED.sidebar.info = (function() { propRow = $(''+RED._("sidebar.info."+(node.type==='tab'?'flow':'subflow'))+'').appendTo(tableBody); RED.utils.createObjectElement(node.id).appendTo(propRow.children()[1]); propRow = $(''+RED._("sidebar.info.tabName")+"").appendTo(tableBody); - $(propRow.children()[1]).html(' '+(node.label||node.name||"")) + $(propRow.children()[1]).text(node.label||node.name||""); if (node.type === "tab") { propRow = $(''+RED._("sidebar.info.status")+'').appendTo(tableBody); $(propRow.children()[1]).html((!!!node.disabled)?RED._("sidebar.info.enabled"):RED._("sidebar.info.disabled")) @@ -166,10 +176,10 @@ RED.sidebar.info = (function() { if (node.type !== "subflow" && node.name) { - $(''+RED._("common.label.name")+' '+node.name+'').appendTo(tableBody); + $(''+RED._("common.label.name")+''+node.name+'').appendTo(tableBody); } if (!m) { - $(''+RED._("sidebar.info.type")+" "+node.type+"").appendTo(tableBody); + $(''+RED._("sidebar.info.type")+""+node.type+"").appendTo(tableBody); } if (!m && node.type != "subflow" && node.type != "comment") { @@ -357,7 +367,7 @@ RED.sidebar.info = (function() { function clear() { // sections.hide(); - refresh(); + refresh(null); } function set(html,title) { @@ -365,15 +375,16 @@ RED.sidebar.info = (function() { // sections.show(); // nodeSection.container.hide(); infoSection.title.text(title||""); - refresh(); + refresh(null); $(infoSection.content).empty(); setInfoText(html); $(".sidebar-node-info-stack").scrollTop(0); } - - - RED.events.on("view:selection-changed",function(selection) { + function refreshSelection(selection) { + if (selection === undefined) { + selection = RED.view.selection(); + } if (selection.nodes) { if (selection.nodes.length == 1) { var node = selection.nodes[0]; @@ -396,12 +407,15 @@ RED.sidebar.info = (function() { if (workspace && workspace.info) { refresh(workspace); } else { - refresh() + refresh(null) // clear(); } } } - }); + } + + + RED.events.on("view:selection-changed",refreshSelection); return { init: init, diff --git a/editor/js/ui/view.js b/editor/js/ui/view.js index c83abf9d2..7358c1419 100644 --- a/editor/js/ui/view.js +++ b/editor/js/ui/view.js @@ -1141,7 +1141,6 @@ RED.view = (function() { } } } - var selectionJSON = activeWorkspace+":"+JSON.stringify(selection,function(key,value) { if (key === 'nodes') { return value.map(function(n) { return n.id }) diff --git a/editor/js/ui/workspaces.js b/editor/js/ui/workspaces.js index 1f3b3c52e..5b2e301fe 100644 --- a/editor/js/ui/workspaces.js +++ b/editor/js/ui/workspaces.js @@ -312,6 +312,9 @@ RED.workspaces = (function() { workspace_tabs.removeTab(ws.id); } } + if (ws.id === activeWorkspace) { + activeWorkspace = 0; + } } function setWorkspaceOrder(order) { diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss index e0ee31cf6..31cccf4d2 100644 --- a/editor/sass/projects.scss +++ b/editor/sass/projects.scss @@ -34,7 +34,8 @@ input[type=checkbox], input[type=radio] { width: auto; vertical-align: top; - } } + } + } } .projects-dialog-spinner { position: absolute; @@ -58,10 +59,10 @@ width: 80px; } &.projects-dialog-spinner-sidebar { - padding: 20px; - background: $shade-color; + background: white; + padding:0; img { - width: 50px; + width: 40px; } } @@ -231,3 +232,14 @@ } } } + + +.project-settings-tab-pane { + position: absolute; + top:0; + left:0; + right:0; + bottom:0; + overflow-y: scroll; + padding: 8px 20px 20px; +} diff --git a/editor/sass/tab-info.scss b/editor/sass/tab-info.scss index cd278e445..a4d3eea3d 100644 --- a/editor/sass/tab-info.scss +++ b/editor/sass/tab-info.scss @@ -102,6 +102,7 @@ table.node-info tr:not(.blank) td:first-child{ table.node-info tr:not(.blank) td:last-child{ padding: 3px 3px 3px 6px; color: #666; + overflow-y: hidden; } div.node-info { margin: 5px; diff --git a/red/api/editor/projects/index.js b/red/api/editor/projects/index.js index 79a4faad2..207c61dda 100644 --- a/red/api/editor/projects/index.js +++ b/red/api/editor/projects/index.js @@ -80,9 +80,14 @@ module.exports = { } else { res.redirect(303,req.baseUrl + '/'); } - } else if (req.body.credentialSecret || req.body.description || req.body.dependencies) { + } else if (req.body.hasOwnProperty('credentialSecret') || + req.body.hasOwnProperty('description') || + req.body.hasOwnProperty('dependencies')|| + req.body.hasOwnProperty('summary')) { runtime.storage.projects.updateProject(req.params.id, req.body).then(function() { - res.redirect(303,req.baseUrl + '/'); + setTimeout(function() { + res.redirect(303,req.baseUrl + '/'); + },5000); }).otherwise(function(err) { if (err.code) { res.status(400).json({error:err.code, message: err.message}); diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index b1907494e..517d2dd64 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -143,6 +143,7 @@ function getProject(project) { if (missingFiles.indexOf('package.json') === -1) { promises.push(nodeFn.call(fs.readFile,fspath.join(projectPath,"package.json"),"utf8").then(function(content) { var package = util.parseJSON(content); + projectData.summary = package.description||""; projectData.dependencies = package.dependencies||{}; })); } @@ -424,16 +425,22 @@ function updateProject(project,data) { return setCredentialSecret(project,data.credentialSecret).then(function() { return reloadActiveProject(project); }) - } else if (data.description) { + } else if (data.hasOwnProperty('description')) { var projectPath = fspath.join(projectsDir,project); var readmeFile = fspath.join(projectPath,"README.md"); return util.writeFile(readmeFile, data.description); - } else if (data.dependencies) { + } else if (data.hasOwnProperty('dependencies') || data.hasOwnProperty('summary')) { var projectPath = fspath.join(projectsDir,project); var packageJSON = fspath.join(projectPath,"package.json"); return nodeFn.call(fs.readFile,packageJSON,"utf8").then(function(content) { var package = util.parseJSON(content); - package.dependencies = data.dependencies; + if (data.dependencies) { + package.dependencies = data.dependencies; + } + if (data.summary) { + package.description = data.summary; + } + return util.writeFile(packageJSON,JSON.stringify(package,"",4)); }); }