diff --git a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json index aae72ab3a..5cadb805f 100644 --- a/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json +++ b/packages/node_modules/@node-red/editor-client/locales/en-US/editor.json @@ -586,6 +586,7 @@ "editor": { "title": "Manage palette", "palette": "Palette", + "allCatalogs": "All Catalogs", "times": { "seconds": "seconds ago", "minutes": "minutes ago", diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js index 34d3ba160..24f9c1909 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette-editor.js @@ -16,15 +16,17 @@ RED.palette.editor = (function() { var disabled = false; - + let catalogues = [] + const loadedCatalogs = [] var editorTabs; - var filterInput; - var searchInput; - var nodeList; - var packageList; - var loadedList = []; - var filteredList = []; - var loadedIndex = {}; + let filterInput; + let searchInput; + let nodeList; + let packageList; + let fullList = [] + let loadedList = []; + let filteredList = []; + let loadedIndex = {}; var typesInUse = {}; var nodeEntries = {}; @@ -162,7 +164,6 @@ RED.palette.editor = (function() { } } - function getContrastingBorder(rgbColor){ var parts = /^rgba?\(\s*(\d+),\s*(\d+),\s*(\d+)[,)]/.exec(rgbColor); if (parts) { @@ -369,10 +370,10 @@ RED.palette.editor = (function() { var activeSort = sortModulesRelevance; function handleCatalogResponse(err,catalog,index,v) { + const url = catalog.url catalogueLoadStatus.push(err||v); if (!err) { if (v.modules) { - var a = false; v.modules = v.modules.filter(function(m) { if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) { loadedIndex[m.id] = m; @@ -389,13 +390,14 @@ RED.palette.editor = (function() { m.timestamp = 0; } m.index = m.index.join(",").toLowerCase(); + m.catalog = catalog; + m.catalogIndex = index; return true; } return false; }) loadedList = loadedList.concat(v.modules); } - searchInput.searchBox('count',loadedList.length); } else { catalogueLoadErrors = true; } @@ -404,7 +406,7 @@ RED.palette.editor = (function() { } if (catalogueLoadStatus.length === catalogueCount) { 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); setTimeout(function() { @@ -416,12 +418,13 @@ RED.palette.editor = (function() { function initInstallTab() { if (loadedList.length === 0) { + fullList = []; loadedList = []; loadedIndex = {}; packageList.editableList('empty'); $(".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 = []; catalogueLoadErrors = false; catalogueCount = catalogues.length; @@ -431,23 +434,92 @@ RED.palette.editor = (function() { $("#red-ui-palette-module-install-shade").show(); catalogueLoadStart = Date.now(); var handled = 0; - catalogues.forEach(function(catalog,index) { - $.getJSON(catalog, {_: new Date().getTime()},function(v) { - handleCatalogResponse(null,catalog,index,v); + loadedCatalogs.length = 0; // clear the loadedCatalogs array + for (let index = 0; index < catalogues.length; index++) { + 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(); }).fail(function(jqxhr, textStatus, error) { - console.warn("Error loading catalog",catalog,":",error); - handleCatalogResponse(jqxhr,catalog,index); + console.warn("Error loading catalog",url,":",error); + handleCatalogResponse(jqxhr,url,index); }).always(function() { handled++; 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($('`) + } + // 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(``) + 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() { packageList.editableList('empty'); var currentFilter = searchInput.searchBox('value').trim(); @@ -462,7 +534,6 @@ RED.palette.editor = (function() { if (filteredList.length === 0) { packageList.editableList('addItem',{}); } - if (filteredList.length > 10) { packageList.editableList('addItem',{start:10,more:filteredList.length-10}) } @@ -492,6 +563,7 @@ RED.palette.editor = (function() { var updateDenyList = []; function init() { + catalogues = RED.settings.theme('palette.catalogues')||['https://catalogue.nodered.org/catalogue.json'] if (RED.settings.get('externalModules.palette.allowInstall', true) === false) { return; } @@ -669,7 +741,8 @@ RED.palette.editor = (function() { }); - nodeList = $('
    ',{id:"red-ui-palette-module-list", style:"position: absolute;top: 35px;bottom: 0;left: 0;right: 0px;"}).appendTo(modulesTab).editableList({ + nodeList = $('
      ',{id:"red-ui-palette-module-list"}).appendTo(modulesTab).editableList({ + class: "scrollable", addButton: false, scrollOnAdd: false, sort: function(A,B) { @@ -800,21 +873,20 @@ RED.palette.editor = (function() { $('
      ',{class:"red-ui-search-empty"}).text(RED._('search.empty')).appendTo(container); } } - }); + }) } function createInstallTab(content) { - var installTab = $('
      ',{class:"red-ui-palette-editor-tab hide"}).appendTo(content); - + const installTab = $('
      ',{class:"red-ui-palette-editor-tab", style: "display: none;"}).appendTo(content); editorTabs.addTab({ id: 'install', label: RED._('palette.editor.tab-install'), content: installTab }) - var toolBar = $('
      ',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab); - - var searchDiv = $('
      ',{class:"red-ui-palette-search"}).appendTo(installTab); + const toolBar = $('
      ',{class:"red-ui-palette-editor-toolbar"}).appendTo(installTab); + + const searchDiv = $('
      ',{class:"red-ui-palette-search"}).appendTo(installTab); searchInput = $('') .appendTo(searchDiv) .searchBox({ @@ -831,19 +903,25 @@ RED.palette.editor = (function() { searchInput.searchBox('count',loadedList.length); packageList.editableList('empty'); packageList.editableList('addItem',{count:loadedList.length}); - } } }); - $('').text(RED._("palette.editor.sort")+' ').appendTo(toolBar); - var sortGroup = $('').appendTo(toolBar); - var sortRelevance = $('').appendTo(sortGroup); - var sortAZ = $('').appendTo(sortGroup); - var sortRecent = $('').appendTo(sortGroup); + const catalogSelection = $('').appendTo(uploadSpan); var uploadInput = uploadButton.find('input[type="file"]'); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss index ca387782b..947ada2e8 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette-editor.scss @@ -14,7 +14,7 @@ * limitations under the License. **/ -#red-ui-settings-tab-palette { + #red-ui-settings-tab-palette { height: 100%; } @@ -28,7 +28,17 @@ padding: 0; box-sizing:border-box; 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 { border: none; border-radius: 0; @@ -72,11 +82,9 @@ } .red-ui-palette-editor-tab { - position:absolute; - top:35px; - left:0; - right:0; - bottom:0 + display: flex; + flex-direction: column; + min-height: 0; } .red-ui-palette-editor-toolbar { background: var(--red-ui-primary-background); @@ -84,6 +92,24 @@ padding: 8px 10px; border-bottom: 1px solid var(--red-ui-primary-border-color); 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 { color: var(--red-ui-secondary-text-color); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss index e4071b9db..a3afc3d76 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/palette.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/palette.scss @@ -54,7 +54,7 @@ } .red-ui-palette-search { position: relative; - overflow: hidden; + // overflow: hidden; background: var(--red-ui-form-input-background); text-align: center; height: 35px;