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 a88890125..9cc557efd 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 @@ -620,6 +620,8 @@ "pluginCount_plural": "__count__ plugins", "moduleCount": "__count__ module available", "moduleCount_plural": "__count__ modules available", + "updateCount": "__count__ update available", + "updateCount_plural": "__count__ updates available", "inuse": "in use", "enableall": "enable all", "disableall": "disable all", 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 3c0c36dff..1c2a7d2f1 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 @@ -426,54 +426,62 @@ RED.palette.editor = (function() { var catalogueCount; var catalogueLoadStatus = []; var catalogueLoadStart; - var catalogueLoadErrors = false; var activeSort = sortModulesRelevance; - function handleCatalogResponse(err,catalog,index,v) { - const url = catalog.url - catalogueLoadStatus.push(err||v); - if (!err) { - if (v.modules) { - v.modules = v.modules.filter(function(m) { - if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) { - loadedIndex[m.id] = m; - m.index = [m.id]; - if (m.keywords) { - m.index = m.index.concat(m.keywords); - } - if (m.types) { - m.index = m.index.concat(m.types); - } - if (m.updated_at) { - m.timestamp = new Date(m.updated_at).getTime(); - } else { - m.timestamp = 0; - } - m.index = m.index.join(",").toLowerCase(); - m.catalog = catalog; - m.catalogIndex = index; - return true; + function refreshCatalogues (done) { + catalogueLoadStatus = []; + catalogueCount = catalogues.length; + loadedList = [] + loadedIndex = {} + loadedCatalogs.length = 0 + let handled = 0 + 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({ url: url, name: v.name},index,v); + }).fail(function(jqxhr, textStatus, error) { + console.warn("Error loading catalog",url,":",error); + }).always(function() { + handled++; + if (handled === catalogueCount) { + //sort loadedCatalogs by e.index ascending + loadedCatalogs.sort((a, b) => a.index - b.index) + refreshUpdateStatus(); + if (done) { + done() } - return false; - }) - loadedList = loadedList.concat(v.modules); - } - } else { - catalogueLoadErrors = true; + } + }) } - if (catalogueCount > 1) { - $(".red-ui-palette-module-shade-status").html(RED._('palette.editor.loading')+"
"+catalogueLoadStatus.length+"/"+catalogueCount); - } - if (catalogueLoadStatus.length === catalogueCount) { - if (catalogueLoadErrors) { - RED.notify(RED._('palette.editor.errors.catalogLoadFailed',{url: url}),"error",false,8000); - } - var delta = 250-(Date.now() - catalogueLoadStart); - setTimeout(function() { - $("#red-ui-palette-module-install-shade").hide(); - },Math.max(delta,0)); + } + function handleCatalogResponse(catalog,index,v) { + if (v.modules) { + v.modules = v.modules.filter(function(m) { + if (RED.utils.checkModuleAllowed(m.id,m.version,installAllowList,installDenyList)) { + loadedIndex[m.id] = m; + m.index = [m.id]; + if (m.keywords) { + m.index = m.index.concat(m.keywords); + } + if (m.types) { + m.index = m.index.concat(m.types); + } + if (m.updated_at) { + m.timestamp = new Date(m.updated_at).getTime(); + } else { + m.timestamp = 0; + } + m.index = m.index.join(",").toLowerCase(); + m.catalog = catalog; + m.catalogIndex = index; + return true; + } + return false; + }) + loadedList = loadedList.concat(v.modules); } } @@ -485,35 +493,19 @@ RED.palette.editor = (function() { packageList.editableList('empty'); $(".red-ui-palette-module-shade-status").text(RED._('palette.editor.loading')); - - catalogueLoadStatus = []; - catalogueLoadErrors = false; - catalogueCount = catalogues.length; - if (catalogues.length > 1) { - $(".red-ui-palette-module-shade-status").html(RED._('palette.editor.loading')+"
0/"+catalogues.length); - } $("#red-ui-palette-module-install-shade").show(); - catalogueLoadStart = Date.now(); - var handled = 0; - 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",url,":",error); - handleCatalogResponse(jqxhr,url,index); - }).always(function() { - handled++; - if (handled === catalogueCount) { - //sort loadedCatalogs by e.index ascending - loadedCatalogs.sort((a, b) => a.index - b.index) - updateCatalogFilter(loadedCatalogs) - } - }) - } + catalogueLoadStart = Date.now() + refreshCatalogues(function () { + refreshNodeModuleList(); + updateCatalogFilter(loadedCatalogs) + const delta = 250-(Date.now() - catalogueLoadStart); + setTimeout(function() { + $("#red-ui-palette-module-install-shade").hide(); + },Math.max(delta,0)); + }) + } else { + refreshNodeModuleList(); + updateCatalogFilter(loadedCatalogs) } } @@ -527,7 +519,6 @@ RED.palette.editor = (function() { 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) @@ -666,12 +657,18 @@ RED.palette.editor = (function() { } }) + // Add the update status to the status bar + addUpdateInfoToStatusBar(); + + refreshCatalogues() + RED.actions.add("core:manage-palette",function() { RED.userSettings.show('palette'); }); RED.events.on('registry:module-updated', function(ns) { refreshNodeModule(ns.module); + refreshUpdateStatus(); }); RED.events.on('registry:node-set-enabled', function(ns) { refreshNodeModule(ns.module); @@ -683,12 +680,14 @@ RED.palette.editor = (function() { if (!/^subflow:/.test(nodeType)) { var ns = RED.nodes.registry.getNodeSetForType(nodeType); refreshNodeModule(ns.module); + refreshUpdateStatus(); } }); RED.events.on('registry:node-type-removed', function(nodeType) { if (!/^subflow:/.test(nodeType)) { var ns = RED.nodes.registry.getNodeSetForType(nodeType); refreshNodeModule(ns.module); + refreshUpdateStatus(); } }); RED.events.on('registry:node-set-added', function(ns) { @@ -757,6 +756,7 @@ RED.palette.editor = (function() { _refreshNodeModule(module); } + refreshUpdateStatus(); for (var i=0;i'); + let updateAvailable = []; + + function addUpdateInfoToStatusBar() { + updateStatusWidget.on("click", function (evt) { + RED.actions.invoke("core:manage-palette", { + view: "nodes", + filter: '"' + updateAvailable.join('", "') + '"' + }); + }); + + RED.popover.tooltip(updateStatusWidget, function () { + const count = updateAvailable.length || 0; + return RED._("palette.editor.updateCount", { count: count }); + }); + + RED.statusBar.add({ + id: "update", + align: "right", + element: updateStatusWidget + }); + + updateStatus({ count: 0 }); + } + + let pendingRefreshTimeout + function refreshUpdateStatus() { + clearTimeout(pendingRefreshTimeout) + pendingRefreshTimeout = setTimeout(() => { + updateAvailable = []; + for (const module of Object.keys(nodeEntries)) { + if (loadedIndex.hasOwnProperty(module)) { + const moduleInfo = nodeEntries[module].info; + if (moduleInfo.pending_version) { + // Module updated + continue; + } + if (updateAllowed && + semVerCompare(loadedIndex[module].version, moduleInfo.version) > 0 && + RED.utils.checkModuleAllowed(module, null, updateAllowList, updateDenyList) + ) { + updateAvailable.push(module); + } + } + } + + updateStatus({ count: updateAvailable.length }); + }, 200) + } + + function updateStatus(opts) { + if (opts.count) { + RED.statusBar.show("update"); + updateStatusWidget.empty(); + $(' ' + opts.count + '').appendTo(updateStatusWidget); + } else { + RED.statusBar.hide("update"); + } + } + return { init: init, install: install diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js index 7fde232c3..89682fb07 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js @@ -33,6 +33,7 @@ RED.statusBar = (function() { var el = $(''); el.prop('id', options.id); options.element.appendTo(el); + options.elementDiv = el; if (options.align === 'left') { leftBucket.append(el); } else if (options.align === 'right') { @@ -40,12 +41,30 @@ RED.statusBar = (function() { } } + function hideWidget(id) { + const widget = widgets[id]; + + if (widget && widget.elementDiv) { + widget.elementDiv.hide(); + } + } + + function showWidget(id) { + const widget = widgets[id]; + + if (widget && widget.elementDiv) { + widget.elementDiv.show(); + } + } + return { init: function() { leftBucket = $('').appendTo("#red-ui-workspace-footer"); rightBucket = $('').appendTo("#red-ui-workspace-footer"); }, - add: addWidget + add: addWidget, + hide: hideWidget, + show: showWidget } })(); 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 947ada2e8..47a91d917 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 @@ -305,3 +305,8 @@ button.red-ui-palette-editor-upload-button { margin-left: 10px; } } + +button.red-ui-update-status { + width: auto; + padding: 0 3px; +}