From b0cafda4964daf846f1b42d905143acb0a29c557 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 21 Oct 2025 17:46:59 +0100 Subject: [PATCH 01/21] Reimplement Palette as Sidebar component --- .../@node-red/editor-client/src/js/red.js | 4 +- .../editor-client/src/js/ui/palette.js | 37 ++++++------ .../editor-client/src/js/ui/sidebar.js | 1 + .../editor-client/src/sass/palette.scss | 58 ++++++++++--------- 4 files changed, 51 insertions(+), 49 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 99cb8375b..64cb2483a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -843,6 +843,7 @@ var RED = (function() { RED.user.init(); RED.notifications.init(); RED.library.init(); + RED.sidebar.init(); RED.palette.init(); RED.eventLog.init(); @@ -852,7 +853,6 @@ var RED = (function() { console.log("Palette editor disabled"); } - RED.sidebar.init(); if (RED.settings.theme("projects.enabled",false)) { RED.projects.init(); @@ -897,7 +897,7 @@ var RED = (function() { $('
'+ '
'+ '
'+ - '
'+ + // '
'+ '
'+ '
'+ '
').appendTo(options.target); 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 89337e7c9..63bba053c 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 @@ -33,7 +33,6 @@ RED.palette = (function() { ]; var categoryContainers = {}; - var sidebarControls; let paletteState = { filter: "", collapsed: [] }; @@ -310,6 +309,7 @@ RED.palette = (function() { width: "300px", content: "hi", delay: { show: 750, hide: 50 } + // direction: "left" }); d.data('popover',popover); @@ -332,7 +332,7 @@ RED.palette = (function() { revert: 'invalid', revertDuration: 200, containment:'#red-ui-main-container', - start: function() { + start: function(e, ui) { dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked); paletteWidth = $("#red-ui-palette").width(); paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; @@ -358,7 +358,9 @@ RED.palette = (function() { }, drag: function(e,ui) { var paletteNode = getPaletteNode(nt); - ui.originalPosition.left = paletteNode.offset().left; + console.log(ui.originalPosition.left, paletteNode.offset().left) + // ui.originalPosition.left = paletteNode.offset().left; + // console.log(paletteNode.offset()) if (dropEnabled) { mouseX = ui.position.left - paletteWidth + (ui.helper.width()/2) + chart.scrollLeft(); mouseY = ui.position.top - paletteTop + (ui.helper.height()/2) + chart.scrollTop() + 10; @@ -607,11 +609,22 @@ RED.palette = (function() { function init() { + const content = $('
') + RED.sidebar.addTab({ + id: "palette", + label: "Palette", + name: "Palette", + iconClass: "fa fa-tags", + content: content, + pinned: true, + enableOnEdit: true + }); + $('').appendTo("#red-ui-palette"); $('').appendTo("#red-ui-palette"); $('
').appendTo("#red-ui-palette"); $('').appendTo("#red-ui-palette"); - $('
').appendTo("#red-ui-palette"); + // $('
').appendTo("#red-ui-palette"); $("#red-ui-palette > .red-ui-palette-spinner").show(); @@ -670,19 +683,6 @@ RED.palette = (function() { } }); - sidebarControls = $('
').appendTo($("#red-ui-palette")); - RED.popover.tooltip(sidebarControls,RED._("keyboard.togglePalette"),"core:toggle-palette"); - - sidebarControls.on("click", function() { - RED.menu.toggleSelected("menu-item-palette"); - }) - $("#red-ui-palette").on("mouseenter", function() { - sidebarControls.toggle("slide", { direction: "left" }, 200); - }) - $("#red-ui-palette").on("mouseleave", function() { - sidebarControls.stop(false,true); - sidebarControls.hide(); - }) var userCategories = []; if (RED.settings.paletteCategories) { userCategories = RED.settings.paletteCategories; @@ -754,11 +754,8 @@ RED.palette = (function() { function togglePalette(state) { if (!state) { $("#red-ui-main-container").addClass("red-ui-palette-closed"); - sidebarControls.hide(); - sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); } else { $("#red-ui-main-container").removeClass("red-ui-palette-closed"); - sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); } setTimeout(function() { $(window).trigger("resize"); } ,200); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index eb10fe043..fa817f564 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -223,6 +223,7 @@ RED.sidebar = (function() { } function init () { + console.log('sidebar init') setupSidebarSeparator(); sidebar_tabs = RED.tabs.create({ element: $('').appendTo("#red-ui-sidebar"), 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 507869690..327782674 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 @@ -18,36 +18,35 @@ #red-ui-palette{ - position: absolute; - top: 0px; - bottom: 0px; - left:0px; + // position: absolute; + // top: 0px; + // bottom: 0px; + // left:0px; background: var(--red-ui-primary-background); - width: 180px; + // width: 180px; text-align: center; @include mixins.disable-selection; - @include mixins.component-border; transition: width 0.2s ease-in-out; - &:before { - content: ''; - top: 0px; - bottom: 0px; - right: 0px; - width: 7px; - height: 100%; - z-index: 4; - position: absolute; - -webkit-mask-image: url(images/grip.svg); - mask-image: url(images/grip.svg); - -webkit-mask-size: auto; - mask-size: auto; - -webkit-mask-position: 50% 50%; - mask-position: 50% 50%; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - background-color: var(--red-ui-grip-color); - } + // &:before { + // content: ''; + // top: 0px; + // bottom: 0px; + // right: 0px; + // width: 7px; + // height: 100%; + // z-index: 4; + // position: absolute; + // -webkit-mask-image: url(images/grip.svg); + // mask-image: url(images/grip.svg); + // -webkit-mask-size: auto; + // mask-size: auto; + // -webkit-mask-position: 50% 50%; + // mask-position: 50% 50%; + // -webkit-mask-repeat: no-repeat; + // mask-repeat: no-repeat; + // background-color: var(--red-ui-grip-color); + // } } .red-ui-palette-closed { @@ -91,6 +90,11 @@ .red-ui-palette-content { background: var(--red-ui-palette-content-background); padding: 3px; + > div { + display: flex; + flex-direction: column; + align-items: center; + } } .red-ui-palette-header { @@ -144,7 +148,7 @@ // display: inline-block; cursor: move; background: var(--red-ui-secondary-background); - margin: 10px auto; + margin: 5px 0; height: 25px; border-radius: 5px; border: 1px solid var(--red-ui-node-border); @@ -153,7 +157,7 @@ width: 120px; background-size: contain; position: relative; - z-index: 4; + z-index: 20; &:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child { margin-top: 15px; } From 4bcec3741dfc5b5b618644a407d186444124fea0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 21 Oct 2025 18:54:33 +0100 Subject: [PATCH 02/21] Move palette footer to sidebar toolbar --- .../@node-red/editor-client/src/js/ui/palette.js | 7 ++++--- .../@node-red/editor-client/src/sass/palette.scss | 2 +- 2 files changed, 5 insertions(+), 4 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 63bba053c..de66eb224 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 @@ -610,12 +610,14 @@ RED.palette = (function() { function init() { const content = $('
') + const toolbar = $('
'); RED.sidebar.addTab({ id: "palette", label: "Palette", name: "Palette", iconClass: "fa fa-tags", - content: content, + content, + toolbar, pinned: true, enableOnEdit: true }); @@ -623,7 +625,6 @@ RED.palette = (function() { $('').appendTo("#red-ui-palette"); $('').appendTo("#red-ui-palette"); $('
').appendTo("#red-ui-palette"); - $('').appendTo("#red-ui-palette"); // $('
').appendTo("#red-ui-palette"); $("#red-ui-palette > .red-ui-palette-spinner").show(); @@ -704,7 +705,7 @@ RED.palette = (function() { } }); - var paletteFooterButtons = $('').appendTo("#red-ui-palette .red-ui-component-footer"); + var paletteFooterButtons = $('').appendTo(toolbar); var paletteCollapseAll = $('').appendTo(paletteFooterButtons); paletteCollapseAll.on("click", function(e) { e.preventDefault(); 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 327782674..236bdf570 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 @@ -64,7 +64,7 @@ position: absolute; top: 35px; right: 0; - bottom: 25px; + bottom: 0; left:0; padding: 0; overflow-y: auto; From 6e264084e747fb8c3df1b308c917e55c6009e5b0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 22 Oct 2025 11:39:13 +0100 Subject: [PATCH 03/21] Fix z ordering of palette nodes --- .../node_modules/@node-red/editor-client/src/js/ui/palette.js | 1 + .../node_modules/@node-red/editor-client/src/sass/palette.scss | 2 +- 2 files changed, 2 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 de66eb224..df4214900 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 @@ -333,6 +333,7 @@ RED.palette = (function() { revertDuration: 200, containment:'#red-ui-main-container', start: function(e, ui) { + ui.helper.css('z-index', 1000); dropEnabled = !(RED.nodes.workspace(RED.workspaces.active())?.locked); paletteWidth = $("#red-ui-palette").width(); paletteTop = $("#red-ui-palette").parent().position().top + $("#red-ui-palette-container").position().top; 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 236bdf570..d451d44a8 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 @@ -157,7 +157,7 @@ width: 120px; background-size: contain; position: relative; - z-index: 20; + z-index: 5; &:not(.red-ui-palette-node-config):not(.red-ui-palette-node-small):first-child { margin-top: 15px; } From c90f93ec5618c53f6baa44b589db4a110ea84fec Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 23 Oct 2025 17:24:15 +0100 Subject: [PATCH 04/21] Fix sidebar apis for dual sidebars --- .../@node-red/editor-client/src/js/red.js | 7 +- .../editor-client/src/js/ui/common/tabs.js | 4 +- .../editor-client/src/js/ui/palette.js | 18 +- .../editor-client/src/js/ui/sidebar.js | 244 +++++++++++------- .../editor-client/src/js/ui/tab-info.js | 1 + .../editor-client/src/sass/base.scss | 2 + .../editor-client/src/sass/palette.scss | 11 - .../editor-client/src/sass/sidebar.scss | 50 ++-- .../editor-client/src/sass/workspace.scss | 23 +- 9 files changed, 189 insertions(+), 171 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 64cb2483a..1c073b137 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -894,13 +894,14 @@ var RED = (function() { var logo = $('').appendTo(header); $('
    ').appendTo(header); $('
    ').appendTo(header); - $('
    '+ + $('
    '+ + '
    '+ '
    '+ + '
    '+ '
    '+ // '
    '+ - '
    '+ - '
    '+ '
    ').appendTo(options.target); + $('
    ').appendTo(options.target); $('
    ').appendTo(options.target); $('
    ').appendTo(options.target); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js index d9dc4b289..a9fef7cee 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/tabs.js @@ -545,7 +545,6 @@ RED.tabs = (function() { ul.find("li.red-ui-tab.active .red-ui-tab-label").css({paddingLeft:""}) } } - } ul.find("li.red-ui-tab a") @@ -1045,7 +1044,8 @@ RED.tabs = (function() { pinnedButtons["__menu__"].appendTo(collapsedButtonsRow); updateTabWidths(); } - } + }, + container: wrapper } return tabAPI; } 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 df4214900..1cd2d0b41 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 @@ -613,6 +613,7 @@ RED.palette = (function() { const content = $('
    ') const toolbar = $('
    '); RED.sidebar.addTab({ + target: 'secondary', id: "palette", label: "Palette", name: "Palette", @@ -729,13 +730,7 @@ RED.palette = (function() { }); RED.popover.tooltip(paletteExpandAll,RED._('palette.actions.expand-all')); - RED.actions.add("core:toggle-palette", function(state) { - if (state === undefined) { - RED.menu.toggleSelected("menu-item-palette"); - } else { - togglePalette(state); - } - }); + try { paletteState = JSON.parse(RED.settings.getLocal("palette-state") || '{"filter":"", "collapsed": []}'); @@ -753,15 +748,6 @@ RED.palette = (function() { }, 10000) } - function togglePalette(state) { - if (!state) { - $("#red-ui-main-container").addClass("red-ui-palette-closed"); - } else { - $("#red-ui-main-container").removeClass("red-ui-palette-closed"); - } - setTimeout(function() { $(window).trigger("resize"); } ,200); - } - function getCategories() { var categories = []; $("#red-ui-palette-container .red-ui-palette-category").each(function(i,d) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index fa817f564..f4ec5be40 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -15,13 +15,29 @@ **/ RED.sidebar = (function() { - //$('#sidebar').tabs(); - var sidebar_tabs; + const primarySidebar = { + id: 'primary', + direction: 'right', + menuToggle: 'menu-item-sidebar', + minimumWidth: 180, + defaultWidth: 300 + } + + // Palette Sidebar + const secondarySidebar = { + id: 'secondary', + direction: 'left', + menuToggle: 'menu-item-palette', + minimumWidth: 180, + defaultWidth: 180 + } + + var knownTabs = {}; // We store the current sidebar tab id in localStorage as 'last-sidebar-tab' // This is restored when the editor is reloaded. - // We use sidebar_tabs.onchange to update localStorage. However that will + // We use primarySidebar.tabs.onchange to update localStorage. However that will // also get triggered when the first tab gets added to the tabs - typically // the 'info' tab. So we use the following variable to store the retrieved // value from localStorage before we start adding the actual tabs @@ -44,9 +60,10 @@ RED.sidebar = (function() { options = title; } + const targetSidebar = options.target === 'secondary' ? secondarySidebar : primarySidebar; delete options.closeable; - options.wrapper = $('
    ',{style:"height:100%"}).appendTo("#red-ui-sidebar-content") + options.wrapper = $('
    ',{style:"height:100%"}).appendTo(targetSidebar.content) options.wrapper.append(options.content); options.wrapper.hide(); @@ -55,7 +72,7 @@ RED.sidebar = (function() { } if (options.toolbar) { - $("#red-ui-sidebar-footer").append(options.toolbar); + targetSidebar.footer.append(options.toolbar); $(options.toolbar).hide(); } var id = options.id; @@ -74,131 +91,151 @@ RED.sidebar = (function() { knownTabs[options.id] = options; if (options.visible !== false) { - sidebar_tabs.addTab(knownTabs[options.id]); + targetSidebar.tabs.addTab(knownTabs[options.id]); + } + if (targetSidebar.tabs.count() > 1) { + targetSidebar.tabs.container.show() } } function removeTab(id) { - sidebar_tabs.removeTab(id); - $(knownTabs[id].wrapper).remove(); - if (knownTabs[id].footer) { - knownTabs[id].footer.remove(); + if (knownTabs[id]) { + const targetSidebar = knownTabs[id].target === 'secondary' ? secondarySidebar : primarySidebar; + targetSidebar.tabs.removeTab(id); + $(knownTabs[id].wrapper).remove(); + if (knownTabs[id].footer) { + knownTabs[id].footer.remove(); + } + delete knownTabs[id]; + RED.menu.removeItem("menu-item-view-menu-"+id); + if (targetSidebar.tabs.count() <= 1) { + targetSidebar.tabs.container.hide() + } } - delete knownTabs[id]; - RED.menu.removeItem("menu-item-view-menu-"+id); } var sidebarSeparator = {}; sidebarSeparator.dragging = false; - function setupSidebarSeparator() { - $("#red-ui-sidebar-separator").draggable({ + function setupSidebarSeparator(sidebar) { + const separator = $('
    '); + let scaleFactor = 1; + let controlClass = 'red-ui-sidebar-control-right'; + let controlSlideDirection = 'right'; + if (sidebar.direction === 'right') { + separator.insertBefore(sidebar.container); + } else if (sidebar.direction === 'left') { + scaleFactor = -1; + controlClass = 'red-ui-sidebar-control-left'; + controlSlideDirection = 'left'; + separator.insertAfter(sidebar.container); + } + separator.draggable({ axis: "x", start:function(event,ui) { sidebarSeparator.closing = false; sidebarSeparator.opening = false; - var winWidth = $("#red-ui-editor").width(); + // var winWidth = $("#red-ui-editor").width(); sidebarSeparator.start = ui.position.left; + sidebarSeparator.width = sidebar.container.width(); sidebarSeparator.chartWidth = $("#red-ui-workspace").width(); - sidebarSeparator.chartRight = winWidth-$("#red-ui-workspace").width()-$("#red-ui-workspace").offset().left-2; sidebarSeparator.dragging = true; - if (!RED.menu.isSelected("menu-item-sidebar")) { + if (!RED.menu.isSelected(sidebar.menuToggle)) { sidebarSeparator.opening = true; - var newChartRight = 7; - $("#red-ui-sidebar").addClass("closing"); - $("#red-ui-workspace").css("right",newChartRight); - $("#red-ui-editor-stack").css("right",newChartRight+1); - $("#red-ui-sidebar").width(0); - RED.menu.setSelected("menu-item-sidebar",true); + sidebar.container.width(0); + RED.menu.setSelected(sidebar.menuToggle,true); RED.events.emit("sidebar:resize"); } - sidebarSeparator.width = $("#red-ui-sidebar").width(); + sidebarSeparator.width = sidebar.container.width(); }, drag: function(event,ui) { - var d = ui.position.left-sidebarSeparator.start; - var newSidebarWidth = sidebarSeparator.width-d; + var d = scaleFactor * (ui.position.left-sidebarSeparator.start); + + var newSidebarWidth = sidebarSeparator.width - d; if (sidebarSeparator.opening) { - newSidebarWidth -= 3; + newSidebarWidth -= 3 * scaleFactor; } if (newSidebarWidth > 150) { - if (sidebarSeparator.chartWidth+d < 200) { + if (sidebarSeparator.chartWidth + d < 200) { ui.position.left = 200+sidebarSeparator.start-sidebarSeparator.chartWidth; d = ui.position.left-sidebarSeparator.start; newSidebarWidth = sidebarSeparator.width-d; } } - if (newSidebarWidth < 150) { - if (!sidebarSeparator.closing) { - $("#red-ui-sidebar").addClass("closing"); - sidebarSeparator.closing = true; + if (newSidebarWidth < sidebar.minimumWidth) { + if (newSidebarWidth > 100) { + newSidebarWidth = sidebar.minimumWidth + sidebarSeparator.closing = false + } else { + newSidebarWidth = 0 + sidebarSeparator.closing = true } - if (!sidebarSeparator.opening) { - newSidebarWidth = 150; - ui.position.left = sidebarSeparator.width-(150 - sidebarSeparator.start); - d = ui.position.left-sidebarSeparator.start; - } - } else if (newSidebarWidth > 150 && (sidebarSeparator.closing || sidebarSeparator.opening)) { - sidebarSeparator.closing = false; - $("#red-ui-sidebar").removeClass("closing"); + } else { + sidebarSeparator.closing = false } + sidebar.container.width(newSidebarWidth); + ui.position.left -= scaleFactor * d - var newChartRight = sidebarSeparator.chartRight-d; - $("#red-ui-workspace").css("right",newChartRight); - $("#red-ui-editor-stack").css("right",newChartRight+1); - $("#red-ui-sidebar").width(newSidebarWidth); - - sidebar_tabs.resize(); + sidebar.tabs.resize(); RED.events.emit("sidebar:resize"); }, stop:function(event,ui) { sidebarSeparator.dragging = false; if (sidebarSeparator.closing) { - $("#red-ui-sidebar").removeClass("closing"); - RED.menu.setSelected("menu-item-sidebar",false); - if ($("#red-ui-sidebar").width() < 180) { - $("#red-ui-sidebar").width(180); - $("#red-ui-workspace").css("right",187); - $("#red-ui-editor-stack").css("right",188); + sidebar.container.removeClass("closing"); + if (sidebar.menuToggle) { + RED.menu.setSelected(sidebar.menuToggle,false); + } + sidebar.container.hide() + if (sidebar.container.width() < sidebar.minimumWidth) { + sidebar.container.width(sidebar.defaultWidth); } } - $("#red-ui-sidebar-separator").css("left","auto"); - $("#red-ui-sidebar-separator").css("right",($("#red-ui-sidebar").width()+2)+"px"); + // $(".red-ui-sidebar-separator").css("left","auto"); + // $(".red-ui-sidebar-separator").css("right",(sidebar.container.width()+2)+"px"); RED.events.emit("sidebar:resize"); } }); - var sidebarControls = $('
    ').appendTo($("#red-ui-sidebar-separator")); + var sidebarControls = $('
    ').appendTo(separator); + sidebarControls.addClass(controlClass) sidebarControls.on("click", function() { sidebarControls.hide(); - RED.menu.toggleSelected("menu-item-sidebar"); + RED.menu.toggleSelected(sidebar.menuToggle); }) - $("#red-ui-sidebar-separator").on("mouseenter", function() { + separator.on("mouseenter", function() { if (!sidebarSeparator.dragging) { - if (RED.menu.isSelected("menu-item-sidebar")) { + if ( (RED.menu.isSelected(sidebar.menuToggle) && sidebar.direction === 'right') || + (!RED.menu.isSelected(sidebar.menuToggle) && sidebar.direction === 'left') ) { sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); } else { sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); } - sidebarControls.toggle("slide", { direction: "right" }, 200); + sidebarControls.toggle("slide", { direction: controlSlideDirection }, 200); } }) - $("#red-ui-sidebar-separator").on("mouseleave", function() { + separator.on("mouseleave", function() { if (!sidebarSeparator.dragging) { sidebarControls.stop(false,true); sidebarControls.hide(); } }); + return separator } - function toggleSidebar(state) { + function toggleSidebar(sidebar, state) { if (!state) { - $("#red-ui-main-container").addClass("red-ui-sidebar-closed"); + sidebar.container.hide() } else { - $("#red-ui-main-container").removeClass("red-ui-sidebar-closed"); - sidebar_tabs.resize(); + // console.log(sidebar.container.width(),sidebar.tabs.container.width()) + sidebar.container.show() + sidebar.tabs.container.width() + setTimeout(function () { + sidebar.tabs.resize() + }, 100) } RED.events.emit("sidebar:resize"); } @@ -208,36 +245,43 @@ RED.sidebar = (function() { id = lastSessionSelectedTab || RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0] } if (id) { - if (!containsTab(id) && knownTabs[id]) { - sidebar_tabs.addTab(knownTabs[id]); - } - sidebar_tabs.activateTab(id); - if (!skipShowSidebar && !RED.menu.isSelected("menu-item-sidebar")) { - RED.menu.setSelected("menu-item-sidebar",true); + const tabOptions = knownTabs[id]; + if (tabOptions) { + const targetSidebar = tabOptions.target === 'secondary' ? secondarySidebar : primarySidebar; + if (!targetSidebar.tabs.contains(id)) { + targetSidebar.tabs.addTab(knownTabs[id]); + } + targetSidebar.tabs.activateTab(id); + if (!skipShowSidebar && !RED.menu.isSelected(targetSidebar.menuToggle)) { + RED.menu.setSelected(targetSidebar.menuToggle,true); + } } } } function containsTab(id) { - return sidebar_tabs.contains(id); + return primarySidebar.tabs.contains(id); } - function init () { - console.log('sidebar init') - setupSidebarSeparator(); - sidebar_tabs = RED.tabs.create({ - element: $('
      ').appendTo("#red-ui-sidebar"), + function setupSidebar(sidebar) { + sidebar.container.addClass("red-ui-sidebar"); + sidebar.container.width(sidebar.defaultWidth); + sidebar.separator = setupSidebarSeparator(sidebar); + sidebar.tabs = RED.tabs.create({ + element: $('
        ').appendTo(sidebar.container), onchange:function(tab) { - $("#red-ui-sidebar-content").children().hide(); - $("#red-ui-sidebar-footer").children().hide(); - if (tab.onchange) { - tab.onchange.call(tab); + sidebar.content.children().hide(); + sidebar.footer.children().hide(); + if (tab) { + if (tab.onchange) { + tab.onchange.call(tab); + } + $(tab.wrapper).show(); + if (tab.toolbar) { + $(tab.toolbar).show(); + } + RED.settings.setLocal("last-sidebar-tab", tab.id) } - $(tab.wrapper).show(); - if (tab.toolbar) { - $(tab.toolbar).show(); - } - RED.settings.setLocal("last-sidebar-tab", tab.id) }, onremove: function(tab) { $(tab.wrapper).hide(); @@ -253,19 +297,35 @@ RED.sidebar = (function() { order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"]) // scrollable: true }); + sidebar.tabs.container.hide() + sidebar.content = $('
        ').appendTo(sidebar.container); + sidebar.footer = $('').appendTo(sidebar.container); + sidebar.shade = $('
        ').appendTo(sidebar.container); - $('
        ').appendTo("#red-ui-sidebar"); - $('').appendTo("#red-ui-sidebar"); - $('
        ').appendTo("#red-ui-sidebar"); + } + function init () { + primarySidebar.container = $("#red-ui-sidebar"); + setupSidebar(primarySidebar) + secondarySidebar.container = $("#red-ui-sidebar-left"); + setupSidebar(secondarySidebar) RED.actions.add("core:toggle-sidebar",function(state){ if (state === undefined) { - RED.menu.toggleSelected("menu-item-sidebar"); + RED.menu.toggleSelected(primarySidebar.menuToggle); } else { - toggleSidebar(state); + toggleSidebar(primarySidebar, state); } }); - RED.popover.tooltip($("#red-ui-sidebar-separator").find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar"); + RED.actions.add("core:toggle-palette", function(state) { + if (state === undefined) { + RED.menu.toggleSelected(secondarySidebar.menuToggle); + } else { + toggleSidebar(secondarySidebar, state); + } + }); + + + RED.popover.tooltip(primarySidebar.separator.find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar"); lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab") @@ -273,7 +333,7 @@ RED.sidebar = (function() { RED.sidebar.help.init(); RED.sidebar.config.init(); RED.sidebar.context.init(); - // hide info bar at start if screen rather narrow... + // hide sidebar at start if screen rather narrow... if ($("#red-ui-editor").width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); } } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index fa9b98322..b6fdb8f33 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -119,6 +119,7 @@ RED.sidebar.info = (function() { RED.sidebar.addTab({ id: "info", + // target: "secondary", label: RED._("sidebar.info.label"), name: RED._("sidebar.info.name"), iconClass: "fa fa-info", diff --git a/packages/node_modules/@node-red/editor-client/src/sass/base.scss b/packages/node_modules/@node-red/editor-client/src/sass/base.scss index afbafe049..b6bded65d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/base.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/base.scss @@ -42,6 +42,8 @@ body { position: absolute; top: var(--red-ui-header-height); left:0; bottom: 0; right:0; overflow:hidden; + display: flex; + flex-direction: row; } #red-ui-palette-shade, #red-ui-editor-shade, #red-ui-header-shade, #red-ui-sidebar-shade { 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 d451d44a8..39b17eab2 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 @@ -49,17 +49,6 @@ // } } -.red-ui-palette-closed { - #red-ui-palette { - width: 8px; - .red-ui-component-footer { - display: none; - } - } - #red-ui-palette-search { display: none; } - #red-ui-palette-container { display: none; } -} - .red-ui-palette-scroll { position: absolute; top: 35px; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 47c8dbc13..acd983aad 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -16,38 +16,42 @@ * limitations under the License. **/ -#red-ui-sidebar { - position: absolute; - top: 0px; - right: 0px; - bottom: 0px; +.red-ui-sidebar { + position: relative; + flex-grow: 0; + flex-shrink: 0; width: 315px; background: var(--red-ui-primary-background); box-sizing: border-box; z-index: 10; @include mixins.component-border; + display: flex; + flex-direction: column; } #red-ui-sidebar.closing { border-style: dashed; } - -#red-ui-sidebar-content { - position: absolute; +.red-ui-sidebar > .red-ui-tabs { + flex-grow: 0; + flex-shrink: 0; +} +.red-ui-sidebar-footer { + @include mixins.component-footer; + position: relative; + flex-grow: 0; + flex-shrink: 0; +} +.red-ui-sidebar-content { + position: relative; background: var(--red-ui-secondary-background); - top: 35px; - right: 0; - bottom: 25px; - left: 0px; + flex-grow: 1; overflow-y: auto; } -#red-ui-sidebar-separator { - position: absolute; - top: 5px; - right: 315px; - bottom:10px; - width: 7px; +.red-ui-sidebar-separator { + width: 10px; + flex: 0 0 auto; // z-index: 11; background-color: var(--red-ui-primary-background); cursor: col-resize; @@ -69,12 +73,7 @@ } } -.red-ui-sidebar-closed > #red-ui-sidebar { display: none; } -.red-ui-sidebar-closed > #red-ui-sidebar-separator { right: 0px !important; } -.red-ui-sidebar-closed > #red-ui-workspace { right: 7px !important; } -.red-ui-sidebar-closed > #red-ui-editor-stack { right: 8px !important; } - -#red-ui-sidebar .button { +.red-ui-sidebar .button { @include mixins.workspace-button; line-height: 18px; font-size: 12px; @@ -138,7 +137,7 @@ button.red-ui-sidebar-header-button-toggle { display: none; position: absolute; top: calc(50% - 26px); - + z-index: 13; padding:15px 8px; border:1px solid var(--red-ui-primary-border-color); background:var(--red-ui-primary-background); @@ -152,7 +151,6 @@ button.red-ui-sidebar-header-button-toggle { right: calc(100%); border-top-left-radius: 5px; border-bottom-left-radius: 5px; - z-index: 13; } .red-ui-sidebar-control-left { @include red-ui-sidebar-control; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index 95b8b4b45..d95cbf295 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -17,15 +17,12 @@ **/ #red-ui-workspace { - position: absolute; margin: 0; - top:0px; - left:179px; - bottom: 0px; - right: 322px; overflow: hidden; @include mixins.component-border; transition: left 0.1s ease-in-out; + position: relative; + flex-grow: 1; } #red-ui-workspace-chart { @@ -66,22 +63,6 @@ } } -.red-ui-palette-closed #red-ui-workspace { - left: 7px; -} - -// .workspace-footer-button { -// @include component-footer-button; -// margin-left: 2px; -// margin-right: 2px; -// } -// -// .workspace-footer-button-toggle { -// @include component-footer-button-toggle; -// margin-left: 2px; -// margin-right: 2px; -// } - #red-ui-workspace-tabs:not(.red-ui-workspace-focussed) { opacity:0.8; } From 2e777db80c87fbb48e83ab49568116c2cb5a551b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 24 Oct 2025 11:40:56 +0100 Subject: [PATCH 05/21] Handle resizing and shade on separators --- .../editor-client/src/js/ui/actionList.js | 8 +- .../editor-client/src/js/ui/deploy.js | 4 +- .../@node-red/editor-client/src/js/ui/diff.js | 4 +- .../src/js/ui/projects/projectSettings.js | 4 +- .../editor-client/src/js/ui/search.js | 8 +- .../editor-client/src/js/ui/sidebar.js | 138 +++++++++--------- .../@node-red/editor-client/src/js/ui/tray.js | 2 + .../editor-client/src/js/ui/userSettings.js | 4 +- .../@node-red/editor-client/src/js/ui/view.js | 4 +- .../editor-client/src/sass/base.scss | 7 +- .../editor-client/src/sass/sidebar.scss | 6 +- 11 files changed, 94 insertions(+), 95 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js b/packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js index d47a20f5d..4ec773d39 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/actionList.js @@ -154,8 +154,7 @@ RED.actionList = (function() { $("#red-ui-header-shade").show(); $("#red-ui-editor-shade").show(); $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); - $("#red-ui-sidebar-separator").hide(); + $(".red-ui-sidebar-shade").show(); if (dialog === null) { createDialog(); } @@ -189,8 +188,7 @@ RED.actionList = (function() { $("#red-ui-header-shade").hide(); $("#red-ui-editor-shade").hide(); $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); - $("#red-ui-sidebar-separator").show(); + $(".red-ui-sidebar-shade").hide(); if (dialog !== null) { dialog.slideUp(200,function() { searchInput.searchBox('value',''); @@ -222,7 +220,7 @@ RED.actionList = (function() { $("#red-ui-header-shade").on('mousedown',hide); $("#red-ui-editor-shade").on('mousedown',hide); $("#red-ui-palette-shade").on('mousedown',hide); - $("#red-ui-sidebar-shade").on('mousedown',hide); + $(".red-ui-sidebar-shade").on('mousedown',hide); } return { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js index d318f476c..456c0f249 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js @@ -313,13 +313,13 @@ RED.deploy = (function() { $("#red-ui-header-shade").show(); $("#red-ui-editor-shade").show(); $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); + $(".red-ui-sidebar-shade").show(); } function shadeHide() { $("#red-ui-header-shade").hide(); $("#red-ui-editor-shade").hide(); $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); + $(".red-ui-sidebar-shade").hide(); } function deployButtonSetBusy(){ $(".red-ui-deploy-button-content").css('opacity',0); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js index 26cdceb7e..186a56fcc 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/diff.js @@ -1363,11 +1363,11 @@ RED.diff = (function() { diffTable.finish(); diffTable.list.show(); },300); - $("#red-ui-sidebar-shade").show(); + $(".red-ui-sidebar-shade").show(); }, close: function() { diffVisible = false; - $("#red-ui-sidebar-shade").hide(); + $(".red-ui-sidebar-shade").hide(); }, show: function() { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js index b682a5f60..0c47c4510 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/projects/projectSettings.js @@ -95,7 +95,7 @@ RED.projects.settings = (function() { }); settingsContent.i18n(); settingsTabs.activateTab("red-ui-project-settings-tab-"+(initialTab||'main')) - $("#red-ui-sidebar-shade").show(); + $(".red-ui-sidebar-shade").show(); }, close: function() { settingsVisible = false; @@ -104,7 +104,7 @@ RED.projects.settings = (function() { pane.close(); } }); - $("#red-ui-sidebar-shade").hide(); + $(".red-ui-sidebar-shade").hide(); }, show: function() {} diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js index 3903a4a0a..d07023484 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/search.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/search.js @@ -518,8 +518,7 @@ RED.search = (function() { $("#red-ui-header-shade").show(); $("#red-ui-editor-shade").show(); $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); - $("#red-ui-sidebar-separator").hide(); + $(".red-ui-sidebar-shade").show(); if (dialog === null) { createDialog(); @@ -543,8 +542,7 @@ RED.search = (function() { $("#red-ui-header-shade").hide(); $("#red-ui-editor-shade").hide(); $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); - $("#red-ui-sidebar-separator").show(); + $(".red-ui-sidebar-shade").hide(); if (dialog !== null) { dialog.slideUp(200,function() { searchInput.searchBox('value',''); @@ -644,7 +642,7 @@ RED.search = (function() { $("#red-ui-header-shade").on('mousedown',hide); $("#red-ui-editor-shade").on('mousedown',hide); $("#red-ui-palette-shade").on('mousedown',hide); - $("#red-ui-sidebar-shade").on('mousedown',hide); + $(".red-ui-sidebar-shade").on('mousedown',hide); $("#red-ui-view-searchtools-close").on("click", function close() { clearActiveSearch(); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index f4ec5be40..be6f8863a 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -20,6 +20,7 @@ RED.sidebar = (function() { direction: 'right', menuToggle: 'menu-item-sidebar', minimumWidth: 180, + maximumWidth: 800, defaultWidth: 300 } @@ -29,10 +30,10 @@ RED.sidebar = (function() { direction: 'left', menuToggle: 'menu-item-palette', minimumWidth: 180, + maximumWidth: 180, defaultWidth: 180 } - var knownTabs = {}; // We store the current sidebar tab id in localStorage as 'last-sidebar-tab' @@ -119,6 +120,7 @@ RED.sidebar = (function() { function setupSidebarSeparator(sidebar) { const separator = $('
        '); + const shade = $('
        ').appendTo(separator); let scaleFactor = 1; let controlClass = 'red-ui-sidebar-control-right'; let controlSlideDirection = 'right'; @@ -131,73 +133,75 @@ RED.sidebar = (function() { separator.insertAfter(sidebar.container); } separator.draggable({ - axis: "x", - start:function(event,ui) { - sidebarSeparator.closing = false; - sidebarSeparator.opening = false; - // var winWidth = $("#red-ui-editor").width(); - sidebarSeparator.start = ui.position.left; - sidebarSeparator.width = sidebar.container.width(); - sidebarSeparator.chartWidth = $("#red-ui-workspace").width(); - sidebarSeparator.dragging = true; + axis: "x", + start:function(event,ui) { + if (shade.is(":visible")) { + return false + } + sidebarSeparator.closing = false; + sidebarSeparator.opening = false; + // var winWidth = $("#red-ui-editor").width(); + sidebarSeparator.start = ui.position.left; + sidebarSeparator.width = sidebar.container.width(); + sidebarSeparator.chartWidth = $("#red-ui-workspace").width(); + sidebarSeparator.dragging = true; - if (!RED.menu.isSelected(sidebar.menuToggle)) { - sidebarSeparator.opening = true; - sidebar.container.width(0); - RED.menu.setSelected(sidebar.menuToggle,true); - RED.events.emit("sidebar:resize"); - } - sidebarSeparator.width = sidebar.container.width(); - }, - drag: function(event,ui) { - var d = scaleFactor * (ui.position.left-sidebarSeparator.start); - - var newSidebarWidth = sidebarSeparator.width - d; - if (sidebarSeparator.opening) { - newSidebarWidth -= 3 * scaleFactor; - } - - if (newSidebarWidth > 150) { - if (sidebarSeparator.chartWidth + d < 200) { - ui.position.left = 200+sidebarSeparator.start-sidebarSeparator.chartWidth; - d = ui.position.left-sidebarSeparator.start; - newSidebarWidth = sidebarSeparator.width-d; - } - } - - if (newSidebarWidth < sidebar.minimumWidth) { - if (newSidebarWidth > 100) { - newSidebarWidth = sidebar.minimumWidth - sidebarSeparator.closing = false - } else { - newSidebarWidth = 0 - sidebarSeparator.closing = true - } - } else { - sidebarSeparator.closing = false - } - sidebar.container.width(newSidebarWidth); - ui.position.left -= scaleFactor * d - - sidebar.tabs.resize(); - RED.events.emit("sidebar:resize"); - }, - stop:function(event,ui) { - sidebarSeparator.dragging = false; - if (sidebarSeparator.closing) { - sidebar.container.removeClass("closing"); - if (sidebar.menuToggle) { - RED.menu.setSelected(sidebar.menuToggle,false); - } - sidebar.container.hide() - if (sidebar.container.width() < sidebar.minimumWidth) { - sidebar.container.width(sidebar.defaultWidth); - } - } - // $(".red-ui-sidebar-separator").css("left","auto"); - // $(".red-ui-sidebar-separator").css("right",(sidebar.container.width()+2)+"px"); + if (!RED.menu.isSelected(sidebar.menuToggle)) { + sidebarSeparator.opening = true; + sidebar.container.width(0); + RED.menu.setSelected(sidebar.menuToggle,true); RED.events.emit("sidebar:resize"); } + sidebarSeparator.width = sidebar.container.width(); + }, + drag: function(event,ui) { + var d = scaleFactor * (ui.position.left-sidebarSeparator.start); + + var newSidebarWidth = sidebarSeparator.width - d; + if (newSidebarWidth > sidebar.maximumWidth) { + newSidebarWidth = sidebar.maximumWidth; + d = sidebarSeparator.width - sidebar.maximumWidth; + ui.position.left = sidebarSeparator.start + scaleFactor * d; + } + + if (newSidebarWidth > sidebar.minimumWidth) { + if (sidebarSeparator.chartWidth + d < 200) { + // Chart is now too small, but we have room to resize the sidebar + d += (200 - (sidebarSeparator.chartWidth + d)); + newSidebarWidth = sidebarSeparator.width - d; + ui.position.left = sidebarSeparator.start + scaleFactor * d; + } + } else if (newSidebarWidth < sidebar.minimumWidth) { + if (newSidebarWidth > 100) { + newSidebarWidth = sidebar.minimumWidth + sidebarSeparator.closing = false + } else { + newSidebarWidth = 0 + sidebarSeparator.closing = true + } + } else { + sidebarSeparator.closing = false + } + sidebar.container.width(newSidebarWidth); + ui.position.left -= scaleFactor * d + + sidebar.tabs.resize(); + RED.events.emit("sidebar:resize"); + }, + stop:function(event,ui) { + sidebarSeparator.dragging = false; + if (sidebarSeparator.closing) { + sidebar.container.removeClass("closing"); + if (sidebar.menuToggle) { + RED.menu.setSelected(sidebar.menuToggle,false); + } + sidebar.container.hide() + if (sidebar.container.width() < sidebar.minimumWidth) { + sidebar.container.width(sidebar.defaultWidth); + } + } + RED.events.emit("sidebar:resize"); + } }); var sidebarControls = $('
        ').appendTo(separator); @@ -207,7 +211,7 @@ RED.sidebar = (function() { RED.menu.toggleSelected(sidebar.menuToggle); }) separator.on("mouseenter", function() { - if (!sidebarSeparator.dragging) { + if (!sidebarSeparator.dragging && !shade.is(":visible")) { if ( (RED.menu.isSelected(sidebar.menuToggle) && sidebar.direction === 'right') || (!RED.menu.isSelected(sidebar.menuToggle) && sidebar.direction === 'left') ) { sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); @@ -218,7 +222,7 @@ RED.sidebar = (function() { } }) separator.on("mouseleave", function() { - if (!sidebarSeparator.dragging) { + if (!sidebarSeparator.dragging && !shade.is(":visible")) { sidebarControls.stop(false,true); sidebarControls.hide(); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js index b0f188457..faeac3a22 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js @@ -194,6 +194,8 @@ } function handleWindowResize() { + // TODO: red-ui-sidebar has a width when hidden - so we need to handle that case and set the right pos to its separator + $("#red-ui-editor-stack").css('right', $("#red-ui-sidebar").outerWidth() + 11); if (stack.length > 0) { var tray = stack[stack.length-1]; if (tray.options.maximized || tray.width > $("#red-ui-editor-stack").position().left-8) { diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js index 3bc99e602..a1a1a5661 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js @@ -81,7 +81,7 @@ RED.userSettings = (function() { }); settingsContent.i18n(); settingsTabs.activateTab("red-ui-settings-tab-"+(initialTab||'view')) - $("#red-ui-sidebar-shade").show(); + $(".red-ui-sidebar-shade").show(); }, close: function() { settingsVisible = false; @@ -90,7 +90,7 @@ RED.userSettings = (function() { pane.close(); } }); - $("#red-ui-sidebar-shade").hide(); + $(".red-ui-sidebar-shade").hide(); }, show: function() {} diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js index 89019005f..b28c473ae 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view.js @@ -6973,7 +6973,7 @@ RED.view = (function() { selectNodes: function(options) { $("#red-ui-workspace-tabs-shade").show(); $("#red-ui-palette-shade").show(); - $("#red-ui-sidebar-shade").show(); + $(".red-ui-sidebar-shade").show(); $("#red-ui-header-shade").show(); $("#red-ui-workspace").addClass("red-ui-workspace-select-mode"); @@ -6995,7 +6995,7 @@ RED.view = (function() { clearSelection(); $("#red-ui-workspace-tabs-shade").hide(); $("#red-ui-palette-shade").hide(); - $("#red-ui-sidebar-shade").hide(); + $(".red-ui-sidebar-shade").hide(); $("#red-ui-header-shade").hide(); $("#red-ui-workspace").removeClass("red-ui-workspace-select-mode"); resetMouseVars(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/base.scss b/packages/node_modules/@node-red/editor-client/src/sass/base.scss index b6bded65d..298dab416 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/base.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/base.scss @@ -46,15 +46,10 @@ body { flex-direction: row; } -#red-ui-palette-shade, #red-ui-editor-shade, #red-ui-header-shade, #red-ui-sidebar-shade { +#red-ui-palette-shade, #red-ui-editor-shade, #red-ui-header-shade, .red-ui-sidebar-shade { @include mixins.shade; z-index: 5; } -#red-ui-sidebar-shade { - left: -8px; - top: -1px; - bottom: -1px; -} #red-ui-full-shade { @include mixins.shade; z-index: 15; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index acd983aad..e098b39a7 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -128,8 +128,10 @@ button.red-ui-sidebar-header-button-toggle { border-left: none; } -.red-ui-sidebar-shade { - @include mixins.shade; +.red-ui-sidebar-separator .red-ui-sidebar-shade { + // Other shades overlay their element so consume pointer events + // Sidebar separator shade is a child element so needs to explicitly block + pointer-events: none; } From 0ead2d815c5a707a7dbb7a2d7b0874a4107f905d Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Fri, 24 Oct 2025 17:35:00 +0100 Subject: [PATCH 06/21] Update sidebar tab ux --- .../editor-client/src/js/ui/sidebar.js | 270 +++++++++++------- .../editor-client/src/sass/sidebar.scss | 57 ++-- 2 files changed, 192 insertions(+), 135 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index be6f8863a..67f2bb5be 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -15,30 +15,30 @@ **/ RED.sidebar = (function() { - const primarySidebar = { - id: 'primary', - direction: 'right', - menuToggle: 'menu-item-sidebar', - minimumWidth: 180, - maximumWidth: 800, - defaultWidth: 300 - } - - // Palette Sidebar - const secondarySidebar = { - id: 'secondary', - direction: 'left', - menuToggle: 'menu-item-palette', - minimumWidth: 180, - maximumWidth: 180, - defaultWidth: 180 + const sidebars = { + primary: { + id: 'primary', + direction: 'right', + menuToggle: 'menu-item-sidebar', + minimumWidth: 180, + maximumWidth: 800, + defaultWidth: 300 + }, + secondary: { + id: 'secondary', + direction: 'left', + menuToggle: 'menu-item-palette', + minimumWidth: 180, + maximumWidth: 800, + defaultWidth: 180 + } } var knownTabs = {}; // We store the current sidebar tab id in localStorage as 'last-sidebar-tab' // This is restored when the editor is reloaded. - // We use primarySidebar.tabs.onchange to update localStorage. However that will + // We use sidebars.primary.tabs.onchange to update localStorage. However that will // also get triggered when the first tab gets added to the tabs - typically // the 'info' tab. So we use the following variable to store the retrieved // value from localStorage before we start adding the actual tabs @@ -61,7 +61,7 @@ RED.sidebar = (function() { options = title; } - const targetSidebar = options.target === 'secondary' ? secondarySidebar : primarySidebar; + const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; delete options.closeable; options.wrapper = $('
        ',{style:"height:100%"}).appendTo(targetSidebar.content) @@ -91,45 +91,115 @@ RED.sidebar = (function() { knownTabs[options.id] = options; - if (options.visible !== false) { - targetSidebar.tabs.addTab(knownTabs[options.id]); + options.tabButton = $('').appendTo(targetSidebar.tabBar); + options.tabButton.attr('data-tab-id', options.id) + + options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); + if (options.icon) { + $('',{style:"mask-image: url("+options.icon+"); -webkit-mask-image: url("+options.icon+");"}).appendTo(options.tabButton); + } else if (options.iconClass) { + $('',{class:options.iconClass}).appendTo(options.tabButton); } - if (targetSidebar.tabs.count() > 1) { - targetSidebar.tabs.container.show() + options.tabButton.on('click', function() { + const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; + if (targetSidebar.activeTab === options.id && RED.menu.isSelected(targetSidebar.menuToggle)) { + RED.menu.setSelected(targetSidebar.menuToggle, false); + } else { + RED.sidebar.show(options.id) + } + }) + if (targetSidebar.content.children().length === 1) { + RED.sidebar.show(options.id) } } function removeTab(id) { if (knownTabs[id]) { - const targetSidebar = knownTabs[id].target === 'secondary' ? secondarySidebar : primarySidebar; - targetSidebar.tabs.removeTab(id); + const targetSidebar = knownTabs[id].target === 'secondary' ? sidebars.secondary : sidebars.primary; $(knownTabs[id].wrapper).remove(); if (knownTabs[id].footer) { knownTabs[id].footer.remove(); } - delete knownTabs[id]; + targetSidebar.tabBar.find('button[data-tab-id="'+id+'"]').remove() RED.menu.removeItem("menu-item-view-menu-"+id); - if (targetSidebar.tabs.count() <= 1) { - targetSidebar.tabs.container.hide() + if (knownTabs[id].onremove) { + knownTabs[id].onremove.call(knownTabs[id]); + } + delete knownTabs[id]; + const firstTab = targetSidebar.tabBar.find('button').first().attr('data-tab-id'); + if (firstTab) { + RED.sidebar.show(firstTab); } } } + function moveTab(id, targetSidebar) { + const options = knownTabs[id]; + options.target = targetSidebar.id; + $(options.wrapper).appendTo(targetSidebar.content); + if (options.toolbar) { + targetSidebar.footer.append(options.toolbar); + } + // Reset the tooltip so its left/right direction is recalculated + options.tabButtonTooltip.delete() + options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); + + } + var sidebarSeparator = {}; sidebarSeparator.dragging = false; + function setupSidebarTabs(sidebar) { + const tabBar = $('
        ') + tabBar.data('sidebar', sidebar.id) + if (sidebar.direction === 'right') { + tabBar.insertAfter(sidebar.container); + } else if (sidebar.direction === 'left') { + tabBar.insertBefore(sidebar.container); + } + tabBar.sortable({ + cancel: false, + placeholder: "red-ui-sidebar-tab-bar-button-placeholder", + connectWith: ".red-ui-sidebar-tab-bar", + start: function(event, ui) { + // Remove the tooltip so it doesn't display unexpectedly whilst dragging + const tabId = ui.item.attr('data-tab-id'); + const options = knownTabs[tabId]; + options.tabButtonTooltip.delete() + }, + stop: function(event, ui) { + // Restore the tooltip + const tabId = ui.item.attr('data-tab-id'); + const options = knownTabs[tabId]; + options.tabButtonTooltip.delete() + options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); + }, + receive: function(event, ui) { + // Tab has been moved from one sidebar to another + const src = sidebars[ui.sender.data('sidebar')] + const dest = sidebars[$(this).data('sidebar')] + const tabId = ui.item.attr('data-tab-id'); + const tabOptions = knownTabs[tabId]; + moveTab(tabId, dest) + if (ui.item.hasClass('selected')) { + const firstTab = src.tabBar.find('button').first().attr('data-tab-id'); + if (firstTab) { + RED.sidebar.show(firstTab); + } + } + RED.sidebar.show(tabId) + } + }) + return tabBar + } function setupSidebarSeparator(sidebar) { const separator = $('
        '); const shade = $('
        ').appendTo(separator); let scaleFactor = 1; - let controlClass = 'red-ui-sidebar-control-right'; - let controlSlideDirection = 'right'; if (sidebar.direction === 'right') { separator.insertBefore(sidebar.container); } else if (sidebar.direction === 'left') { scaleFactor = -1; - controlClass = 'red-ui-sidebar-control-left'; - controlSlideDirection = 'left'; separator.insertAfter(sidebar.container); } separator.draggable({ @@ -185,7 +255,7 @@ RED.sidebar = (function() { sidebar.container.width(newSidebarWidth); ui.position.left -= scaleFactor * d - sidebar.tabs.resize(); + // sidebar.tabs.resize(); RED.events.emit("sidebar:resize"); }, stop:function(event,ui) { @@ -196,6 +266,7 @@ RED.sidebar = (function() { RED.menu.setSelected(sidebar.menuToggle,false); } sidebar.container.hide() + sidebar.separator.hide() if (sidebar.container.width() < sidebar.minimumWidth) { sidebar.container.width(sidebar.defaultWidth); } @@ -203,43 +274,16 @@ RED.sidebar = (function() { RED.events.emit("sidebar:resize"); } }); - - var sidebarControls = $('
        ').appendTo(separator); - sidebarControls.addClass(controlClass) - sidebarControls.on("click", function() { - sidebarControls.hide(); - RED.menu.toggleSelected(sidebar.menuToggle); - }) - separator.on("mouseenter", function() { - if (!sidebarSeparator.dragging && !shade.is(":visible")) { - if ( (RED.menu.isSelected(sidebar.menuToggle) && sidebar.direction === 'right') || - (!RED.menu.isSelected(sidebar.menuToggle) && sidebar.direction === 'left') ) { - sidebarControls.find("i").addClass("fa-chevron-right").removeClass("fa-chevron-left"); - } else { - sidebarControls.find("i").removeClass("fa-chevron-right").addClass("fa-chevron-left"); - } - sidebarControls.toggle("slide", { direction: controlSlideDirection }, 200); - } - }) - separator.on("mouseleave", function() { - if (!sidebarSeparator.dragging && !shade.is(":visible")) { - sidebarControls.stop(false,true); - sidebarControls.hide(); - } - }); return separator } function toggleSidebar(sidebar, state) { if (!state) { sidebar.container.hide() + sidebar.separator.hide() } else { - // console.log(sidebar.container.width(),sidebar.tabs.container.width()) sidebar.container.show() - sidebar.tabs.container.width() - setTimeout(function () { - sidebar.tabs.resize() - }, 100) + sidebar.separator.show() } RED.events.emit("sidebar:resize"); } @@ -251,11 +295,21 @@ RED.sidebar = (function() { if (id) { const tabOptions = knownTabs[id]; if (tabOptions) { - const targetSidebar = tabOptions.target === 'secondary' ? secondarySidebar : primarySidebar; - if (!targetSidebar.tabs.contains(id)) { - targetSidebar.tabs.addTab(knownTabs[id]); + const targetSidebar = tabOptions.target === 'secondary' ? sidebars.secondary : sidebars.primary; + targetSidebar.content.children().hide(); + targetSidebar.footer.children().hide(); + if (tabOptions.onchange) { + tabOptions.onchange.call(tabOptions); } - targetSidebar.tabs.activateTab(id); + $(tabOptions.wrapper).show(); + if (tabOptions.toolbar) { + $(tabOptions.toolbar).show(); + } + RED.settings.setLocal("last-sidebar-tab", tabOptions.id) + targetSidebar.tabBar.find('button').removeClass('selected') + targetSidebar.tabBar.find('button[data-tab-id="'+id+'"]').addClass('selected') + targetSidebar.activeTab = id + if (!skipShowSidebar && !RED.menu.isSelected(targetSidebar.menuToggle)) { RED.menu.setSelected(targetSidebar.menuToggle,true); } @@ -264,73 +318,71 @@ RED.sidebar = (function() { } function containsTab(id) { - return primarySidebar.tabs.contains(id); + return sidebars.primary.tabs.contains(id); } function setupSidebar(sidebar) { sidebar.container.addClass("red-ui-sidebar"); sidebar.container.width(sidebar.defaultWidth); sidebar.separator = setupSidebarSeparator(sidebar); - sidebar.tabs = RED.tabs.create({ - element: $('
          ').appendTo(sidebar.container), - onchange:function(tab) { - sidebar.content.children().hide(); - sidebar.footer.children().hide(); - if (tab) { - if (tab.onchange) { - tab.onchange.call(tab); - } - $(tab.wrapper).show(); - if (tab.toolbar) { - $(tab.toolbar).show(); - } - RED.settings.setLocal("last-sidebar-tab", tab.id) - } - }, - onremove: function(tab) { - $(tab.wrapper).hide(); - if (tab.onremove) { - tab.onremove.call(tab); - } - }, - // minimumActiveTabWidth: 70, - collapsible: true, - onreorder: function(order) { - RED.settings.set("editor.sidebar.order",order); - }, - order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"]) - // scrollable: true - }); - sidebar.tabs.container.hide() + sidebar.tabBar = setupSidebarTabs(sidebar) + // sidebar.tabs = RED.tabs.create({ + // element: $('
            ').appendTo(sidebar.container), + // onchange:function(tab) { + // sidebar.content.children().hide(); + // sidebar.footer.children().hide(); + // if (tab) { + // if (tab.onchange) { + // tab.onchange.call(tab); + // } + // $(tab.wrapper).show(); + // if (tab.toolbar) { + // $(tab.toolbar).show(); + // } + // RED.settings.setLocal("last-sidebar-tab", tab.id) + // } + // }, + // onremove: function(tab) { + // $(tab.wrapper).hide(); + // if (tab.onremove) { + // tab.onremove.call(tab); + // } + // }, + // // minimumActiveTabWidth: 70, + // collapsible: true, + // onreorder: function(order) { + // RED.settings.set("editor.sidebar.order",order); + // }, + // order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"]) + // // scrollable: true + // }); + // sidebar.tabs.container.hide() sidebar.content = $('
            ').appendTo(sidebar.container); sidebar.footer = $('').appendTo(sidebar.container); sidebar.shade = $('
            ').appendTo(sidebar.container); } function init () { - primarySidebar.container = $("#red-ui-sidebar"); - setupSidebar(primarySidebar) - secondarySidebar.container = $("#red-ui-sidebar-left"); - setupSidebar(secondarySidebar) + sidebars.primary.container = $("#red-ui-sidebar"); + setupSidebar(sidebars.primary) + sidebars.secondary.container = $("#red-ui-sidebar-left"); + setupSidebar(sidebars.secondary) RED.actions.add("core:toggle-sidebar",function(state){ if (state === undefined) { - RED.menu.toggleSelected(primarySidebar.menuToggle); + RED.menu.toggleSelected(sidebars.primary.menuToggle); } else { - toggleSidebar(primarySidebar, state); + toggleSidebar(sidebars.primary, state); } }); RED.actions.add("core:toggle-palette", function(state) { if (state === undefined) { - RED.menu.toggleSelected(secondarySidebar.menuToggle); + RED.menu.toggleSelected(sidebars.secondary.menuToggle); } else { - toggleSidebar(secondarySidebar, state); + toggleSidebar(sidebars.secondary, state); } }); - - RED.popover.tooltip(primarySidebar.separator.find(".red-ui-sidebar-control-right"),RED._("keyboard.toggleSidebar"),"core:toggle-sidebar"); - lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab") RED.sidebar.info.init(); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index e098b39a7..6a0384191 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -27,6 +27,7 @@ @include mixins.component-border; display: flex; flex-direction: column; + overflow: hidden; } #red-ui-sidebar.closing { @@ -73,6 +74,36 @@ } } +.red-ui-sidebar-tab-bar { + // width: 40px; + padding: 5px; + background-color: var(--red-ui-primary-background); + flex: 0 0 auto; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + + button { + @include mixins.workspace-button; + display: flex; + align-items: center; + justify-content: center; + padding: 0; + height: 28px; + width: 28px; + &:not(.selected):not(:hover) i { + opacity: 0.6; + } + &.selected { + box-shadow: 0 2px 0 0 var(--red-ui-form-input-border-selected-color); + } + } + .red-ui-sidebar-tab-bar-button-placeholder { + border: 1px dashed var(--red-ui-form-input-border-color); + } +} + .red-ui-sidebar .button { @include mixins.workspace-button; line-height: 18px; @@ -134,29 +165,3 @@ button.red-ui-sidebar-header-button-toggle { pointer-events: none; } - -@mixin red-ui-sidebar-control { - display: none; - position: absolute; - top: calc(50% - 26px); - z-index: 13; - padding:15px 8px; - border:1px solid var(--red-ui-primary-border-color); - background:var(--red-ui-primary-background); - color: var(--red-ui-secondary-text-color); - text-align: center; - cursor: pointer; -} - -.red-ui-sidebar-control-right { - @include red-ui-sidebar-control; - right: calc(100%); - border-top-left-radius: 5px; - border-bottom-left-radius: 5px; -} -.red-ui-sidebar-control-left { - @include red-ui-sidebar-control; - left: calc(100%); - border-top-right-radius: 5px; - border-bottom-right-radius: 5px; -} From b23d455ad549c2ed7db8884ddc22310e7d367567 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 27 Oct 2025 10:06:44 +0000 Subject: [PATCH 07/21] Fix tray positioning --- .../editor-client/src/js/ui/sidebar.js | 52 +++++-------------- .../@node-red/editor-client/src/js/ui/tray.js | 4 +- .../editor-client/src/sass/sidebar.scss | 7 +-- 3 files changed, 17 insertions(+), 46 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index 67f2bb5be..27f6105c1 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -60,6 +60,7 @@ RED.sidebar = (function() { } else if (typeof title === "object") { options = title; } + options.target = options.target || 'primary'; const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; delete options.closeable; @@ -133,7 +134,7 @@ RED.sidebar = (function() { } } - function moveTab(id, targetSidebar) { + function moveTab(id, srcSidebar, targetSidebar) { const options = knownTabs[id]; options.target = targetSidebar.id; $(options.wrapper).appendTo(targetSidebar.content); @@ -144,6 +145,14 @@ RED.sidebar = (function() { options.tabButtonTooltip.delete() options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); + if (targetSidebar.content.children().length === 1) { + RED.sidebar.show(options.id) + } + if (srcSidebar.content.children().length === 0) { + RED.menu.setSelected(srcSidebar.menuToggle, false); + } + + } var sidebarSeparator = {}; @@ -151,6 +160,7 @@ RED.sidebar = (function() { function setupSidebarTabs(sidebar) { const tabBar = $('
            ') + tabBar.attr('id', sidebar.container.attr('id') + '-tab-bar') tabBar.data('sidebar', sidebar.id) if (sidebar.direction === 'right') { tabBar.insertAfter(sidebar.container); @@ -179,8 +189,7 @@ RED.sidebar = (function() { const src = sidebars[ui.sender.data('sidebar')] const dest = sidebars[$(this).data('sidebar')] const tabId = ui.item.attr('data-tab-id'); - const tabOptions = knownTabs[tabId]; - moveTab(tabId, dest) + moveTab(tabId, src, dest) if (ui.item.hasClass('selected')) { const firstTab = src.tabBar.find('button').first().attr('data-tab-id'); if (firstTab) { @@ -194,7 +203,8 @@ RED.sidebar = (function() { } function setupSidebarSeparator(sidebar) { const separator = $('
            '); - const shade = $('
            ').appendTo(separator); + separator.attr('id', sidebar.container.attr('id') + '-separator') + $('
            ').appendTo(separator); let scaleFactor = 1; if (sidebar.direction === 'right') { separator.insertBefore(sidebar.container); @@ -205,9 +215,6 @@ RED.sidebar = (function() { separator.draggable({ axis: "x", start:function(event,ui) { - if (shade.is(":visible")) { - return false - } sidebarSeparator.closing = false; sidebarSeparator.opening = false; // var winWidth = $("#red-ui-editor").width(); @@ -326,37 +333,6 @@ RED.sidebar = (function() { sidebar.container.width(sidebar.defaultWidth); sidebar.separator = setupSidebarSeparator(sidebar); sidebar.tabBar = setupSidebarTabs(sidebar) - // sidebar.tabs = RED.tabs.create({ - // element: $('
              ').appendTo(sidebar.container), - // onchange:function(tab) { - // sidebar.content.children().hide(); - // sidebar.footer.children().hide(); - // if (tab) { - // if (tab.onchange) { - // tab.onchange.call(tab); - // } - // $(tab.wrapper).show(); - // if (tab.toolbar) { - // $(tab.toolbar).show(); - // } - // RED.settings.setLocal("last-sidebar-tab", tab.id) - // } - // }, - // onremove: function(tab) { - // $(tab.wrapper).hide(); - // if (tab.onremove) { - // tab.onremove.call(tab); - // } - // }, - // // minimumActiveTabWidth: 70, - // collapsible: true, - // onreorder: function(order) { - // RED.settings.set("editor.sidebar.order",order); - // }, - // order: RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"]) - // // scrollable: true - // }); - // sidebar.tabs.container.hide() sidebar.content = $('
              ').appendTo(sidebar.container); sidebar.footer = $('').appendTo(sidebar.container); sidebar.shade = $('
              ').appendTo(sidebar.container); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js index faeac3a22..a275a1173 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tray.js @@ -194,8 +194,8 @@ } function handleWindowResize() { - // TODO: red-ui-sidebar has a width when hidden - so we need to handle that case and set the right pos to its separator - $("#red-ui-editor-stack").css('right', $("#red-ui-sidebar").outerWidth() + 11); + let sidebarWidth = $("#red-ui-sidebar").is(":visible") ? $("#red-ui-sidebar").outerWidth() + $("#red-ui-sidebar-separator").outerWidth() : 0; + $("#red-ui-editor-stack").css('right', sidebarWidth + $("#red-ui-sidebar-tab-bar").outerWidth() + 1); if (stack.length > 0) { var tray = stack[stack.length-1]; if (tray.options.maximized || tray.width > $("#red-ui-editor-stack").position().left-8) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 6a0384191..d5b6b454e 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -83,6 +83,7 @@ flex-direction: column; align-items: center; gap: 10px; + z-index: 10; button { @include mixins.workspace-button; @@ -159,9 +160,3 @@ button.red-ui-sidebar-header-button-toggle { border-left: none; } -.red-ui-sidebar-separator .red-ui-sidebar-shade { - // Other shades overlay their element so consume pointer events - // Sidebar separator shade is a child element so needs to explicitly block - pointer-events: none; -} - From 6ffa23a44e5a8f42038a4d541d6775678fbae7e1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 27 Oct 2025 16:28:40 +0000 Subject: [PATCH 08/21] Fix sidebar sortable for touch events --- .../node_modules/@node-red/editor-client/src/js/ui/sidebar.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index 27f6105c1..ba2cb632c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -14,7 +14,6 @@ * limitations under the License. **/ RED.sidebar = (function() { - const sidebars = { primary: { id: 'primary', @@ -101,7 +100,7 @@ RED.sidebar = (function() { } else if (options.iconClass) { $('',{class:options.iconClass}).appendTo(options.tabButton); } - options.tabButton.on('click', function() { + options.tabButton.on('mouseup', function(evt) { const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; if (targetSidebar.activeTab === options.id && RED.menu.isSelected(targetSidebar.menuToggle)) { RED.menu.setSelected(targetSidebar.menuToggle, false); @@ -168,6 +167,7 @@ RED.sidebar = (function() { tabBar.insertBefore(sidebar.container); } tabBar.sortable({ + distance: 10, cancel: false, placeholder: "red-ui-sidebar-tab-bar-button-placeholder", connectWith: ".red-ui-sidebar-tab-bar", From 6c36f789bde66569a8c02ad5e52a049e365f1e76 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 28 Oct 2025 14:41:19 +0000 Subject: [PATCH 09/21] Fix login display --- packages/node_modules/@node-red/editor-client/src/js/red.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 1c073b137..7645482eb 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -894,13 +894,17 @@ var RED = (function() { var logo = $('').appendTo(header); $('
                ').appendTo(header); $('
                ').appendTo(header); - $('
                '+ + $('
                '+ '
                '+ '
                '+ '
                '+ '
                '+ // '
                '+ '
                ').appendTo(options.target); + + // Don't use the `hide` class on this container, as the show reverts it to block rather + // than the expected flex. So hide via jQuery as it'll track the show state internally. + options.target.find('#red-ui-main-container').hide() $('
                ').appendTo(options.target); $('
                ').appendTo(options.target); From b6367bbb44be5aa01732476a638bb757b5ed0075 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 10:40:02 +0000 Subject: [PATCH 10/21] Track sidebar state --- .../editor-client/src/js/ui/sidebar.js | 100 ++++++++++++++---- 1 file changed, 79 insertions(+), 21 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index ba2cb632c..30a58d897 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -33,7 +33,24 @@ RED.sidebar = (function() { } } - var knownTabs = {}; + const knownTabs = {}; + + function exportSidebarState () { + const state = { + primary: [], + secondary: [] + } + sidebars.primary.tabBar.children('button').each(function() { + const tabId = $(this).attr('data-tab-id'); + state.primary.push(tabId); + }) + sidebars.secondary.tabBar.children('button').each(function() { + const tabId = $(this).attr('data-tab-id'); + state.secondary.push(tabId); + }) + RED.settings.set('editor.sidebar.state', state) + } + // We store the current sidebar tab id in localStorage as 'last-sidebar-tab' // This is restored when the editor is reloaded. @@ -41,8 +58,7 @@ RED.sidebar = (function() { // also get triggered when the first tab gets added to the tabs - typically // the 'info' tab. So we use the following variable to store the retrieved // value from localStorage before we start adding the actual tabs - var lastSessionSelectedTab = null; - + let lastSessionSelectedTabs = {} function addTab(title,content,closeable,visible) { var options; @@ -60,6 +76,28 @@ RED.sidebar = (function() { options = title; } options.target = options.target || 'primary'; + let targetTabButtonIndex = -1 // Append to end by default + + // Check the saved sidebar state to see if this tab should be added to the primary or secondary sidebar + const savedState = RED.settings.get('editor.sidebar.state', null) + if (savedState) { + let targetSidebar = null + let sidebarState + if (savedState.secondary.includes(options.id)) { + options.target = 'secondary' + sidebarState = savedState.secondary + targetSidebar = sidebars.secondary + } else if (savedState.primary.includes(options.id)) { + options.target = 'primary' + sidebarState = savedState.primary + targetSidebar = sidebars.primary + } + if (targetSidebar) { + // This tab was found in the saved sidebar state. Now find the target position for the tab button + targetTabButtonIndex = sidebarState.indexOf(options.id) + } + } + const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; delete options.closeable; @@ -78,6 +116,7 @@ RED.sidebar = (function() { } var id = options.id; + // console.log('menu', options.id, options.name) RED.menu.addItem("menu-item-view-menu",{ id:"menu-item-view-menu-"+options.id, label:options.name, @@ -90,17 +129,28 @@ RED.sidebar = (function() { options.iconClass = options.iconClass || "fa fa-square-o" knownTabs[options.id] = options; - - options.tabButton = $('').appendTo(targetSidebar.tabBar); + options.tabButton = $('') + // Insert the tab button at the correct index + if (targetTabButtonIndex === -1 || targetTabButtonIndex >= targetSidebar.tabBar.children().length) { + // Append to end + options.tabButton = $('').appendTo(targetSidebar.tabBar); + } else { + // Insert before the item at targetTabButtonIndex + options.tabButton = $('').insertBefore(targetSidebar.tabBar.children().eq(targetTabButtonIndex)); + } options.tabButton.attr('data-tab-id', options.id) options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); if (options.icon) { - $('',{style:"mask-image: url("+options.icon+"); -webkit-mask-image: url("+options.icon+");"}).appendTo(options.tabButton); + $('',{class: 'red-ui-sidebar-tab-icon', style:"mask-image: url("+options.icon+"); -webkit-mask-image: url("+options.icon+");"}).appendTo(options.tabButton); } else if (options.iconClass) { $('',{class:options.iconClass}).appendTo(options.tabButton); } options.tabButton.on('mouseup', function(evt) { + if (draggingTabButton) { + draggingTabButton = false + return + } const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; if (targetSidebar.activeTab === options.id && RED.menu.isSelected(targetSidebar.menuToggle)) { RED.menu.setSelected(targetSidebar.menuToggle, false); @@ -150,13 +200,9 @@ RED.sidebar = (function() { if (srcSidebar.content.children().length === 0) { RED.menu.setSelected(srcSidebar.menuToggle, false); } - - } - var sidebarSeparator = {}; - sidebarSeparator.dragging = false; - + let draggingTabButton = false function setupSidebarTabs(sidebar) { const tabBar = $('
                ') tabBar.attr('id', sidebar.container.attr('id') + '-tab-bar') @@ -176,6 +222,7 @@ RED.sidebar = (function() { const tabId = ui.item.attr('data-tab-id'); const options = knownTabs[tabId]; options.tabButtonTooltip.delete() + draggingTabButton = true }, stop: function(event, ui) { // Restore the tooltip @@ -183,6 +230,8 @@ RED.sidebar = (function() { const options = knownTabs[tabId]; options.tabButtonTooltip.delete() options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); + // Save the sidebar state + exportSidebarState() }, receive: function(event, ui) { // Tab has been moved from one sidebar to another @@ -212,6 +261,8 @@ RED.sidebar = (function() { scaleFactor = -1; separator.insertAfter(sidebar.container); } + // Track sidebar state whilst dragging + const sidebarSeparator = {} separator.draggable({ axis: "x", start:function(event,ui) { @@ -288,6 +339,7 @@ RED.sidebar = (function() { if (!state) { sidebar.container.hide() sidebar.separator.hide() + sidebar.tabBar.find('button').removeClass('selected') } else { sidebar.container.show() sidebar.separator.show() @@ -297,7 +349,16 @@ RED.sidebar = (function() { function showSidebar(id, skipShowSidebar) { if (id === ":first") { - id = lastSessionSelectedTab || RED.settings.get("editor.sidebar.order",["info", "help", "version-control", "debug"])[0] + // Show the last selected tab for each sidebar + Object.keys(sidebars).forEach(function(sidebarKey) { + const sidebar = sidebars[sidebarKey]; + let lastTabId = lastSessionSelectedTabs[sidebarKey]; + if (!lastTabId) { + lastTabId = sidebar.tabBar.children('button').first().attr('data-tab-id'); + } + showSidebar(lastTabId, true) + }) + return } if (id) { const tabOptions = knownTabs[id]; @@ -312,7 +373,7 @@ RED.sidebar = (function() { if (tabOptions.toolbar) { $(tabOptions.toolbar).show(); } - RED.settings.setLocal("last-sidebar-tab", tabOptions.id) + RED.settings.setLocal("last-sidebar-tab-" + targetSidebar.id, tabOptions.id) targetSidebar.tabBar.find('button').removeClass('selected') targetSidebar.tabBar.find('button[data-tab-id="'+id+'"]').addClass('selected') targetSidebar.activeTab = id @@ -359,14 +420,11 @@ RED.sidebar = (function() { } }); - lastSessionSelectedTab = RED.settings.getLocal("last-sidebar-tab") - - RED.sidebar.info.init(); - RED.sidebar.help.init(); - RED.sidebar.config.init(); - RED.sidebar.context.init(); - // hide sidebar at start if screen rather narrow... - if ($("#red-ui-editor").width() < 600) { RED.menu.setSelected("menu-item-sidebar",false); } + // Remember the last selected tab for each sidebar before + // the tabs are readded causing the state to get updated + Object.keys(sidebars).forEach(function(sidebarKey) { + lastSessionSelectedTabs[sidebarKey] = RED.settings.getLocal("last-sidebar-tab-" + sidebarKey) + }) } return { From d5a28ce5e795f5372a1ffb05d81794f3a98e95a0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 10:40:21 +0000 Subject: [PATCH 11/21] Fix sidebar menu to include all options Needed to reorder initialisation as sidebars were adding before the menu component was initialised, meaning their menu entries didn't get added --- .../@node-red/editor-client/src/js/red.js | 34 ++++++++++++------- .../editor-client/src/js/ui/common/menu.js | 2 +- .../editor-client/src/js/ui/tab-info.js | 2 ++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/red.js b/packages/node_modules/@node-red/editor-client/src/js/red.js index 7645482eb..7a49b114f 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/red.js +++ b/packages/node_modules/@node-red/editor-client/src/js/red.js @@ -844,7 +844,6 @@ var RED = (function() { RED.notifications.init(); RED.library.init(); RED.sidebar.init(); - RED.palette.init(); RED.eventLog.init(); if (RED.settings.get('externalModules.palette.allowInstall', true) !== false) { @@ -869,23 +868,34 @@ var RED = (function() { RED.diagnostics.init(); RED.diff.init(); - RED.deploy.init(RED.settings.theme("deployButton",null)); + RED.keyboard.init(() => { + buildMainMenu(); - RED.keyboard.init(buildMainMenu); - RED.envVar.init(); + // Register the core set of sidebar panels now the menu is ready to receive items + RED.palette.init(); + RED.sidebar.info.init(); + RED.sidebar.help.init(); + RED.sidebar.config.init(); + RED.sidebar.context.init(); + // hide sidebar at start if screen rather narrow... + if ($("#red-ui-editor").width() < 600) { RED.menu.setSelected("menu-item-sidebar", false); } - RED.nodes.init(); - RED.runtime.init() + RED.envVar.init(); - if (RED.settings.theme("multiplayer.enabled",false)) { - RED.multiplayer.init() - } - RED.comms.connect(); + RED.nodes.init(); + RED.runtime.init() - $("#red-ui-main-container").show(); + if (RED.settings.theme("multiplayer.enabled",false)) { + RED.multiplayer.init() + } + RED.comms.connect(); - loadPluginList(); + $("#red-ui-main-container").show(); + RED.events.emit("sidebar:resize") + + loadPluginList(); + }); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js index 8d0f1dbd3..091c69c34 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/common/menu.js @@ -323,7 +323,7 @@ RED.menu = (function() { } else { for (var i=0;i Date: Wed, 29 Oct 2025 10:41:15 +0000 Subject: [PATCH 12/21] Update palette sidebar icon to custom svg --- .../editor-client/src/js/ui/palette.js | 2 +- .../editor-client/src/sass/sidebar.scss | 17 +++++++++++++++++ 2 files changed, 18 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 1cd2d0b41..5fece2198 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 @@ -617,7 +617,7 @@ RED.palette = (function() { id: "palette", label: "Palette", name: "Palette", - iconClass: "fa fa-tags", + icon: "red/images/subflow_tab.svg", content, toolbar, pinned: true, diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index d5b6b454e..46a023bb5 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -160,3 +160,20 @@ button.red-ui-sidebar-header-button-toggle { border-left: none; } +i.red-ui-sidebar-tab-icon { + display: inline-block; + // margin-left: -8px; + // margin-right: 3px; + // margin-top: -2px; + opacity: 1; + width: 18px; + height: 18px; + vertical-align: middle; + -webkit-mask-size: contain; + mask-size: contain; + -webkit-mask-position: center; + mask-position: center; + -webkit-mask-repeat: no-repeat; + mask-repeat: no-repeat; + background-color: var(--red-ui-workspace-button-color); +} \ No newline at end of file From 41ed5c94b6df45e21ebd264c5cd1a83417cfaae0 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 10:51:31 +0000 Subject: [PATCH 13/21] Don't squash buttons when resizing window --- .../@node-red/editor-client/src/js/ui/sidebar.js | 10 ++++++++++ .../@node-red/editor-client/src/sass/sidebar.scss | 2 ++ 2 files changed, 12 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index 30a58d897..4ddfcd8ee 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -248,6 +248,16 @@ RED.sidebar = (function() { RED.sidebar.show(tabId) } }) + // $(window).on("resize", function () { + // const lastChild = tabBar.children().last(); + // if (lastChild.length > 0) { + // const tabBarHeight = tabBar.height(); + // const lastChildBottom = lastChild.position().top + lastChild.outerHeight(); + // if (lastChildBottom > tabBarHeight) { + // console.log('overflow') + // } + // } + // }) return tabBar } function setupSidebarSeparator(sidebar) { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 46a023bb5..af20097dc 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -88,6 +88,8 @@ button { @include mixins.workspace-button; display: flex; + flex-grow: 0; + flex-shrink: 0; align-items: center; justify-content: center; padding: 0; From f690fcb295331237f816f61837d98e00353e06eb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 11:01:13 +0000 Subject: [PATCH 14/21] Apply default layout --- .../@node-red/editor-client/src/js/ui/sidebar.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index 4ddfcd8ee..f438df283 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -29,9 +29,14 @@ RED.sidebar = (function() { menuToggle: 'menu-item-palette', minimumWidth: 180, maximumWidth: 800, + // Make LH side slightly narrower by default as its the palette that doesn't require a lot of width defaultWidth: 180 } } + const defaultSidebarConfiguration = { + primary: ['info','debug','help','config','context'], + secondary: ['palette'] + } const knownTabs = {}; @@ -79,7 +84,7 @@ RED.sidebar = (function() { let targetTabButtonIndex = -1 // Append to end by default // Check the saved sidebar state to see if this tab should be added to the primary or secondary sidebar - const savedState = RED.settings.get('editor.sidebar.state', null) + const savedState = RED.settings.get('editor.sidebar.state', defaultSidebarConfiguration) if (savedState) { let targetSidebar = null let sidebarState From bd8a1eda90426920dfd11ea49d7fca6d5e19352b Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 16:14:55 +0000 Subject: [PATCH 15/21] Make sidebar separators a bit narrower --- .../node_modules/@node-red/editor-client/src/sass/sidebar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index af20097dc..7437e1b30 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -51,7 +51,7 @@ } .red-ui-sidebar-separator { - width: 10px; + width: 7px; flex: 0 0 auto; // z-index: 11; background-color: var(--red-ui-primary-background); From 8b0f926856a413fe173f79448fee75591e456862 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 16:48:10 +0000 Subject: [PATCH 16/21] Improve styling for info sidebar on LH side --- .../@node-red/editor-client/src/sass/tab-info.scss | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss index 732d4dfe8..a8fc62446 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tab-info.scss @@ -442,18 +442,6 @@ div.red-ui-info-table { right: 1px; padding: 1px 2px 0 1px; text-align: right; - background: var(--red-ui-list-item-background); - - .red-ui-treeList-label:hover & { - background: var(--red-ui-list-item-background-hover); - } - .red-ui-treeList-label.focus & { - background: var(--red-ui-list-item-background-hover); - } - .red-ui-treeList-label.selected & { - background: var(--red-ui-list-item-background-selected); - } - &.red-ui-info-outline-item-hover-controls button { min-width: 23px; @@ -580,6 +568,7 @@ div.red-ui-info-table { top: 6px; right: 8px; width: calc(100% - 130px); + min-width: 150px; max-width: 250px; background: var(--red-ui-palette-header-background); } From 56b243951134a8a39f6da2c62022a2f9a512c26a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 29 Oct 2025 17:07:55 +0000 Subject: [PATCH 17/21] Ensure tab button is visible when sorting --- .../node_modules/@node-red/editor-client/src/js/ui/sidebar.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index f438df283..6813e7626 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -228,6 +228,7 @@ RED.sidebar = (function() { const options = knownTabs[tabId]; options.tabButtonTooltip.delete() draggingTabButton = true + tabBar.css('z-index','inherit') }, stop: function(event, ui) { // Restore the tooltip @@ -237,6 +238,7 @@ RED.sidebar = (function() { options.tabButtonTooltip = RED.popover.tooltip(options.tabButton, options.name, options.action); // Save the sidebar state exportSidebarState() + tabBar.css('z-index','') }, receive: function(event, ui) { // Tab has been moved from one sidebar to another From 096bafb75c7b68f767542e9f3271fe3e2c14d2da Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 30 Oct 2025 14:15:59 +0000 Subject: [PATCH 18/21] Update info tab sidebar --- .../@node-red/editor-client/src/images/explorer.svg | 1 + .../node_modules/@node-red/editor-client/src/js/ui/tab-info.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 packages/node_modules/@node-red/editor-client/src/images/explorer.svg diff --git a/packages/node_modules/@node-red/editor-client/src/images/explorer.svg b/packages/node_modules/@node-red/editor-client/src/images/explorer.svg new file mode 100644 index 000000000..555efcc92 --- /dev/null +++ b/packages/node_modules/@node-red/editor-client/src/images/explorer.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js index 557fd970b..49b171ec5 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js @@ -122,7 +122,7 @@ RED.sidebar.info = (function() { // target: "secondary", label: RED._("sidebar.info.label"), name: RED._("sidebar.info.name"), - iconClass: "fa fa-info", + icon: "red/images/explorer.svg", action:"core:show-info-tab", content: content, pinned: true, From 9e9fa2b92da12a7a083747d974584f6fc051f17a Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 4 Dec 2025 13:33:44 +0000 Subject: [PATCH 19/21] Improve styling --- .../editor-client/src/js/ui/sidebar.js | 7 +- .../editor-client/src/sass/mixins.scss | 1 + .../editor-client/src/sass/sidebar.scss | 70 +++++++++++++------ .../editor-client/src/sass/workspace.scss | 2 + 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index 6813e7626..dc1b97f47 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -30,7 +30,7 @@ RED.sidebar = (function() { minimumWidth: 180, maximumWidth: 800, // Make LH side slightly narrower by default as its the palette that doesn't require a lot of width - defaultWidth: 180 + defaultWidth: 210 } } const defaultSidebarConfiguration = { @@ -209,7 +209,7 @@ RED.sidebar = (function() { let draggingTabButton = false function setupSidebarTabs(sidebar) { - const tabBar = $('
                ') + const tabBar = $('
                ').addClass('red-ui-sidebar-' + sidebar.direction); tabBar.attr('id', sidebar.container.attr('id') + '-tab-bar') tabBar.data('sidebar', sidebar.id) if (sidebar.direction === 'right') { @@ -271,6 +271,7 @@ RED.sidebar = (function() { const separator = $('
                '); separator.attr('id', sidebar.container.attr('id') + '-separator') $('
                ').appendTo(separator); + $('
                ').appendTo(separator); let scaleFactor = 1; if (sidebar.direction === 'right') { separator.insertBefore(sidebar.container); @@ -407,7 +408,7 @@ RED.sidebar = (function() { } function setupSidebar(sidebar) { - sidebar.container.addClass("red-ui-sidebar"); + sidebar.container.addClass("red-ui-sidebar").addClass('red-ui-sidebar-' + sidebar.direction);; sidebar.container.width(sidebar.defaultWidth); sidebar.separator = setupSidebarSeparator(sidebar); sidebar.tabBar = setupSidebarTabs(sidebar) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss b/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss index 486396c59..28eeba3c2 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/mixins.scss @@ -70,6 +70,7 @@ text-align: center; margin:0; cursor:pointer; + border-radius: 3px; &.selected:not(.disabled):not(:disabled) { color: var(--red-ui-workspace-button-color-selected) !important; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss index 7437e1b30..02a4bec8d 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sidebar.scss @@ -29,7 +29,12 @@ flex-direction: column; overflow: hidden; } - +.red-ui-sidebar-left { + border-left: none; +} +.red-ui-sidebar-right { + border-right: none; +} #red-ui-sidebar.closing { border-style: dashed; } @@ -51,39 +56,57 @@ } .red-ui-sidebar-separator { - width: 7px; + width: 0; flex: 0 0 auto; // z-index: 11; - background-color: var(--red-ui-primary-background); + background-color: var(--red-ui-view-background); + overflow: visible; cursor: col-resize; - &:before { - content: ''; - display: block; - width: 100%; + .red-ui-sidebar-separator-handle { + position: absolute; + // background: rgba(255,140,0,0.2); + top: 0; + left: -6px; + width: 12px; height: 100%; - -webkit-mask-image: url(images/grip.svg); - mask-image: url(images/grip.svg); - -webkit-mask-size: auto; - mask-size: auto; - -webkit-mask-position: 50% 50%; - mask-position: 50% 50%; - -webkit-mask-repeat: no-repeat; - mask-repeat: no-repeat; - background-color: var(--red-ui-grip-color); + z-index: 20; } + // &:before { + // content: ''; + // display: block; + // width: 100%; + // height: 100%; + // -webkit-mask-image: url(images/grip.svg); + // mask-image: url(images/grip.svg); + // -webkit-mask-size: auto; + // mask-size: auto; + // -webkit-mask-position: 50% 50%; + // mask-position: 50% 50%; + // -webkit-mask-repeat: no-repeat; + // mask-repeat: no-repeat; + // background-color: var(--red-ui-grip-color); + // } } .red-ui-sidebar-tab-bar { // width: 40px; - padding: 5px; - background-color: var(--red-ui-primary-background); + padding: 8px; + background-color: var(--red-ui-secondary-background); flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; - gap: 10px; + gap: 12px; z-index: 10; + border: 1px solid var(--red-ui-primary-border-color); + + &.red-ui-sidebar-left { + border-left: none; + } + &.red-ui-sidebar-right { + border-right: none; + } button { @include mixins.workspace-button; @@ -95,11 +118,14 @@ padding: 0; height: 28px; width: 28px; - &:not(.selected):not(:hover) i { - opacity: 0.6; + &:not(.selected):not(:hover) { + border: none; + i { + opacity: 0.7; + } } &.selected { - box-shadow: 0 2px 0 0 var(--red-ui-form-input-border-selected-color); + // box-shadow: 0 2px 0 0 var(--red-ui-form-input-border-selected-color); } } .red-ui-sidebar-tab-bar-button-placeholder { diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss index d95cbf295..7e4843402 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspace.scss @@ -20,6 +20,8 @@ margin: 0; overflow: hidden; @include mixins.component-border; + border-left: none; + border-right: none; transition: left 0.1s ease-in-out; position: relative; flex-grow: 1; From 503fe7377bedc72d3db8b1bdb8ed0d9e76a97104 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 4 Dec 2025 13:37:23 +0000 Subject: [PATCH 20/21] Fix linting --- .../node_modules/@node-red/editor-client/src/js/ui/sidebar.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js index dc1b97f47..8bbc516bd 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/sidebar.js @@ -408,7 +408,7 @@ RED.sidebar = (function() { } function setupSidebar(sidebar) { - sidebar.container.addClass("red-ui-sidebar").addClass('red-ui-sidebar-' + sidebar.direction);; + sidebar.container.addClass("red-ui-sidebar").addClass('red-ui-sidebar-' + sidebar.direction); sidebar.container.width(sidebar.defaultWidth); sidebar.separator = setupSidebarSeparator(sidebar); sidebar.tabBar = setupSidebarTabs(sidebar) From 81ab7d7e0a992185d28a001eb530da3652640ff1 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Thu, 4 Dec 2025 14:09:06 +0000 Subject: [PATCH 21/21] Update tour with sidebar details --- .../editor-client/src/tours/welcome.js | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js index fcc2519b1..4dd386683 100644 --- a/packages/node_modules/@node-red/editor-client/src/tours/welcome.js +++ b/packages/node_modules/@node-red/editor-client/src/tours/welcome.js @@ -15,6 +15,33 @@ export default {

                We will be making incremental changes betwen each beta release, so please try it out and let us know your feedback!

                ` } + }, + { + title: { + "en-US": "New Sidebar Design", + }, + description: { + "en-US": ` +

                The sidebars have been redesigned to provide a cleaner and more consistent user experience.

                +

                Rather than hide them in a dropdown menu, the available sidebars are now listed down each side of the editor.

                +

                You can also move the sidebars between the two sides by dragging their buttons around.

                +

                We have moved the Information sidebar to the left-hand side to provide a more natural way to navigate around your flows.

                +

                This is the first iteration of changes. In a future beta we will add the ability to split the sidebar vertically, so you can have multiple +panels showing at once.

                +`, + } + }, + { + title: { + "en-US": "Better Flow Navigation", + }, + description: { + "en-US": ` +

                Some of the ways you can pan and zoom around the editor workspace have been improved to provide a more standard way of working.

                +

                You can use your middle-mouse button to pan around the workspace, but if you don't have such a button, you can press the spacebar and drag with the left mouse button.

                +

                There is also a new 'zoom to fit' button in the status bar; this will set your zoom level to ensure all of your nodes are currently visible.

                +`, + } } ] }