1
0
mirror of https://github.com/node-red/node-red.git synced 2023-10-10 13:36:53 +02:00

Merge pull request #4248 from node-red/node-catalog-filter

Improve Catalogue visibility
This commit is contained in:
Nick O'Leary 2023-07-14 16:35:54 +01:00 committed by GitHub
commit 3fad690d1e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 45 deletions

View File

@ -586,6 +586,7 @@
"editor": { "editor": {
"title": "Manage palette", "title": "Manage palette",
"palette": "Palette", "palette": "Palette",
"allCatalogs": "All Catalogs",
"times": { "times": {
"seconds": "seconds ago", "seconds": "seconds ago",
"minutes": "minutes ago", "minutes": "minutes ago",

View File

@ -16,15 +16,17 @@
RED.palette.editor = (function() { RED.palette.editor = (function() {
var disabled = false; var disabled = false;
let catalogues = []
const loadedCatalogs = []
var editorTabs; var editorTabs;
var filterInput; let filterInput;
var searchInput; let searchInput;
var nodeList; let nodeList;
var packageList; let packageList;
var loadedList = []; let fullList = []
var filteredList = []; let loadedList = [];
var loadedIndex = {}; let filteredList = [];
let loadedIndex = {};
var typesInUse = {}; var typesInUse = {};
var nodeEntries = {}; var nodeEntries = {};
@ -162,7 +164,6 @@ RED.palette.editor = (function() {
} }
} }
function getContrastingBorder(rgbColor){ function getContrastingBorder(rgbColor){
var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor); var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor);
if (parts) { if (parts) {
@ -369,10 +370,10 @@ RED.palette.editor = (function() {
var activeSort = sortModulesRelevance; var activeSort = sortModulesRelevance;
function handleCatalogResponse(err,catalog,index,v) { function handleCatalogResponse(err,catalog,index,v) {
const url = catalog.url
catalogueLoadStatus.push(err||v); catalogueLoadStatus.push(err||v);
if (!err) { if (!err) {
if (v.modules) { if (v.modules) {
var a = false;
v.modules = v.modules.filter(function(m) { v.modules = v.modules.filter(function(m) {
if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) { if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) {
loadedIndex[m.id] = m; loadedIndex[m.id] = m;
@ -389,13 +390,14 @@ RED.palette.editor = (function() {
m.timestamp = 0; m.timestamp = 0;
} }
m.index = m.index.join(",").toLowerCase(); m.index = m.index.join(",").toLowerCase();
m.catalog = catalog;
m.catalogIndex = index;
return true; return true;
} }
return false; return false;
}) })
loadedList = loadedList.concat(v.modules); loadedList = loadedList.concat(v.modules);
} }
searchInput.searchBox('count',loadedList.length);
} else { } else {
catalogueLoadErrors = true; catalogueLoadErrors = true;
} }
@ -404,7 +406,7 @@ RED.palette.editor = (function() {
} }
if (catalogueLoadStatus.length === catalogueCount) { if (catalogueLoadStatus.length === catalogueCount) {
if (catalogueLoadErrors) { if (catalogueLoadErrors) {
RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: catalog}),"error",false,8000); RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000);
} }
var delta = 250-(Date.now() - catalogueLoadStart); var delta = 250-(Date.now() - catalogueLoadStart);
setTimeout(function() { setTimeout(function() {
@ -416,12 +418,13 @@ RED.palette.editor = (function() {
function initInstallTab() { function initInstallTab() {
if (loadedList.length === 0) { if (loadedList.length === 0) {
fullList = [];
loadedList = []; loadedList = [];
loadedIndex = {}; loadedIndex = {};
packageList.editableList('empty'); packageList.editableList('empty');
$(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading')); $(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading'));
var catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'];
catalogueLoadStatus = []; catalogueLoadStatus = [];
catalogueLoadErrors = false; catalogueLoadErrors = false;
catalogueCount = catalogues.length; catalogueCount = catalogues.length;
@ -431,23 +434,92 @@ RED.palette.editor = (function() {
$("#red-ui-palette-module-install-shade").show(); $("#red-ui-palette-module-install-shade").show();
catalogueLoadStart = Date.now(); catalogueLoadStart = Date.now();
var handled = 0; var handled = 0;
catalogues.forEach(function(catalog,index) { loadedCatalogs.length = 0; // clear the loadedCatalogs array
$.getJSON(catalog, {_: new Date().getTime()},function(v) { for (let index = 0; index < catalogues.length; index++) {
handleCatalogResponse(null,catalog,index,v); const url = catalogues[index];
$.getJSON(url, {_: new Date().getTime()},function(v) {
loadedCatalogs.push({ index: index, url: url, name: v.name, updated_at: v.updated_at, modules_count: (v.modules || []).length })
handleCatalogResponse(null,{ url: url, name: v.name},index,v);
refreshNodeModuleList(); refreshNodeModuleList();
}).fail(function(jqxhr, textStatus, error) { }).fail(function(jqxhr, textStatus, error) {
console.warn("Error loading catalog",catalog,":",error); console.warn("Error loading catalog",url,":",error);
handleCatalogResponse(jqxhr,catalog,index); handleCatalogResponse(jqxhr,url,index);
}).always(function() { }).always(function() {
handled++; handled++;
if (handled === catalogueCount) { if (handled === catalogueCount) {
searchInput.searchBox('change'); //sort loadedCatalogs by e.index ascending
loadedCatalogs.sort((a, b) => a.index - b.index)
updateCatalogFilter(loadedCatalogs)
} }
}) })
}); }
} }
} }
/**
* Refreshes the catalog filter dropdown and updates local variables
* @param {[{url:String, name:String, updated_at:String, modules_count:Number}]} catalogEntries
*/
function updateCatalogFilter(catalogEntries, maxRetry = 3) {
// clean up existing filters
const catalogSelection = $('#red-catalogue-filter-select')
if (catalogSelection.length === 0) {
// sidebar not yet loaded (red-catalogue-filter-select is not in dom)
if (maxRetry > 0) {
// console.log("updateCatalogFilter: sidebar not yet loaded, retrying in 100ms")
// try again in 100ms
setTimeout(() => {
updateCatalogFilter(catalogEntries, maxRetry - 1)
}, 100);
return;
}
return; // give up
}
catalogSelection.off("change") // remove any existing event handlers
catalogSelection.attr('disabled', 'disabled')
catalogSelection.empty()
catalogSelection.append($('<option>', { value: "loading", text: RED._('palette.editor.loading'), disabled: true, selected: true }));
fullList = loadedList.slice()
catalogSelection.empty() // clear the select list
// loop through catalogTypes, and an option entry per catalog
for (let index = 0; index < catalogEntries.length; index++) {
const catalog = catalogEntries[index];
catalogSelection.append(`<option value="${catalog.name}">${catalog.name}</option>`)
}
// select the 1st option in the select list
catalogSelection.val(catalogSelection.find('option:first').val())
// if there is only 1 catalog, hide the select
if (catalogEntries.length > 1) {
catalogSelection.prepend(`<option value="all">${RED._('palette.editor.allCatalogs')}</option>`)
catalogSelection.removeAttr('disabled') // permit the user to select a catalog
}
// refresh the searchInput counter and trigger a change
filterByCatalog(catalogSelection.val())
searchInput.searchBox('change');
// hook up the change event handler
catalogSelection.on("change", function() {
const selectedCatalog = $(this).val();
filterByCatalog(selectedCatalog);
searchInput.searchBox('change');
})
}
function filterByCatalog(selectedCatalog) {
if (loadedCatalogs.length <= 1 || selectedCatalog === "all") {
loadedList = fullList.slice();
} else {
loadedList = fullList.filter(function(m) {
return (m.catalog.name === selectedCatalog);
})
}
refreshFilteredItems();
searchInput.searchBox('count',filteredList.length+" / "+loadedList.length);
}
function refreshFilteredItems() { function refreshFilteredItems() {
packageList.editableList('empty'); packageList.editableList('empty');
var currentFilter = searchInput.searchBox('value').trim(); var currentFilter = searchInput.searchBox('value').trim();
@ -462,7 +534,6 @@ RED.palette.editor = (function() {
if (filteredList.length === 0) { if (filteredList.length === 0) {
packageList.editableList('addItem',{}); packageList.editableList('addItem',{});
} }
if (filteredList.length > 10) { if (filteredList.length > 10) {
packageList.editableList('addItem',{start:10,more:filteredList.length-10}) packageList.editableList('addItem',{start:10,more:filteredList.length-10})
} }
@ -492,6 +563,7 @@ RED.palette.editor = (function() {
var updateDenyList = []; var updateDenyList = [];
function init() { function init() {
catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json']
if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { if (RED.settings.get('externalModules.palette.allowInstall', true) === false) {
return; return;
} }
@ -669,7 +741,8 @@ RED.palette.editor = (function() {
}); });
nodeList = $('<ol>',{id:"red-ui-palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({ nodeList = $('<ol>',{id:"red-ui-palette-module-list"}).appendTo(modulesTab).editableList({
class: "scrollable",
addButton: false, addButton: false,
scrollOnAdd: false, scrollOnAdd: false,
sort: function(A,B) { sort: function(A,B) {
@ -800,21 +873,20 @@ RED.palette.editor = (function() {
$('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container); $('<div>',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container);
} }
} }
}); })
} }
function createInstallTab(content) { function createInstallTab(content) {
var installTab = $('<div>',{class:"red-ui-palette-editor-tab hide"}).appendTo(content); const installTab = $('<div>',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content);
editorTabs.addTab({ editorTabs.addTab({
id: 'install', id: 'install',
label: RED._('palette.editor.tab-install'), label: RED._('palette.editor.tab-install'),
content: installTab content: installTab
}) })
var toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab); const toolBar = $('<div>',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab);
var searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab); const searchDiv = $('<div>',{class:"red-ui-palette-search"}).appendTo(installTab);
searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>') searchInput = $('<input type="text" data-i18n="[placeholder]palette.search"></input>')
.appendTo(searchDiv) .appendTo(searchDiv)
.searchBox({ .searchBox({
@ -831,19 +903,25 @@ RED.palette.editor = (function() {
searchInput.searchBox('count',loadedList.length); searchInput.searchBox('count',loadedList.length);
packageList.editableList('empty'); packageList.editableList('empty');
packageList.editableList('addItem',{count:loadedList.length}); packageList.editableList('addItem',{count:loadedList.length});
} }
} }
}); });
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBar); const catalogSelection = $('<select id="red-catalogue-filter-select">').appendTo(toolBar);
var sortGroup = $('<span class="button-group"></span>').appendTo(toolBar); catalogSelection.addClass('red-ui-palette-editor-catalogue-filter');
var sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
var sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortAZ"></a>').appendTo(sortGroup); const toolBarActions = $('<div>',{class:"red-ui-palette-editor-toolbar-actions"}).appendTo(toolBar);
var sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle" data-i18n="palette.editor.sortRecent"></a>').appendTo(sortGroup);
$('<span>').text(RED._("palette.editor.sort")+' ').appendTo(toolBarActions);
const sortGroup = $('<span class="button-group"></span>').appendTo(toolBarActions);
const sortRelevance = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle selected"><i class="fa fa-sort-amount-desc"></i></a>').appendTo(sortGroup);
const sortAZ = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-sort-alpha-asc"></i></a>').appendTo(sortGroup);
const sortRecent = $('<a href="#" class="red-ui-palette-editor-install-sort-option red-ui-sidebar-header-button-toggle"><i class="fa fa-calendar"></i></a>').appendTo(sortGroup);
RED.popover.tooltip(sortAZ,RED._("palette.editor.sortAZ"));
RED.popover.tooltip(sortRecent,RED._("palette.editor.sortRecent"));
var sortOpts = [ const sortOpts = [
{button: sortRelevance, func: sortModulesRelevance}, {button: sortRelevance, func: sortModulesRelevance},
{button: sortAZ, func: sortModulesAZ}, {button: sortAZ, func: sortModulesAZ},
{button: sortRecent, func: sortModulesRecent} {button: sortRecent, func: sortModulesRecent}
@ -861,7 +939,7 @@ RED.palette.editor = (function() {
}); });
}); });
var refreshSpan = $('<span>').appendTo(toolBar); var refreshSpan = $('<span>').appendTo(toolBarActions);
var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan); var refreshButton = $('<a href="#" class="red-ui-sidebar-header-button"><i class="fa fa-refresh"></i></a>').appendTo(refreshSpan);
refreshButton.on("click", function(e) { refreshButton.on("click", function(e) {
e.preventDefault(); e.preventDefault();
@ -871,7 +949,8 @@ RED.palette.editor = (function() {
}) })
RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh")); RED.popover.tooltip(refreshButton,RED._("palette.editor.refresh"));
packageList = $('<ol>',{style:"position: absolute;top: 79px;bottom: 0;left: 0;right: 0px;"}).appendTo(installTab).editableList({ packageList = $('<ol>').appendTo(installTab).editableList({
class: "scrollable",
addButton: false, addButton: false,
scrollOnAdd: false, scrollOnAdd: false,
addItem: function(container,i,object) { addItem: function(container,i,object) {
@ -906,6 +985,9 @@ RED.palette.editor = (function() {
var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow); var metaRow = $('<div class="red-ui-palette-module-meta"></div>').appendTo(headerRow);
$('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow); $('<span class="red-ui-palette-module-version"><i class="fa fa-tag"></i> '+entry.version+'</span>').appendTo(metaRow);
$('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow); $('<span class="red-ui-palette-module-updated"><i class="fa fa-calendar"></i> '+formatUpdatedAt(entry.updated_at)+'</span>').appendTo(metaRow);
if (loadedCatalogs.length > 1) {
$('<span class="red-ui-palette-module-updated"><i class="fa fa-cubes"></i>' + (entry.catalog.name || entry.catalog.url) + '</span>').appendTo(metaRow);
}
var duplicateType = false; var duplicateType = false;
if (entry.types && entry.types.length > 0) { if (entry.types && entry.types.length > 0) {
@ -952,9 +1034,10 @@ RED.palette.editor = (function() {
} }
} }
}); });
if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) { if (RED.settings.get('externalModules.palette.allowUpload', true) !== false) {
var uploadSpan = $('<span class="button-group">').prependTo(toolBar); var uploadSpan = $('<span class="button-group">').prependTo(toolBarActions);
var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan); var uploadButton = $('<button type="button" class="red-ui-sidebar-header-button red-ui-palette-editor-upload-button"><label><i class="fa fa-upload"></i><form id="red-ui-palette-editor-upload-form" enctype="multipart/form-data"><input name="tarball" type="file" accept=".tgz"></label></button>').appendTo(uploadSpan);
var uploadInput = uploadButton.find('input[type="file"]'); var uploadInput = uploadButton.find('input[type="file"]');

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
**/ **/
#red-ui-settings-tab-palette { #red-ui-settings-tab-palette {
height: 100%; height: 100%;
} }
@ -28,7 +28,17 @@
padding: 0; padding: 0;
box-sizing:border-box; box-sizing:border-box;
background: var(--red-ui-secondary-background); background: var(--red-ui-secondary-background);
display: flex;
flex-direction: column;
.red-ui-tabs {
flex-shrink: 0;
margin-bottom: 0;
}
.red-ui-editableList.scrollable {
overflow-y: auto;
}
.red-ui-editableList-container { .red-ui-editableList-container {
border: none; border: none;
border-radius: 0; border-radius: 0;
@ -72,11 +82,9 @@
} }
.red-ui-palette-editor-tab { .red-ui-palette-editor-tab {
position:absolute; display: flex;
top:35px; flex-direction: column;
left:0; min-height: 0;
right:0;
bottom:0
} }
.red-ui-palette-editor-toolbar { .red-ui-palette-editor-toolbar {
background: var(--red-ui-primary-background); background: var(--red-ui-primary-background);
@ -84,6 +92,24 @@
padding: 8px 10px; padding: 8px 10px;
border-bottom: 1px solid var(--red-ui-primary-border-color); border-bottom: 1px solid var(--red-ui-primary-border-color);
text-align: right; text-align: right;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 3px 12px;
.red-ui-palette-editor-toolbar-actions {
flex-shrink: 0;
flex-grow: 1;
}
.red-ui-palette-editor-catalogue-filter {
width: unset;
margin: 0;
flex-shrink: 1;
flex-grow: 1;
font-size: 12px;
height: 26px;
padding: 1px;
}
} }
.red-ui-palette-module-shade-status { .red-ui-palette-module-shade-status {
color: var(--red-ui-secondary-text-color); color: var(--red-ui-secondary-text-color);

View File

@ -54,7 +54,7 @@
} }
.red-ui-palette-search { .red-ui-palette-search {
position: relative; position: relative;
overflow: hidden; // overflow: hidden;
background: var(--red-ui-form-input-background); background: var(--red-ui-form-input-background);
text-align: center; text-align: center;
height: 35px; height: 35px;