Add workspace search option

This commit is contained in:
Nick O'Leary 2016-09-29 23:46:29 +01:00
parent 9a49fb9450
commit 18c8bbb0fc
12 changed files with 489 additions and 6 deletions

View File

@ -130,6 +130,7 @@ module.exports = function(grunt) {
"editor/js/ui/clipboard.js",
"editor/js/ui/library.js",
"editor/js/ui/notifications.js",
"editor/js/ui/search.js",
"editor/js/ui/subflow.js",
"editor/js/ui/touch/radialMenu.js"
],

BIN
editor/icons/cog.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 493 B

View File

@ -197,6 +197,8 @@ var RED = (function() {
{id:"menu-item-export-library",label:RED._("menu.label.library"),disabled:true,onselect:RED.library.export}
]},
null,
{id:"menu-item-search",label:RED._("menu.label.search"),onselect:RED.search.show},
null,
{id:"menu-item-config-nodes",label:RED._("menu.label.displayConfig"),onselect:function() {}},
{id:"menu-item-workspace",label:RED._("menu.label.flows"),options:[
{id:"menu-item-workspace-add",label:RED._("menu.label.add"),onselect:RED.workspaces.add},
@ -226,6 +228,7 @@ var RED = (function() {
RED.subflow.init();
RED.workspaces.init();
RED.clipboard.init();
RED.search.init();
RED.view.init();
RED.editor.init();

View File

@ -309,7 +309,14 @@ RED.nodes = (function() {
});
sf.name = subflowName;
}
sf._def = {
defaults:{},
icon:"subflow.png",
category: "subflows",
color: "#da9",
inputs: sf.in.length,
outputs: sf.out.length
}
subflows[sf.id] = sf;
RED.nodes.registerType("subflow:"+sf.id, {
defaults:{name:{value:""}},

View File

@ -124,6 +124,7 @@ RED.keyboard = (function() {
'<div style="vertical-align: top;display:inline-block; box-sizing: border-box; width:50%; padding: 10px;">'+
'<table class="keyboard-shortcuts">'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">Space</span></td><td>'+RED._("keyboard.toggleSidebar")+'</td></tr>'+
'<tr><td><span class="help-key">Ctrl/&#8984;</span> + <span class="help-key">.</span></td><td>'+RED._("keyboard.searchBox")+'</td></tr>'+
'<tr><td></td><td></td></tr>'+
'<tr><td><span class="help-key">Delete</span></td><td rowspan="2">'+RED._("keyboard.deleteSelected")+'</td></tr>'+
'<tr><td><span class="help-key">Backspace</span></td></tr>'+

287
editor/js/ui/search.js Normal file
View File

@ -0,0 +1,287 @@
/**
* Copyright 2013, 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
RED.search = (function() {
var dialog = null;
var searchInput;
var searchResults;
var selected = -1;
var index = {};
var keys = [];
var results = [];
function indexNode(n) {
var l = "";
if (n._def && n._def.label) {
l = n._def.label;
try {
l = (typeof l === "function" ? l.call(n) : l);
if (l) {
l = (""+l).toLowerCase();
index[l] = index[l] || {};
index[l][n.id] = {node:n,label:l}
}
} catch(err) {
console.log("Definition error: "+n.type+".label",err);
}
}
l = l||n.label||n.name||n.id||"";
var properties = ['id','type','name','label','info'];
for (var i=0;i<properties.length;i++) {
if (n.hasOwnProperty(properties[i])) {
var v = n[properties[i]];
if (typeof v === 'string' || typeof v === 'number') {
v = (""+v).toLowerCase();
index[v] = index[v] || {};
index[v][n.id] = {node:n,label:l};
}
}
}
}
function indexWorkspace() {
index = {};
RED.nodes.eachWorkspace(indexNode);
RED.nodes.eachSubflow(indexNode);
RED.nodes.eachConfig(indexNode);
RED.nodes.eachNode(indexNode);
keys = Object.keys(index);
keys.sort();
keys.forEach(function(key) {
index[key] = Object.keys(index[key]).map(function(id) {
return index[key][id];
})
})
}
function search(val) {
searchResults.editableList('empty');
selected = -1;
results = [];
if (val.length > 0) {
val = val.toLowerCase();
var i;
var j;
var list = [];
var nodes = {};
for (i=0;i<keys.length;i++) {
var key = keys[i];
var kpos = keys[i].indexOf(val);
if (kpos > -1) {
for (j=0;j<index[key].length;j++) {
var node = index[key][j];
nodes[node.node.id] = nodes[node.node.id] = node;
nodes[node.node.id].index = Math.min(nodes[node.node.id].index||Infinity,kpos);
}
}
}
list = Object.keys(nodes);
list.sort(function(A,B) {
return nodes[A].index - nodes[B].index;
});
for (i=0;i<list.length;i++) {
results.push(nodes[list[i]]);
}
if (results.length > 0) {
for (i=0;i<Math.min(results.length,25);i++) {
searchResults.editableList('addItem',results[i])
}
} else {
searchResults.editableList('addItem',{});
}
}
}
function ensureSelectedIsVisible() {
var selectedEntry = searchResults.find("li.selected");
var scrollWindow = searchResults.parent();
var scrollHeight = scrollWindow.height();
var scrollOffset = scrollWindow.scrollTop();
var y = selectedEntry.position().top;
var h = selectedEntry.height();
if (y+h > scrollHeight) {
scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-10)},50);
} else if (y<0) {
scrollWindow.animate({scrollTop: '+='+(y-10)},50);
}
}
function createDialog() {
dialog = $("<div>",{id:"red-ui-search",class:"red-ui-search"}).appendTo("#main-container");
var searchDiv = $("<div>",{class:"red-ui-search-container"}).appendTo(dialog);
searchInput = $('<input type="text" placeholder="search your flows">').appendTo(searchDiv).searchBox({
delay: 200,
change: function() {
search($(this).val());
}
});
searchInput.on('keydown',function(evt) {
var children;
if (results.length > 0) {
if (evt.keyCode === 40) {
// Down
children = searchResults.children();
if (selected < children.length-1) {
if (selected > -1) {
$(children[selected]).removeClass('selected');
}
selected++;
}
$(children[selected]).addClass('selected');
ensureSelectedIsVisible();
evt.preventDefault();
} else if (evt.keyCode === 38) {
// Up
children = searchResults.children();
if (selected > 0) {
if (selected < children.length) {
$(children[selected]).removeClass('selected');
}
selected--;
}
$(children[selected]).addClass('selected');
ensureSelectedIsVisible();
evt.preventDefault();
} else if (evt.keyCode === 13) {
// Enter
if (results.length > 0) {
reveal(results[Math.max(0,selected)].node);
}
}
}
});
var searchResultsDiv = $("<div>",{class:"red-ui-search-results-container"}).appendTo(dialog);
searchResults = $('<ol>',{id:"search-result-list", style:"position: absolute;top: 5px;bottom: 5px;left: 5px;right: 5px;"}).appendTo(searchResultsDiv).editableList({
addButton: false,
addItem: function(container,i,object) {
var node = object.node;
if (node === undefined) {
$('<div>',{class:"red-ui-search-empty"}).html(RED._('search.empty')).appendTo(container);
} else {
var def = node._def;
var div = $('<a>',{href:'#',class:"red-ui-search-result"}).appendTo(container);
var nodeDiv = $('<div>',{class:"red-ui-search-result-node"}).appendTo(div);
var colour = def.color;
var icon_url = "arrow-in.png";
if (node.type === 'tab') {
colour = "#C0DEED";
icon_url = "subflow.png";
} else if (def.category === 'config') {
icon_url = "cog.png";
} else if (node.type === 'unknown') {
icon_url = "alert.png";
} else {
try {
icon_url = (typeof def.icon === "function" ? def.icon.call({}) : def.icon);
} catch(err) {
console.log("Definition error: "+nt+".icon",err);
}
}
nodeDiv.css('backgroundColor',colour);
var iconContainer = $('<div/>',{class:"palette_icon_container"}).appendTo(nodeDiv);
$('<div/>',{class:"palette_icon",style:"background-image: url(icons/"+icon_url+")"}).appendTo(iconContainer);
var contentDiv = $('<div>',{class:"red-ui-search-result-description"}).appendTo(div);
if (node.z) {
var workspace = RED.nodes.workspace(node.z);
if (!workspace) {
workspace = RED.nodes.subflow(node.z);
workspace = "subflow:"+workspace.name;
} else {
workspace = "flow:"+workspace.label;
}
$('<div>',{class:"red-ui-search-result-node-flow"}).html(workspace).appendTo(contentDiv);
}
$('<div>',{class:"red-ui-search-result-node-label"}).html(object.label || node.id).appendTo(contentDiv);
$('<div>',{class:"red-ui-search-result-node-type"}).html(node.type).appendTo(contentDiv);
$('<div>',{class:"red-ui-search-result-node-id"}).html(node.id).appendTo(contentDiv);
div.click(function(evt) {
evt.preventDefault();
reveal(node);
});
}
},
scrollOnAdd: false
});
}
function reveal(node) {
hide();
RED.view.reveal(node.id);
// if (node.type === 'tab' || node.type === 'subflow') {
// RED.workspaces.show(node.id);
// } else {
// if (node._def.category !== 'config' && node.z) {
// RED.workspaces.show(node.z);
// }
// }
}
var visible = false;
function show() {
if (!visible) {
RED.keyboard.add("*",/* ESCAPE */ 27,function(){hide();d3.event.preventDefault();});
$("#header-shade").show();
$("#editor-shade").show();
$("#palette-shade").show();
$("#sidebar-shade").show();
indexWorkspace();
if (dialog === null) {
createDialog();
}
dialog.slideDown();
visible = true;
}
searchInput.focus();
}
function hide() {
if (visible) {
RED.keyboard.remove(/* ESCAPE */ 27);
visible = false;
$("#header-shade").hide();
$("#editor-shade").hide();
$("#palette-shade").hide();
$("#sidebar-shade").hide();
if (dialog !== null) {
dialog.slideUp(200,function() {
searchInput.searchBox('value','');
});
}
}
}
function init() {
RED.keyboard.add("*",/* . */ 190,{ctrl:true},function(){show();d3.event.preventDefault();});
}
return {
init: init,
show: show,
hide: hide
};
})();

View File

@ -149,7 +149,7 @@ RED.sidebar.config = (function() {
currentType = node.type;
}
var entry = $('<li class="palette_node config_node"></li>').appendTo(list);
var entry = $('<li class="palette_node config_node palette_node_id_'+node.id.replace(/\./g,"-")+'"></li>').appendTo(list);
$('<div class="palette_label"></div>').text(label).appendTo(entry);
var iconContainer = $('<div/>',{class:"palette_icon_container palette_icon_container_right"}).text(node.users.length).appendTo(entry);
@ -280,8 +280,8 @@ RED.sidebar.config = (function() {
}
function show(unused) {
if (unused !== undefined) {
function show(id) {
if (typeof id === 'boolean') {
if (unused) {
$('#workspace-config-node-filter-unused').click();
} else {
@ -289,6 +289,36 @@ RED.sidebar.config = (function() {
}
}
refreshConfigNodeList();
if (typeof id === "string") {
$('#workspace-config-node-filter-all').click();
id = id.replace(/\./g,"-");
setTimeout(function() {
var node = $(".palette_node_id_"+id);
var y = node.position().top;
var h = node.height();
var scrollWindow = $(".sidebar-node-config");
var scrollHeight = scrollWindow.height();
if (y+h > scrollHeight) {
scrollWindow.animate({scrollTop: '-='+(scrollHeight-(y+h)-30)},150);
} else if (y<0) {
scrollWindow.animate({scrollTop: '+='+(y-10)},150);
}
var flash = 21;
var flashFunc = function() {
if ((flash%2)===0) {
node.removeClass('node_highlighted');
} else {
node.addClass('node_highlighted');
}
flash--;
if (flash >= 0) {
setTimeout(flashFunc,100);
}
}
flashFunc();
},100);
}
RED.sidebar.show("config");
}
return {

View File

@ -2385,6 +2385,46 @@ RED.view = (function() {
}
}
return result;
},
reveal: function(id) {
if (RED.nodes.workspace(id) || RED.nodes.subflow(id)) {
RED.workspaces.show(id);
} else {
var node = RED.nodes.node(id);
if (node._def.category !== 'config' && node.z) {
node.highlighted = true;
node.dirty = true;
RED.workspaces.show(node.z);
RED.view.redraw();
var screenSize = [$("#chart").width(),$("#chart").height()];
var scrollPos = [$("#chart").scrollLeft(),$("#chart").scrollTop()];
if (node.x < scrollPos[0] || node.y < scrollPos[1] || node.x > screenSize[0]+scrollPos[0] || node.y > screenSize[1]+scrollPos[1]) {
var deltaX = '-='+((scrollPos[0] - node.x) + screenSize[0]/2);
var deltaY = '-='+((scrollPos[1] - node.y) + screenSize[1]/2);
$("#chart").animate({
scrollLeft: deltaX,
scrollTop: deltaY
},200);
}
var flash = 22;
var flashFunc = function() {
flash--;
node.highlighted = !node.highlighted;
node.dirty = true;
RED.view.redraw();
if (flash >= 0) {
setTimeout(flashFunc,100);
}
}
flashFunc();
} else if (node._def.category === 'config') {
RED.sidebar.config.show(id);
}
}
}
};
})();

View File

@ -157,6 +157,8 @@
stroke: $node-selected-color !important;
}
.node_highlighted {
border-color: #dd1616 !important;
border-style: dashed !important;
stroke: #dd1616;
stroke-width: 2;
stroke-dasharray: 10, 4;

106
editor/sass/search.scss Normal file
View File

@ -0,0 +1,106 @@
/**
* Copyright 2016 IBM Corp.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
**/
.red-ui-search {
z-index:1000;
display: none;
position: absolute;
width: 500px;
background: white;
left: 50%;
margin-left: -250px;
top: 0px;
border: 1px solid $primary-border-color;
box-shadow: 0 0 10px rgba(0,0,0,0.4);
ol {
}
}
.red-ui-search-container {
padding: 3px;
border-bottom: 1px solid $secondary-border-color;
}
.red-ui-search-results-container {
position:relative;
height: 300px;
padding: 5px;
background: $background-color;
.red-ui-editableList-container {
background: white;
border-radius: 2px;
padding: 0;
background: $background-color;
li {
padding: 0;
&.selected .red-ui-search-result {
border-color: $primary-border-color;
}
}
}
}
.red-ui-search-result {
padding: 8px 2px 8px 5px;
display: block;
cursor: pointer;
color: $form-text-color;
border: 2px solid white;
&:hover {
text-decoration: none;
border-color: $primary-border-color;
color: $form-text-color;
}
}
.red-ui-search-result-node {
display: inline-block;
width: 30px;
float:left;
height: 25px;
background: #ddd;
border-radius: 5px;
border: 1px solid #999;
background-position: 5% 50%;
background-repeat: no-repeat;
background-size: contain;
position: relative;
}
.red-ui-search-result-description {
margin-left: 40px;
margin-right: 5px;
}
.red-ui-search-result-node-label {
font-weight: bold;
}
.red-ui-search-result-node-type {
font-style: italic;
}
.red-ui-search-result-node-flow {
float:right;
font-size: 0.9em;
}
.red-ui-search-result-node-id {
display:none;
font-size: 0.9em;
}
.red-ui-search-empty {
padding: 10px;
text-align: center;
font-style: italic;
background: $background-color;
color: $form-placeholder-color;
}

View File

@ -34,6 +34,7 @@
@import "editor";
@import "library";
@import "search";
@import "tabs";
@import "tab-config";

View File

@ -39,6 +39,7 @@
"displayConfig": "Configuration nodes",
"import": "Import",
"export": "Export",
"search": "Find",
"clipboard": "Clipboard",
"library": "Library",
"examples": "Examples",
@ -164,7 +165,7 @@
"addRemoveNode": "Add/remove node from selection",
"deleteSelected": "Delete selected nodes or link",
"importNode": "Import nodes",
"exportNode": "Export selected nodes",
"exportNode": "Export nodes",
"nudgeNode": "Move selected node(s) by a small amount",
"moveNode": "Move selected node(s) by a large amount",
"toggleSidebar": "Toggle sidebar",
@ -172,7 +173,8 @@
"copyNode": "Copy selected nodes",
"cutNode": "Cut selected nodes",
"pasteNode": "Paste nodes",
"undoChange": "Undo the last change performed"
"undoChange": "Undo the last change performed",
"searchBox": "Open search box"
},
"library": {
"openLibrary": "Open Library...",
@ -305,5 +307,8 @@
},
"editableList": {
"add": "add"
},
"search": {
"empty": "No matches found"
}
}