From 9a07fc03c605c55f4528cddd2b237a9e340534c1 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Mon, 25 Mar 2024 20:47:55 +0100 Subject: [PATCH 1/5] Retain palette collapse and filter to localStorage --- .../@node-red/editor-client/src/js/ui/palette.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 23f30fc61..27f6bb6df 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -73,11 +73,15 @@ RED.palette = (function() { $("#red-ui-palette-header-"+category+" i").addClass("expanded"); }, toggle: function() { + const collapse = JSON.parse(localStorage.getItem("palette-collapse") || "[]"); if (catDiv.hasClass("red-ui-palette-open")) { categoryContainers[category].close(); + collapse.push(category); } else { categoryContainers[category].open(); + collapse.splice(collapse.indexOf(category), 1); } + localStorage.setItem("palette-collapse", JSON.stringify(collapse.filter((c, i, array) => array.indexOf(c) === i))); } }; @@ -602,8 +606,9 @@ RED.palette = (function() { delay: 100, change: function() { filterChange($(this).val()); + localStorage.setItem("palette-filter", $(this).val()); } - }) + }); sidebarControls = $('
').appendTo($("#red-ui-palette")); RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette"); @@ -669,6 +674,12 @@ RED.palette = (function() { togglePalette(state); } }); + + const collapse = JSON.parse(localStorage.getItem("palette-collapse") || "[]"); + setTimeout(function () { + collapse.forEach((category) => categoryContainers[category]?.close()); + $("#red-ui-palette-search input").searchBox("value", (localStorage.getItem("palette-filter") || "")); + }, 1000); } function togglePalette(state) { if (!state) { From 4788b81220b58d3c5fea25280587ad778aca0b94 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Tue, 26 Mar 2024 13:05:38 +0100 Subject: [PATCH 2/5] Replace setTimeout with a listener --- .../@node-red/editor-client/src/js/ui/palette.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 27f6bb6df..10654fdda 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -676,10 +676,12 @@ RED.palette = (function() { }); const collapse = JSON.parse(localStorage.getItem("palette-collapse") || "[]"); - setTimeout(function () { + const callback = function () { collapse.forEach((category) => categoryContainers[category]?.close()); $("#red-ui-palette-search input").searchBox("value", (localStorage.getItem("palette-filter") || "")); - }, 1000); + RED.events.off("runtime-state", callback); + }; + RED.events.on("runtime-state", callback); } function togglePalette(state) { if (!state) { From bffd1d61b22001fcf498be9648615ee055818745 Mon Sep 17 00:00:00 2001 From: GogoVega <92022724+GogoVega@users.noreply.github.com> Date: Tue, 26 Mar 2024 16:58:45 +0100 Subject: [PATCH 3/5] Improve with error handling, storage cleanup and centralization in one object --- .../editor-client/src/js/ui/palette.js | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 10654fdda..6400a746b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -35,6 +35,8 @@ RED.palette = (function() { var categoryContainers = {}; var sidebarControls; + let lastState = { filter: "", collapsed: [] }; + function createCategory(originalCategory,rootCategory,category,ns) { if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) { createCategoryContainer(originalCategory,rootCategory, ns+":palette.label."+rootCategory); @@ -73,15 +75,16 @@ RED.palette = (function() { $("#red-ui-palette-header-"+category+" i").addClass("expanded"); }, toggle: function() { - const collapse = JSON.parse(localStorage.getItem("palette-collapse") || "[]"); if (catDiv.hasClass("red-ui-palette-open")) { categoryContainers[category].close(); - collapse.push(category); + if (!lastState.collapsed.includes(category)) { + lastState.collapsed.push(category); + } } else { categoryContainers[category].open(); - collapse.splice(collapse.indexOf(category), 1); + lastState.collapsed.splice(lastState.collapsed.indexOf(category), 1); } - localStorage.setItem("palette-collapse", JSON.stringify(collapse.filter((c, i, array) => array.indexOf(c) === i))); + savePaletteState(); } }; @@ -600,13 +603,12 @@ RED.palette = (function() { RED.events.on("subflows:change",refreshSubflow); - - $("#red-ui-palette-search input").searchBox({ delay: 100, change: function() { filterChange($(this).val()); - localStorage.setItem("palette-filter", $(this).val()); + lastState.filter = $(this).val(); + savePaletteState(); } }); @@ -675,14 +677,33 @@ RED.palette = (function() { } }); - const collapse = JSON.parse(localStorage.getItem("palette-collapse") || "[]"); - const callback = function () { - collapse.forEach((category) => categoryContainers[category]?.close()); - $("#red-ui-palette-search input").searchBox("value", (localStorage.getItem("palette-filter") || "")); - RED.events.off("runtime-state", callback); - }; - RED.events.on("runtime-state", callback); + try { + const stateFromStorage = JSON.parse(localStorage.getItem("palette-state") || "{}"); + + for (const prop in stateFromStorage) { + if (Object.prototype.hasOwnProperty.call(lastState, prop)) { + lastState[prop] = stateFromStorage[prop]; + } + } + + // Remove old/unknown category from localStorage + lastState.collapsed = lastState.collapsed.filter((c, i, a) => a.indexOf(c) === i && categoryContainers[c]); + + const callback = function () { + // Hide categories previously hidded + lastState.collapsed.forEach((category) => categoryContainers[category].close()); + // Apply the category filter + $("#red-ui-palette-search input").searchBox("value", lastState.filter); + RED.events.off("runtime-state", callback); + }; + + savePaletteState(); + RED.events.on("runtime-state", callback); + } catch (error) { + console.error("Unexpected error loading palette state from localStorage: ", error); + } } + function togglePalette(state) { if (!state) { $("#red-ui-main-container").addClass("red-ui-palette-closed"); @@ -702,6 +723,15 @@ RED.palette = (function() { }) return categories; } + + function savePaletteState() { + try { + localStorage.setItem("palette-state", JSON.stringify(lastState)); + } catch (error) { + console.error("Unexpected error saving palette state to localStorage: ", error); + } + } + return { init: init, add:addNodeType, From f5fd6e3a3645811794f4722bf283244c09dd63d6 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 28 Mar 2024 14:23:39 +0000 Subject: [PATCH 4/5] Rework palette state management --- .../editor-client/src/js/ui/palette.js | 112 +++++++++++------- 1 file changed, 71 insertions(+), 41 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 6400a746b..9191b7400 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -35,7 +35,7 @@ RED.palette = (function() { var categoryContainers = {}; var sidebarControls; - let lastState = { filter: "", collapsed: [] }; + let paletteState = { filter: "", collapsed: [] }; function createCategory(originalCategory,rootCategory,category,ns) { if ($("#red-ui-palette-base-category-"+rootCategory).length === 0) { @@ -62,29 +62,61 @@ RED.palette = (function() { catDiv.data('label',label); categoryContainers[category] = { container: catDiv, - close: function() { + hide: function (instant) { + if (instant) { + catDiv.hide() + } else { + catDiv.slideUp() + } + }, + show: function () { + catDiv.show() + }, + isOpen: function () { + return !!catDiv.hasClass("red-ui-palette-open") + }, + getNodeCount: function (visibleOnly) { + const nodes = catDiv.find(".red-ui-palette-node") + if (visibleOnly) { + return nodes.filter(function() { return $(this).css('display') !== 'none'}).length + } else { + return nodes.length + } + }, + close: function(instant, skipSaveState) { catDiv.removeClass("red-ui-palette-open"); catDiv.addClass("red-ui-palette-closed"); - $("#red-ui-palette-base-category-"+category).slideUp(); + if (instant) { + $("#red-ui-palette-base-category-"+category).hide(); + } else { + $("#red-ui-palette-base-category-"+category).slideUp(); + } $("#red-ui-palette-header-"+category+" i").removeClass("expanded"); + if (!skipSaveState) { + if (!paletteState.collapsed.includes(category)) { + paletteState.collapsed.push(category); + savePaletteState(); + } + } }, - open: function() { + open: function(skipSaveState) { catDiv.addClass("red-ui-palette-open"); catDiv.removeClass("red-ui-palette-closed"); $("#red-ui-palette-base-category-"+category).slideDown(); $("#red-ui-palette-header-"+category+" i").addClass("expanded"); + if (!skipSaveState) { + if (paletteState.collapsed.includes(category)) { + paletteState.collapsed.splice(paletteState.collapsed.indexOf(category), 1); + savePaletteState(); + } + } }, toggle: function() { - if (catDiv.hasClass("red-ui-palette-open")) { + if (categoryContainers[category].isOpen()) { categoryContainers[category].close(); - if (!lastState.collapsed.includes(category)) { - lastState.collapsed.push(category); - } } else { categoryContainers[category].open(); - lastState.collapsed.splice(lastState.collapsed.indexOf(category), 1); } - savePaletteState(); } }; @@ -422,7 +454,11 @@ RED.palette = (function() { var categoryNode = $("#red-ui-palette-container-"+rootCategory); if (categoryNode.find(".red-ui-palette-node").length === 1) { - categoryContainers[rootCategory].open(); + if (!paletteState?.collapsed?.includes(rootCategory)) { + categoryContainers[rootCategory].open(); + } else { + categoryContainers[rootCategory].close(true); + } } } @@ -535,16 +571,26 @@ RED.palette = (function() { } }); - for (var category in categoryContainers) { + for (let category in categoryContainers) { if (categoryContainers.hasOwnProperty(category)) { - if (categoryContainers[category].container - .find(".red-ui-palette-node") - .filter(function() { return $(this).css('display') !== 'none'}).length === 0) { - categoryContainers[category].close(); - categoryContainers[category].container.slideUp(); + const categorySection = categoryContainers[category] + if (categorySection.getNodeCount(true) === 0) { + categorySection.hide() } else { - categoryContainers[category].open(); - categoryContainers[category].container.show(); + categorySection.show() + if (val) { + // There is a filter being applied and it has matched + // something in this category - show the contents + categorySection.open(true) + } else { + // No filter. Only show the category if it isn't in lastState + if (!paletteState.collapsed.includes(category)) { + categorySection.open(true) + } else if (categorySection.isOpen()) { + // This section should be collapsed but isn't - so make it so + categorySection.close(true, true) + } + } } } } @@ -607,7 +653,7 @@ RED.palette = (function() { delay: 100, change: function() { filterChange($(this).val()); - lastState.filter = $(this).val(); + paletteState.filter = $(this).val(); savePaletteState(); } }); @@ -678,27 +724,11 @@ RED.palette = (function() { }); try { - const stateFromStorage = JSON.parse(localStorage.getItem("palette-state") || "{}"); - - for (const prop in stateFromStorage) { - if (Object.prototype.hasOwnProperty.call(lastState, prop)) { - lastState[prop] = stateFromStorage[prop]; - } - } - - // Remove old/unknown category from localStorage - lastState.collapsed = lastState.collapsed.filter((c, i, a) => a.indexOf(c) === i && categoryContainers[c]); - - const callback = function () { - // Hide categories previously hidded - lastState.collapsed.forEach((category) => categoryContainers[category].close()); + paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}'); + if (paletteState.filter) { // Apply the category filter - $("#red-ui-palette-search input").searchBox("value", lastState.filter); - RED.events.off("runtime-state", callback); - }; - - savePaletteState(); - RED.events.on("runtime-state", callback); + $("#red-ui-palette-search input").searchBox("value", paletteState.filter); + } } catch (error) { console.error("Unexpected error loading palette state from localStorage: ", error); } @@ -726,7 +756,7 @@ RED.palette = (function() { function savePaletteState() { try { - localStorage.setItem("palette-state", JSON.stringify(lastState)); + RED.settings.setLocal("palette-state", JSON.stringify(paletteState)); } catch (error) { console.error("Unexpected error saving palette state to localStorage: ", error); } From 3278303eec173388504e485154f908b8da022fcc Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 28 Mar 2024 14:31:31 +0000 Subject: [PATCH 5/5] Clear localStorage state on logout --- .../node_modules/@node-red/editor-client/src/js/ui/palette.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js index 9191b7400..2a6d0bdc6 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/palette.js @@ -606,6 +606,9 @@ RED.palette = (function() { $("#red-ui-palette > .red-ui-palette-spinner").show(); + RED.events.on('logout', function () { + RED.settings.removeLocal('palette-state') + }) RED.events.on('registry:node-type-added', function(nodeType) { var def = RED.nodes.getType(nodeType);