[outline] Add outline section to info sidebar

This commit is contained in:
Nick O'Leary 2020-04-27 11:17:19 +01:00
parent d2d872f51c
commit a5b33d11fc
No known key found for this signature in database
GPG Key ID: 4F2157149161A6C9
7 changed files with 613 additions and 3 deletions

View File

@ -165,6 +165,7 @@ module.exports = function(grunt) {
"packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/palette.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-info-outliner.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-config.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/tab-context.js",
"packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js",

View File

@ -583,7 +583,8 @@
"nodeHelp": "Node Help",
"none":"None",
"arrayItems": "__count__ items",
"showTips":"You can open the tips from the settings panel"
"showTips":"You can open the tips from the settings panel",
"outline": "Outline"
},
"config": {
"name": "Configuration nodes",

View File

@ -0,0 +1,426 @@
RED.sidebar.info.outliner = (function() {
var treeList;
var flowList;
var subflowList;
var globalConfigNodes;
var objects = {};
var objectBacklog = {};
function getFlowData(project) {
var flowData = [
{
label: "Flows",
expanded: true,
children: [
{
id: "__global__",
label: "Global",
children: []
}
]
},
{
label: "Subflows",
children: []
}
]
flowList = flowData[0];
subflowList = flowData[1];
globalConfigNodes = flowList.children[0];
if (project) {
flowData = [
{
element: getProjectLabel(project),
icon: "fa fa-archive",
children: flowData,
expanded: true
}
]
}
return flowData;
}
function getProjectLabel(p) {
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
contentDiv.text(p.name);
var controls = $('<div>',{class:"red-ui-info-outline-item-controls"}).appendTo(div);
var editProjectButton = $('<button class="red-ui-button red-ui-button-small" style="position:absolute;right:2px;"><i class="fa fa-ellipsis-h"></i></button>')
.appendTo(controls)
.on("click", function(evt) {
evt.preventDefault();
RED.projects.editProject();
});
RED.popover.tooltip(editProjectButton,RED._('sidebar.project.showProjectSettings'));
return div;
}
var empties = {};
function getEmptyItem(id) {
var item = {
empty: true,
element: $('<div class="red-ui-info-outline-item red-ui-info-outline-item-empty">').text("empty")
}
empties[id] = item;
return item;
}
function getNodeLabelText(n) {
var label = n.name || n.id;
if (n._def.label) {
try {
label = (typeof n._def.label === "function" ? n._def.label.call(n) : n._def.label)||"";
} catch(err) {
console.log("Definition error: "+type+".label",err);
}
}
return label;
}
function getNodeLabel(n) {
var div = $('<div>',{class:"red-ui-info-outline-item"});
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
if (n.type === "group") {
div.addClass('red-ui-info-outline-item-group')
} else {
var colour = RED.utils.getNodeColor(n.type,n._def);
nodeDiv.css('backgroundColor',colour);
}
var icon_url = RED.utils.getNodeIcon(n._def,n);
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, false);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var labelText = getNodeLabelText(n);
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
if (labelText) {
label.text(labelText)
} else {
label.html("&nbsp;")
}
addControls(n, div);
return div;
}
function getFlowLabel(n) {
var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
contentDiv.text(typeof n === "string"? n : n.label);
addControls(n, div);
return div;
}
function getSubflowLabel(n) {
var div = $('<div>',{class:"red-ui-info-outline-item"});
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
if (n.type === "group") {
div.addClass('red-ui-info-outline-item-group')
} else {
var colour = RED.utils.getNodeColor(n.type,n._def);
nodeDiv.css('backgroundColor',colour);
}
var icon_url = RED.utils.getNodeIcon(n._def,n);
var iconContainer = $('<div/>',{class:"red-ui-palette-icon-container"}).appendTo(nodeDiv);
RED.utils.createIconElement(icon_url, iconContainer, false);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
var labelText = getNodeLabelText(n);
var label = $('<div>',{class:"red-ui-search-result-node-label red-ui-info-outline-item-label"}).appendTo(contentDiv);
if (labelText) {
label.text(labelText)
} else {
label.html("&nbsp;")
}
addControls(n, div);
return div;
// var div = $('<div>',{class:"red-ui-info-outline-item red-ui-info-outline-item-flow"});
// var contentDiv = $('<div>',{class:"red-ui-search-result-description red-ui-info-outline-item-label"}).appendTo(div);
// contentDiv.text(n.name || n.id);
// addControls(n, div);
// return div;
}
function addControls(n,div) {
if (n.type === 'group') {
return;
}
var controls = $('<div>',{class:"red-ui-info-outline-item-controls red-ui-info-outline-item-hover-controls"}).appendTo(div);
if (n._def.button) {
$('<button type="button" class="red-ui-info-outline-item-control-action red-ui-button red-ui-button-small"><i class="fa fa-toggle-right"></i></button>').appendTo(controls).on("click",function(evt) {
evt.preventDefault();
evt.stopPropagation();
RED.view.clickNodeButton(n);
})
}
if (n.type !== 'group') {
$('<button type="button" class="red-ui-info-outline-item-control-reveal red-ui-button red-ui-button-small"><i class="fa fa-eye"></i></button>').appendTo(controls).on("click",function(evt) {
evt.preventDefault();
evt.stopPropagation();
RED.view.reveal(n.id);
})
}
if (n.type !== 'group' && n.type !== 'subflow') {
$('<button type="button" class="red-ui-info-outline-item-control-disable red-ui-button red-ui-button-small"><i class="fa fa-circle-thin"></i><i class="fa fa-ban"></i></button>').appendTo(controls).on("click",function(evt) {
evt.preventDefault();
evt.stopPropagation();
if (n.type === 'tab') {
if (n.disabled) {
RED.workspaces.enable(n.id)
} else {
RED.workspaces.disable(n.id)
}
} else {
// TODO: this ought to be a utility function in RED.nodes
var historyEvent = {
t: "edit",
node: n,
changed: n.changed,
changes: {
d: n.d
},
dirty:RED.nodes.dirty()
}
if (n.d) {
delete n.d;
} else {
n.d = true;
}
n.dirty = true;
n.changed = true;
RED.events.emit("nodes:change",n);
RED.nodes.dirty(true)
RED.view.redraw();
}
});
} else {
$('<div class="red-ui-info-outline-item-control-spacer">').appendTo(controls)
}
controls.find("button").on("dblclick", function(evt) {
evt.preventDefault();
evt.stopPropagation();
})
}
function onProjectLoad(activeProject) {
var newFlowData = getFlowData(activeProject);
treeList.treeList('data',newFlowData);
}
function build() {
var container = $("<div>", {class:"red-ui-info-outline"}).css({'height': '400px'});
var toolbar = $("<div>", {class:"red-ui-info-outline-toolbar"}).appendTo(container);
var searchInput = $('<input type="text">').appendTo(toolbar).searchBox({
delay: 100,
change: function() {
var val = $(this).val().trim().toLowerCase();
if (val) {
var c = treeList.treeList('filter',function(item) {
if (item.depth === 0) {
return true;
}
if (item.id && objects[item.id]) {
var l = ((objects[item.id].type||"")+" "+(objects[item.id].name||"")+" "+(objects[item.id].id||"")+" "+(objects[item.id].label||"")).toLowerCase();
var isMatch = l.indexOf(val) > -1;
if (isMatch) {
return true;
}
}
return false;
})
} else {
treeList.treeList('filter',null);
}
}
});
treeList = $("<div>").css({width: "100%"}).appendTo(container).treeList({
data:getFlowData()
})
// treeList.on('treelistselect', function(e,item) {
// console.log(item)
// RED.view.reveal(item.id);
// })
// treeList.treeList('data',[ ... ] )
treeList.on('treelistconfirm', function(e,item) {
var node = RED.nodes.node(item.id);
if (node) {
if (node._def.category === "config") {
RED.editor.editConfig("", node.type, node.id);
} else {
RED.editor.edit(node);
}
}
})
RED.events.on("projects:load", onProjectLoad)
RED.events.on("flows:add", onFlowAdd)
RED.events.on("flows:remove", onObjectRemove)
RED.events.on("flows:change", onFlowChange)
RED.events.on("flows:reorder", onFlowsReorder)
RED.events.on("subflows:add", onSubflowAdd)
RED.events.on("subflows:remove", onObjectRemove)
RED.events.on("subflows:change", onSubflowChange)
RED.events.on("nodes:add",onNodeAdd);
RED.events.on("nodes:remove",onObjectRemove);
RED.events.on("nodes:change",onNodeChange);
RED.events.on("groups:add",onNodeAdd);
RED.events.on("groups:remove",onObjectRemove);
RED.events.on("groups:change",onNodeChange);
RED.events.on("view:selection-changed", onSelectionChanged);
// ["links","nodes","flows","subflows","groups"].forEach(function(t) {
// ["add","remove","change"].forEach(function(v) {
// RED.events.on(t+":"+v, function(n) { console.log(t+":"+v,n)})
// })
// })
// RED.events.on("workspace:clear", function() { console.log("workspace:clear")})
return container;
}
function onFlowAdd(ws) {
objects[ws.id] = {
id: ws.id,
element: getFlowLabel(ws),
children:[getEmptyItem(ws.id)],
deferBuild: true,
icon: "red-ui-icons red-ui-icons-flow"
}
flowList.treeList.addChild(objects[ws.id])
objects[ws.id].element.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
objects[ws.id].treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!ws.disabled)
}
function onFlowChange(n) {
var existingObject = objects[n.id];
existingObject.element.find(".red-ui-info-outline-item-label").text(n.label || n.id);
existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
existingObject.treeList.container.toggleClass("red-ui-info-outline-item-disabled", !!n.disabled)
}
function onFlowsReorder(order) {
var indexMap = {};
order.forEach(function(id,index) {
indexMap[id] = index;
})
flowList.treeList.sortChildren(function(A,B) {
if (A.id === "__global__") { return -1 }
if (B.id === "__global__") { return 1 }
return indexMap[A.id] - indexMap[B.id]
})
}
function onSubflowAdd(sf) {
objects[sf.id] = {
id: sf.id,
element: getSubflowLabel(sf),
children:[getEmptyItem(sf.id)],
deferBuild: true
}
subflowList.treeList.addChild(objects[sf.id])
}
function onSubflowChange(n) {
var existingObject = objects[n.id];
existingObject.treeList.replaceElement(getSubflowLabel(n));
// existingObject.element.find(".red-ui-info-outline-item-label").text(n.name || n.id);
}
function onNodeChange(n) {
var existingObject = objects[n.id];
var parent = n.g||n.z;
var nodeLabelText = getNodeLabelText(n);
if (nodeLabelText) {
existingObject.element.find(".red-ui-info-outline-item-label").text(nodeLabelText);
} else {
existingObject.element.find(".red-ui-info-outline-item-label").html("&nbsp;");
}
if (parent !== existingObject.parent.id) {
existingObject.treeList.remove();
if (!parent) {
globalConfigNodes.treeList.addChild(existingObject);
} else {
objects[parent].treeList.addChild(existingObject)
}
}
existingObject.element.toggleClass("red-ui-info-outline-item-disabled", !!n.d)
}
function onObjectRemove(n) {
var existingObject = objects[n.id];
existingObject.treeList.remove();
delete objects[n.d]
var parent = existingObject.parent;
if (parent.children.length === 0) {
parent.treeList.addChild(getEmptyItem(parent.id));
}
}
function onNodeAdd(n) {
objects[n.id] = {
id: n.id,
element: getNodeLabel(n)
}
if (n.type === "group") {
objects[n.id].children = [];
objects[n.id].deferBuild = true;
}
var parent = n.g||n.z;
if (parent) {
if (objects[parent]) {
if (empties[parent]) {
empties[parent].treeList.remove();
delete empties[parent];
}
objects[parent].treeList.addChild(objects[n.id])
} else {
// The parent hasn't been added yet
console.log("missing",parent)
}
} else {
// No parent - add to Global flow list
globalConfigNodes.treeList.addChild(objects[n.id])
}
objects[n.id].element.toggleClass("red-ui-info-outline-item-disabled", !!n.d)
}
function onSelectionChanged(selection) {
// treeList.treeList('clearSelection');
// console.log(selection);
if (selection.nodes) {
selection.nodes.forEach(function(n) {
// console.log("..",n.id);
treeList.treeList('show',n.id);
if (objects[n.id].treeList) {
objects[n.id].treeList.select(true);
}
});
}
}
return {
build: build
}
})();

View File

@ -18,6 +18,7 @@ RED.sidebar.info = (function() {
var content;
var sections;
var propertiesSection;
var outlinerSection;
var infoSection;
var helpSection;
var tipBox;
@ -39,6 +40,13 @@ RED.sidebar.info = (function() {
container: stackContainer
}).hide();
outlinerSection = sections.add({
title: RED._("sidebar.info.outline"),
collapsible: true
})
outlinerSection.expand();
RED.sidebar.info.outliner.build().appendTo(outlinerSection.content);
propertiesSection = sections.add({
title: RED._("sidebar.info.info"),
collapsible: true

View File

@ -859,7 +859,9 @@ RED.utils = (function() {
}
function getNodeIcon(def,node) {
if (def.category === 'config') {
if (node && node.type === 'group') {
return "font-awesome/fa-object-group"
} else if (def.category === 'config') {
return RED.settings.apiRootUrl+"icons/node-red/cog.svg"
} else if (node && node.type === 'tab') {
return RED.settings.apiRootUrl+"icons/node-red/subflow.svg"

View File

@ -87,6 +87,8 @@
}
.red-ui-palette-icon {
width: 15px;
position:relative;
left: -1px;
}
.red-ui-search-result-description {
margin-left:28px;
@ -153,7 +155,7 @@
width: 30px;
float:left;
height: 25px;
border-radius: 5px;
border-radius: 3px;
border: 1px solid $node-border;
background-position: 5% 50%;
background-repeat: no-repeat;

View File

@ -279,3 +279,173 @@ div.red-ui-info-table {
border-radius: 4px;
padding: 2px 4px 2px;
}
.red-ui-help-search {
border-bottom: 1px solid $secondary-border-color;
}
.red-ui-info-outline {
display: flex;
flex-direction: column;
.red-ui-treeList {
flex-grow: 1;
}
.red-ui-treeList-container,.red-ui-editableList-border {
border: none;
border-radius: 0;
}
.red-ui-treeList-label {
font-size: 13px;
padding: 2px 0;
overflow: hidden;
}
.red-ui-info-outline-item {
display: inline-block;
padding: 0;
font-size: 13px;
border: none;
.red-ui-palette-icon-fa {
position: relative;
top: 1px;
left: 0px;
}
&:hover {
background: inherit
}
&.red-ui-info-outline-item-flow {
.red-ui-search-result-description {
margin-left: 4px;
}
}
&.red-ui-info-outline-item-group .red-ui-search-result-node {
background: none;
border-color: transparent;
.red-ui-palette-icon-container {
background: none;
}
.red-ui-palette-icon-fa {
color: $secondary-text-color;
font-size: 18px;
}
}
&.red-ui-info-outline-item-empty {
font-style: italic;
color: $form-placeholder-color;
}
}
.red-ui-search-result-node {
width: 24px;
height: 20px;
margin-top: 1px;
}
.red-ui-palette-icon-container {
width: 24px;
}
.red-ui-palette-icon {
width: 20px;
}
.red-ui-search-result-description {
margin-left: 32px;
line-height: 22px;
white-space: nowrap;
}
.red-ui-search-result-node-label {
color: $secondary-text-color;
}
}
.red-ui-info-outline-item-control-spacer {
display: inline-block;
width: 23px;
}
.red-ui-info-outline-item-controls {
position: absolute;
top:0;
bottom: 0;
right: 0px;
padding: 2px 0 0 1px;
text-align: right;
background: $list-item-background;
.red-ui-treeList-label:hover & {
background: $list-item-background-hover;
}
.red-ui-treeList-label.selected & {
background: $list-item-background-selected;
}
&.red-ui-info-outline-item-hover-controls button {
min-width: 23px;
}
.red-ui-treeList-label:not(:hover) &.red-ui-info-outline-item-hover-controls {
button {
border: none;
background: none;
}
}
.red-ui-info-outline-item-control-reveal,
.red-ui-info-outline-item-control-action {
display: none;
}
.red-ui-treeList-label:hover & {
.red-ui-info-outline-item-control-reveal,
.red-ui-info-outline-item-control-action {
display: inline-block;
}
}
.fa-ban {
display: none;
}
.red-ui-info-outline-item.red-ui-info-outline-item-disabled & {
.fa-ban {
display: inline-block;
}
.fa-circle-thin {
display: none;
}
}
button {
margin-right: 3px
}
}
.red-ui-info-outline-item-disabled {
.red-ui-search-result-node {
opacity: 0.4;
}
.red-ui-info-outline-item-label {
font-style: italic;
color: $secondary-text-color-disabled;
}
.red-ui-icons-flow {
opacity: 0.4;
}
}
.red-ui-icons {
display: inline-block;
width: 18px;
&:before {
white-space: pre;
content: ' '
}
}
.red-ui-icons-flow {
background-image: url('images/subflow_tab.svg');
background-repeat: no-repeat;
background-size: contain;
filter: brightness(2.5);
}
.red-ui-info-outline-toolbar {
border-bottom: 1px solid $secondary-border-color;
}