From 282db370d79d907269e6d417d832c0b7b9cb3ebb Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 24 Feb 2026 11:23:19 +0000 Subject: [PATCH 1/8] UX updates for beta 3 --- .../@node-red/editor-client/src/js/red.js | 14 ++- .../editor-client/src/js/ui/common/tabs.js | 84 ++++++------- .../editor-client/src/js/ui/subflow.js | 4 +- .../editor-client/src/js/ui/view-navigator.js | 20 ++-- .../@node-red/editor-client/src/js/ui/view.js | 26 ++++- .../editor-client/src/js/ui/workspaces.js | 110 +++++++++++++++++- .../editor-client/src/sass/colors.scss | 2 + .../editor-client/src/sass/editor.scss | 2 +- .../editor-client/src/sass/header.scss | 32 ++--- .../editor-client/src/sass/sidebar.scss | 26 ++--- .../editor-client/src/sass/sizes.scss | 2 +- .../editor-client/src/sass/tabs.scss | 82 ++++++++++--- .../editor-client/src/sass/variables.scss | 3 + .../editor-client/src/sass/workspace.scss | 106 ++++++++++++----- .../src/sass/workspaceToolbar.scss | 11 +- 15 files changed, 390 insertions(+), 134 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 65b7d3bea..15227c7ca 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 @@ -669,12 +669,12 @@ var RED = (function() { RED.eventLog.log(id,payload); }); - $(".red-ui-header-toolbar").show(); + loader.end(); + $(".red-ui-header-toolbar").removeClass('hide'); RED.sidebar.show(":first", true); setTimeout(function() { - loader.end(); checkTelemetry(function () { checkFirstRun(function() { if (showProjectWelcome) { @@ -893,6 +893,7 @@ var RED = (function() { RED.comms.connect(); $("#red-ui-main-container").show(); + setTimeout(() => $("#red-ui-header-tabs").show(), 100) RED.events.emit("sidebar:resize") loadPluginList(); @@ -901,14 +902,17 @@ var RED = (function() { function buildEditor(options) { - var header = $('
').appendTo(options.target); - var logo = $('').appendTo(header); + const header = $('
').appendTo(options.target); + const logo = $('').appendTo(header); + $('
').appendTo(header); $('').appendTo(header); $('
').appendTo(header); $('
'+ '
'+ '
'+ - '
'+ + '
'+ + '
'+ + '
'+ '
'+ '
').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 8f0b705ed..af8ccd524 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 @@ -30,6 +30,7 @@ RED.tabs = (function() { var currentActiveTabWidth = 0; var collapsibleMenu; var mousedownTab; + var mouseclickTab; var preferredOrder = options.order; var ul = options.element || $("#"+options.id); var wrapper = ul.wrap( "
" ).parent(); @@ -39,6 +40,34 @@ RED.tabs = (function() { wrapper.addClass("red-ui-tabs-vertical"); } + var scrollLeft; + var scrollRight; + + if (options.scrollable) { + wrapper.addClass("red-ui-tabs-scrollable"); + scrollContainer.addClass("red-ui-tabs-scroll-container"); + scrollContainer.on("scroll",function(evt) { + // Generated by trackpads - not mousewheel + updateScroll(evt); + }); + scrollContainer.on("wheel", function(evt) { + if (evt.originalEvent.deltaX === 0) { + // Prevent the scroll event from firing + evt.preventDefault(); + + // Assume this is wheel event which might not trigger + // the scroll event, so do things manually + var sl = scrollContainer.scrollLeft(); + sl += evt.originalEvent.deltaY; + scrollContainer.scrollLeft(sl); + } + }) + scrollLeft = $('').prependTo(wrapper).find("a"); + scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();}); + scrollRight = $('
').appendTo(wrapper).find("a"); + scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,evt.shiftKey?('+='+(scrollContainer[0].scrollWidth - scrollContainer.width()-scrollContainer.scrollLeft())):'+=150') }).on('click',function(evt){ evt.preventDefault();}); + } + if (options.addButton) { wrapper.addClass("red-ui-tabs-add"); var addButton = $('
').appendTo(wrapper); @@ -165,34 +194,6 @@ RED.tabs = (function() { }) } - var scrollLeft; - var scrollRight; - - if (options.scrollable) { - wrapper.addClass("red-ui-tabs-scrollable"); - scrollContainer.addClass("red-ui-tabs-scroll-container"); - scrollContainer.on("scroll",function(evt) { - // Generated by trackpads - not mousewheel - updateScroll(evt); - }); - scrollContainer.on("wheel", function(evt) { - if (evt.originalEvent.deltaX === 0) { - // Prevent the scroll event from firing - evt.preventDefault(); - - // Assume this is wheel event which might not trigger - // the scroll event, so do things manually - var sl = scrollContainer.scrollLeft(); - sl += evt.originalEvent.deltaY; - scrollContainer.scrollLeft(sl); - } - }) - scrollLeft = $('
').appendTo(wrapper).find("a"); - scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();}); - scrollRight = $('
').appendTo(wrapper).find("a"); - scrollRight.on('mousedown',function(evt) { scrollEventHandler(evt,evt.shiftKey?('+='+(scrollContainer[0].scrollWidth - scrollContainer.width()-scrollContainer.scrollLeft())):'+=150') }).on('click',function(evt){ evt.preventDefault();}); - } - if (options.collapsible) { // var dropDown = $('
',{class:"red-ui-tabs-select"}).appendTo(wrapper); // ul.hide(); @@ -299,11 +300,12 @@ RED.tabs = (function() { return; } mousedownTab = null; - if (dblClickTime && Date.now()-dblClickTime < 400) { + if (dblClickTime && Date.now()-dblClickTime < 400 && evt.currentTarget === mouseclickTab) { dblClickTime = 0; dblClickArmed = true; return onTabDblClick.call(this,evt); } + mouseclickTab = evt.currentTarget dblClickTime = Date.now(); var currentTab = ul.find("li.red-ui-tab.active"); @@ -382,11 +384,12 @@ RED.tabs = (function() { var scWidth = scrollContainer.width(); var ulWidth = ul.width(); if (sl === 0) { - scrollLeft.hide(); + // We use the parent of the LH button so it doesn't take up space when hidden + scrollLeft.parent().hide(); } else { - scrollLeft.show(); + scrollLeft.parent().show(); } - if (sl === ulWidth-scWidth) { + if (Math.abs(sl - Math.round(ulWidth-scWidth)) < 5) { scrollRight.hide(); } else { scrollRight.show(); @@ -403,7 +406,6 @@ RED.tabs = (function() { } return false; } - function activateTab(link) { if (typeof link === "string") { link = ul.find("a[href='#"+link+"']"); @@ -418,6 +420,7 @@ RED.tabs = (function() { } } if (!link.parent().hasClass("active")) { + updateTabWidths(); ul.children().removeClass("active"); ul.children().css({"transition": "width 100ms"}); link.parent().addClass("active"); @@ -425,6 +428,8 @@ RED.tabs = (function() { wrapper.find(".red-ui-tab-link-button").removeClass("active selected"); $("#"+parentId+"-link-button").addClass("active selected"); if (options.scrollable) { + window.sc = scrollContainer; + window.at = link var pos = link.parent().position().left; if (pos-21 < 0) { scrollContainer.animate( { scrollLeft: '+='+(pos-50) }, 300); @@ -435,7 +440,6 @@ RED.tabs = (function() { if (options.onchange) { options.onchange(tabs[link.attr('href').slice(1)]); } - updateTabWidths(); setTimeout(function() { ul.children().css({"transition": ""}); },100); @@ -467,7 +471,7 @@ RED.tabs = (function() { var allTabs = ul.find("li.red-ui-tab"); var tabs = allTabs.filter(":not(.hide-tab)"); var hiddenTabs = allTabs.filter(".hide-tab"); - var width = wrapper.width(); + var width = options.scrollable ? scrollContainer.width() : wrapper.width(); var tabCount = tabs.length; var tabWidth; @@ -509,20 +513,20 @@ RED.tabs = (function() { tabs.css({width:tabWidth}); } else { - var tabWidth = (width-12-(tabCount*6))/tabCount; + var tabWidth = Math.round((width-12-(tabCount*6))/tabCount); currentTabWidth = (100*tabWidth/width)+"%"; currentActiveTabWidth = currentTabWidth+"%"; if (options.scrollable) { tabWidth = Math.max(tabWidth,140); currentTabWidth = tabWidth+"px"; currentActiveTabWidth = 0; - var listWidth = Math.max(wrapper.width(),12+(tabWidth+6)*tabCount); + var listWidth = Math.max(scrollContainer.width(),12+(tabWidth+6)*tabCount); ul.width(listWidth); updateScroll(); } else if (options.hasOwnProperty("minimumActiveTabWidth")) { if (tabWidth < options.minimumActiveTabWidth) { tabCount -= 1; - tabWidth = (width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount; + tabWidth = Math.round((width-12-options.minimumActiveTabWidth-(tabCount*6))/tabCount); currentTabWidth = (100*tabWidth/width)+"%"; currentActiveTabWidth = options.minimumActiveTabWidth+"px"; } else { @@ -964,7 +968,9 @@ RED.tabs = (function() { activateTab: activateTab, nextTab: activateNextTab, previousTab: activatePreviousTab, - resize: updateTabWidths, + resize: function () { + updateTabWidths() + }, count: function() { return ul.find("li.red-ui-tab:not(.hide)").length; }, diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js index 3e1b9a410..7d825e6c9 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/subflow.js @@ -446,13 +446,13 @@ RED.subflow = (function() { refreshToolbar(activeSubflow); - $("#red-ui-workspace-chart").css({"margin-top": "40px"}); + $("#red-ui-workspace-chart").addClass('red-ui-workspace-toolbar-active'); $("#red-ui-workspace-toolbar").show(); } function hideWorkspaceToolbar() { $("#red-ui-workspace-toolbar").hide().empty(); - $("#red-ui-workspace-chart").css({"margin-top": "0"}); + $("#red-ui-workspace-chart").removeClass('red-ui-workspace-toolbar-active'); } function deleteSubflow(id) { const subflow = RED.nodes.subflow(id || RED.workspaces.active()); diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js index bd22a01d6..e2f0e96b2 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js @@ -129,12 +129,19 @@ RED.view.navigator = (function() { $(window).on("resize", resizeNavBorder); RED.events.on("sidebar:resize",resizeNavBorder); RED.actions.add("core:toggle-navigator",toggle); + + RED.statusBar.add({ + id: "view-navigator", + align: "right", + element: $('') + }) + navContainer = $('
').css({ "position":"absolute", - "bottom":$("#red-ui-workspace-footer").height() + 12, - "right": 16, + "bottom": 25, + "right": 0, zIndex: 1 - }).addClass('red-ui-navigator-container').appendTo("#red-ui-workspace").hide(); + }).addClass('red-ui-navigator-container').appendTo("#red-ui-view-navigator-widget").hide(); navBox = d3.select(navContainer[0]) .append("svg:svg") .attr("width", nav_width) @@ -169,6 +176,7 @@ RED.view.navigator = (function() { navBorder.attr('x',newX).attr('y',newY); $("#red-ui-workspace-chart").scrollLeft(newX*nav_scale*scaleFactor); $("#red-ui-workspace-chart").scrollTop(newY*nav_scale*scaleFactor); + RED.events.emit("view:navigate"); }).on("mouseup", function() { isDragging = false; }).on("mouseenter", function () { @@ -183,11 +191,7 @@ RED.view.navigator = (function() { } }) navBorder = navBox.append("rect").attr("class","red-ui-navigator-border") - RED.statusBar.add({ - id: "view-navigator", - align: "right", - element: $('') - }) + $("#red-ui-view-navigate").on("click", function(evt) { evt.preventDefault(); 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 937dd28de..9009da165 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 @@ -738,6 +738,7 @@ RED.view = (function() { chart.scrollLeft(0); chart.scrollTop(0); } + RED.events.emit("view:navigate"); var scrollDeltaLeft = chart.scrollLeft() - scrollStartLeft; var scrollDeltaTop = chart.scrollTop() - scrollStartTop; if (mouse_position != null) { @@ -824,7 +825,6 @@ RED.view = (function() { if (evt.ctrlKey || evt.altKey || spacebarPressed) { evt.preventDefault(); evt.stopPropagation(); - var currentTime = Date.now(); var timeSinceLastEvent = currentTime - lastWheelEventTime; @@ -845,7 +845,6 @@ RED.view = (function() { var minZoom = calculateMinZoom(); var newScale = Math.min(RED.view.zoomConstants.MAX_ZOOM, Math.max(minZoom, scaleFactor + scaleDelta)); - // Session-based gesture tracking: // - If no active gesture OR gap > gestureEndThreshold, start new gesture // - If gap < wheelEventContinuityThreshold, continue current gesture @@ -869,6 +868,10 @@ RED.view = (function() { var currentScrollPos = [chart.scrollLeft(), chart.scrollTop()]; var focalPoint = RED.view.zoomAnimator.getGestureFocalPoint(currentScrollPos, scaleFactor); zoomView(newScale, focalPoint); // Direct call, no animation + } else { + // At a limit - force a refresh to ensure UI elements are correctly updated + _redraw() + RED.events.emit("view:navigate"); } // Update last event time for continuity tracking @@ -913,6 +916,11 @@ RED.view = (function() { var currentScrollPos = [chart.scrollLeft(), chart.scrollTop()]; var focalPoint = RED.view.zoomAnimator.getGestureFocalPoint(currentScrollPos, scaleFactor); zoomView(newScale, focalPoint); + } else { + // At a limit - force a refresh to ensure UI elements are correctly updated + _redraw() + RED.events.emit("view:navigate"); + } // Update last event time for continuity tracking @@ -2960,6 +2968,8 @@ RED.view = (function() { } animatedZoomView(Math.max(scaleFactor - RED.view.zoomConstants.ZOOM_STEP, minZoom), useFocalPoint, buttonZoomWorkspaceCenter); + } else { + // RED.events.emit("view:navigate"); // Ensure UI updates to reflect zoom limit reached } } function zoomZero() { @@ -3063,6 +3073,7 @@ RED.view = (function() { onStep: function(values) { chart.scrollLeft(values.scrollLeft); chart.scrollTop(values.scrollTop); + RED.events.emit("view:navigate"); }, onStart: function() { RED.events.emit("view:navigate"); @@ -3089,7 +3100,6 @@ RED.view = (function() { factor = 1 } - console.log(factor) var screenSize = [chart.width(),chart.height()]; var scrollPos = [chart.scrollLeft(),chart.scrollTop()]; var oldScaleFactor = scaleFactor; @@ -3144,6 +3154,7 @@ RED.view = (function() { // If we're already at the target, no need to animate // Use a more tolerant threshold to account for floating-point precision if (Math.abs(scaleFactor - targetFactor) < 0.01) { + RED.events.emit("view:navigate"); return; } // Make scale 1 'sticky' @@ -3217,6 +3228,8 @@ RED.view = (function() { eventLayer.attr("transform", "scale(" + scaleFactor + ")"); outer.attr("width", space_width * scaleFactor).attr("height", space_height * scaleFactor); RED.view.navigator.resize(); + _redraw() + RED.events.emit("view:navigate"); }, onStart: function() { // Show minimap when zoom animation starts @@ -3227,15 +3240,18 @@ RED.view = (function() { // Ensure scaleFactor is exactly the target to prevent precision issues scaleFactor = targetFactor; // Full redraw at the end to ensure everything is correct - redraw(); + _redraw(); if (RED.settings.get("editor.view.view-store-zoom")) { RED.settings.setLocal('zoom-level', targetFactor.toFixed(1)); } + RED.events.emit("view:navigate"); }, onCancel: function() { cancelInProgressAnimation = null; // Ensure scaleFactor is set to current target on cancel scaleFactor = targetFactor; + _redraw(); + RED.events.emit("view:navigate"); } }); } @@ -3287,6 +3303,7 @@ RED.view = (function() { // Apply new scroll position chart.scrollLeft(newScrollX); chart.scrollTop(newScrollY); + RED.events.emit("view:navigate"); // Stop if velocity is too small if (Math.abs(scrollVelocity.x) < MIN_VELOCITY && Math.abs(scrollVelocity.y) < MIN_VELOCITY) { @@ -7870,6 +7887,7 @@ RED.view = (function() { if (x !== undefined && y !== undefined) { chart.scrollLeft(chart.scrollLeft()+x); chart.scrollTop(chart.scrollTop()+y) + RED.events.emit("view:navigate"); } else { return [chart.scrollLeft(), chart.scrollTop()] } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 78e1399cd..38b820325 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -497,17 +497,116 @@ RED.workspaces = (function() { $("#red-ui-workspace-footer").children().hide() } + const scrollbars = {} + function updateScrollbars() { + const scaleFactor = RED.view.scale(); + const chartWindowSize = [ $("#red-ui-workspace-chart").width(), $("#red-ui-workspace-chart").height()]; + const chartSize = [ $("#red-ui-workspace-scroll-spacer").width(), $("#red-ui-workspace-scroll-spacer").height()]; + const scrollPos = [$("#red-ui-workspace-chart").scrollLeft(), $("#red-ui-workspace-chart").scrollTop()]; + const scrollRatio = [scrollPos[0]/(chartSize[0] - chartWindowSize[0]), scrollPos[1]/(chartSize[1] - chartWindowSize[1]) ]; + const scrollbarSize = [scrollbars.h.bar.width(), scrollbars.v.bar.height()] + // Set the height of the handles to be the same ratio of chartWindowSize to chartSize, with a minimum size to ensure they are always draggable + + scrollbars.v.handle.height(Math.max(40, scrollbarSize[1] * chartWindowSize[1] / chartSize[1])) + scrollbars.h.handle.width(Math.max(40, scrollbarSize[0] * chartWindowSize[0] / chartSize[0])) + if (isNaN(scrollRatio[0])) { + scrollbars.h.bar.hide() + } else { + scrollbars.h.bar.show() + const sbhWidth = scrollbars.h.bar.width() - scrollbars.h.handle.width() + scrollbars.h.handle.css({ left: sbhWidth * scrollRatio[0] }) + } + if (isNaN(scrollRatio[1])) { + scrollbars.v.bar.hide() + } else { + scrollbars.v.bar.show() + const sbvHeight = scrollbars.v.bar.height() - scrollbars.v.handle.height() + scrollbars.v.handle.css({ top: sbvHeight * scrollRatio[1] }) + } + } + + function setupScrollbar(scrollbar, direction) { + // direction: 'h' | 'v' + let isDragging = false; + let dragStartPos = 0; + let handleStartPos = 0; + function cancelScroll () { + isDragging = false; + $(document).off('mousemove.red-ui-workspace-scrollbar'); + $(document).off('mouseup.red-ui-workspace-scrollbar'); + } + // Update the following event handlers to also handle touch events + scrollbar.handle.on('mousedown', function(evt) { + isDragging = true; + dragStartPos = (direction === 'h' ? evt.pageX : evt.pageY); + handleStartPos = parseInt(scrollbar.handle.css(direction === 'h' ? 'left' : 'top')) || 0; + evt.preventDefault(); + $(document).on('mousemove.red-ui-workspace-scrollbar', function(evt) { + if (isDragging) { + const delta = (direction === 'h' ? evt.pageX : evt.pageY) - dragStartPos; + const newHandlePos = handleStartPos + delta; + const barSize = (direction === 'h' ? scrollbar.bar.width() : scrollbar.bar.height()) - (direction === 'h' ? scrollbar.handle.width() : scrollbar.handle.height()); + const clampedHandlePos = Math.max(0, Math.min(newHandlePos, barSize)); + const scrollRatio = clampedHandlePos / barSize; + const chartWindowSize = [ $("#red-ui-workspace-chart").width(), $("#red-ui-workspace-chart").height()]; + const chartSize = [ $("#red-ui-workspace-scroll-spacer").width(), $("#red-ui-workspace-scroll-spacer").height()]; + if (direction === 'h') { + const newScrollLeft = scrollRatio * (chartSize[0] - chartWindowSize[0]); + $("#red-ui-workspace-chart").scrollLeft(newScrollLeft); + } else { + const newScrollTop = scrollRatio * (chartSize[1] - chartWindowSize[1]); + $("#red-ui-workspace-chart").scrollTop(newScrollTop); + } + updateScrollbars() + } else { + $(document).off('mousemove.red-ui-workspace-scrollbar'); + } + }) + $(document).on('mouseup.red-ui-workspace-scrollbar', function(evt) { + cancelScroll() + }) + }); + } + function init() { - $('
    ').appendTo("#red-ui-workspace"); - $('
    ').appendTo("#red-ui-workspace"); + $('
      ').appendTo("#red-ui-header-tabs"); + $('
      ').appendTo("#red-ui-header-tabs"); $('
      ').appendTo("#red-ui-workspace"); $('
      ').appendTo("#red-ui-workspace"); $('').appendTo("#red-ui-workspace"); + + scrollbars.v = { bar: $('
      ').appendTo("#red-ui-workspace") } + scrollbars.v.handle = scrollbars.v.bar.children().first(); + setupScrollbar(scrollbars.v, 'v') + scrollbars.h = { bar: $('
      ').appendTo("#red-ui-workspace") } + scrollbars.h.handle = scrollbars.h.bar.children().first(); + setupScrollbar(scrollbars.h, 'h') + $('
      ').appendTo("#red-ui-workspace"); - createWorkspaceTabs(); - RED.events.on("sidebar:resize",workspace_tabs.resize); + RED.events.on("view:navigate", function () { + updateScrollbars() + }) + RED.events.on("sidebar:resize",function () { + workspace_tabs.resize(); + let sidebarWidth = $("#red-ui-sidebar-container").width() + const workspaceTargetWidth = $("#red-ui-workspace").width() - sidebarWidth - 10 + // $("#red-ui-workspace-toolbar").width(workspaceTargetWidth) + $("#red-ui-workspace-footer").width(workspaceTargetWidth) + $("#red-ui-workspace-scroll-v").css({ right: sidebarWidth + 2}) + $("#red-ui-workspace-scroll-h").css({ width: workspaceTargetWidth - 4 }) + + // const workspacePosition = $("#red-ui-workspace").position() + // $("#red-ui-header-tabs").css({ left: workspacePosition.left, width: workspaceTargetWidth }) + updateScrollbars() + }); + + RED.events.on("workspace:change", function(event) { + setTimeout(() => { + updateScrollbars() + }, 100) + }); RED.events.on("workspace:clear", () => { // Reset the index used to generate new flow names @@ -534,7 +633,8 @@ RED.workspaces = (function() { }); $(window).on("resize", function() { - workspace_tabs.resize(); + // workspace_tabs.resize(); + updateScrollbars() }); if (RED.settings.theme("menu.menu-item-workspace-add", true)) { RED.actions.add("core:add-flow",function(opts) { addWorkspace(undefined,undefined,opts?opts.index:undefined)}); diff --git a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss index dd3444b67..38af32426 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/colors.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/colors.scss @@ -248,6 +248,8 @@ $clipboard-textarea-background: #F3E7E7; $header-background: $primary-background; $header-button-border: $primary-border-color; +$header-button-background: $header-background; +$header-button-background-hover: #ddd; $header-button-background-active: $workspace-button-background-active; $header-accent: $primary-background; diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 0a0693aba..90c119926 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -50,7 +50,7 @@ background: var(--red-ui-secondary-background); border: 1px solid var(--red-ui-primary-border-color); overflow: hidden; - box-shadow: -2px 0 6px var(--red-ui-shadow); + // box-shadow: -2px 0 6px var(--red-ui-shadow); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index 171033756..dbba356cf 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -34,10 +34,9 @@ display: flex; justify-content: space-between; align-items: center; - padding-top: 6px; span.red-ui-header-logo { - float: left; + min-width: 235px; margin-left: 8px; text-decoration: none; white-space: nowrap; @@ -63,12 +62,15 @@ } .red-ui-header-toolbar { - display: flex; + flex: 1 0 auto; + &:not(.hide) { + display: flex; + } align-items: stretch; padding: 0; - margin: 0 10px 0 0; + margin: 0 10px 0 20px; list-style: none; - gap: 15px; + gap: 10px; > li { display: inline-flex; @@ -80,26 +82,27 @@ } .button { - height: 24px; + height: 30px; display: inline-flex; align-items: center; justify-content: center; - min-width: 24px; + min-width: 32px; text-align: center; font-size: 16px; padding: 0px; text-decoration: none; color: var(--red-ui-header-menu-color); - margin: auto 0; vertical-align: middle; mask-size: contain; + border-radius: 4px; + box-sizing: border-box; &:active, &.active { background: var(--red-ui-header-button-background-active); } - &:focus { - outline: none; - } + &:hover { + background: var(--red-ui-header-button-background-hover); + } } .button-group { @@ -109,7 +112,6 @@ & > a { display: inline-block; position: relative; - float: left; line-height: 22px; font-size: 14px; text-decoration: none; @@ -303,11 +305,13 @@ } } - -.red-ui-user-profile { +#red-ui-header-button-user { background-color: var(--red-ui-header-background); border: 1px solid var(--red-ui-header-button-border); border-radius: 4px; +} + +.red-ui-user-profile { overflow: hidden; padding: 3px; background-position: center center; 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 822bf6438..ce63461d0 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,16 +16,20 @@ * limitations under the License. **/ + #red-ui-sidebar-container { + position: absolute; + top: 0; + bottom: 0; + right: 0; + display: flex; + flex-direction: row; + } .red-ui-sidebar { position: relative; flex-grow: 0; flex-shrink: 0; width: 315px; margin: 4px 0; - @include mixins.component-border; - border-left: none; - border-right: none; - background: var(--red-ui-secondary-background); box-sizing: border-box; z-index: 12; display: flex; @@ -61,6 +65,7 @@ border-radius: 6px; border: 1px solid var(--red-ui-secondary-border-color); overflow: hidden; + &:first-child { margin-top: 0; } &.red-ui-sidebar-section-bottom { flex-grow: 1; flex-shrink: 1; @@ -127,31 +132,27 @@ } .red-ui-sidebar-tab-bar { - background-color: var(--red-ui-secondary-background); flex: 0 0 auto; display: flex; flex-direction: column; align-items: center; - margin: 4px; - @include mixins.component-border; + margin: 0 4px 4px; z-index: 12; overflow: hidden; - // border: 1px solid var(--red-ui-primary-border-color); &.red-ui-sidebar-left { z-index: 10; - border: none; margin-right: 0; margin-left: 0; background: var(--red-ui-primary-background); } &.red-ui-sidebar-right { - border-top-right-radius: 8px; - border-bottom-right-radius: 8px; margin-left: 0; // Account for the RH sidebar having an extra top margin padding-top: 4px; - border-left: none; + margin-right: 0; + margin-bottom: 0; + border-bottom: none; } button { @@ -165,7 +166,6 @@ height: 22px; width: 22px; &:not(.selected):not(:hover) { - border: none; i { opacity: 0.7; } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/sizes.scss b/packages/node_modules/@node-red/editor-client/src/sass/sizes.scss index ecefc0294..449819d45 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/sizes.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/sizes.scss @@ -14,4 +14,4 @@ * limitations under the License. **/ - $header-height: 36px; \ No newline at end of file + $header-height: 40px; \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index 3b7d655d1..4c7dbc125 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -230,7 +230,6 @@ left: 0; right: 0; opacity: 0.4; - background: red; } } .red-ui-tab-button { @@ -284,9 +283,8 @@ width: 21px; top: 0; a { - height: 35px; + // height: 35px; width: 21px; - display: block; color: var(--red-ui-workspace-button-color); font-size: 22px; text-align: center; @@ -295,7 +293,6 @@ border-right: none; border-top: none; border-bottom: 1px solid var(--red-ui-primary-border-color); - line-height: 34px; } } .red-ui-tab-scroll-left { @@ -435,19 +432,74 @@ i.red-ui-tab-icon { } } -ul#red-ui-workspace-tabs { - border-color: var(--red-ui-secondary-border-color); - li { - border-color: var(--red-ui-secondary-border-color); - border-top-left-radius: 4px; - border-top-right-radius: 4px; +#red-ui-header-tabs { + flex: 1 1 100%; + + .red-ui-tabs { + background: var(--red-ui-header-background); + border: none; + display: flex; + padding: 0; + .red-ui-tabs-scroll-container { + min-width:0; + width: 0; + flex: 1 1 0; + } + .red-ui-tab-button { + position: static; + background: var(--red-ui-header-background); + border: var(--red-ui-header-button-border); + a { + background: var(--red-ui-header-background); + border: var(--red-ui-header-button-border); + &:hover { + background: var(--red-ui-header-button-background-hover); + } + } + } + .red-ui-tab-button.red-ui-tab-scroll { + background: none; + border: none; + z-index:10; + border-radius: 0; + } + .red-ui-tab-button.red-ui-tab-scroll a { + border: none; + background: none; + border-radius: 0; + box-shadow: 0 0 8px rgba(0,0,0,0.3); + height: 100%; + display: flex; + align-items: center; + justify-content: center; + } + .red-ui-tab-button.red-ui-tab-scroll.red-ui-tab-scroll-left a { + border: none; + clip-path: inset(0 -8px 0 0); + } + .red-ui-tab-button.red-ui-tab-scroll.red-ui-tab-scroll-right a { + border: none; + clip-path: inset(0 0 0 -8px); + } + } + ul { + display: flex; + align-items: center; + border: none; + li { + min-width: 60px; + max-width: 150px; + flex: 1 1 100%; + border-color: var(--red-ui-header-button-border); + border-radius: 4px; + margin-top: 0; + } } } -#red-ui-workspace > .red-ui-tabs > .red-ui-tab-button { - border-color: var(--red-ui-secondary-border-color); +#red-ui-header-tabs > .red-ui-tabs > .red-ui-tab-button { + border-color: var(--red-ui-header-button-border); } -#red-ui-workspace > .red-ui-tabs > .red-ui-tab-scroll a { - border-color: var(--red-ui-secondary-border-color); - border-radius: 0; +#red-ui-header-tabs > .red-ui-tabs > .red-ui-tab-scroll a { + border-color: var(--red-ui-header-button-border); } diff --git a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss index fca7d05db..4e9459add 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/variables.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/variables.scss @@ -249,7 +249,10 @@ --red-ui-header-background: #{colors.$header-background}; --red-ui-header-accent: #{colors.$header-accent}; + --red-ui-header-button-background: #{colors.$header-background}; + --red-ui-header-button-background-hover: #{colors.$header-button-background-hover}; --red-ui-header-button-background-active: #{colors.$header-button-background-active}; + --red-ui-header-button-border: #{colors.$header-button-border}; --red-ui-header-menu-color: #{colors.$header-menu-color}; --red-ui-header-menu-color-disabled: #{colors.$header-menu-color-disabled}; 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 6391292db..69b1e7760 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 @@ -21,36 +21,36 @@ overflow: hidden; @include mixins.component-border; border-top-left-radius: 8px; - border-bottom-left-radius: 8px; border-right: none; - margin: 4px 0 4px; + border-bottom: none; transition: left 0.1s ease-in-out; position: relative; flex-grow: 1; + .red-ui-workspace-toolbar-active { + top: 40px; + } } #red-ui-workspace-chart { - overflow: auto; - position: absolute; - bottom:0; - top: 35px; - left:0px; - right:0px; - box-sizing:border-box; - transition: right 0.2s ease; - touch-action: none; - padding: 0; - margin: 0; - border-right: 1px solid var(--red-ui-secondary-border-color); -// border-top-right-radius: px; + overflow: auto; + position: absolute; + bottom:0; + top: 0px; + left:0px; + right:0px; + box-sizing:border-box; + transition: right 0.2s ease; + touch-action: none; + padding: 0; + margin: 0; -// // Hide scrollbars -// scrollbar-width: none; /* Firefox */ -// -ms-overflow-style: none; /* Internet Explorer 10+ */ -// &::-webkit-scrollbar { /* WebKit */ -// width: 0; -// height: 0; -// } + // Hide scrollbars + scrollbar-width: none; /* Firefox */ + -ms-overflow-style: none; /* Internet Explorer 10+ */ + &::-webkit-scrollbar { /* WebKit */ + width: 0; + height: 0; + } // Reset SVG default margins > svg { @@ -88,9 +88,9 @@ } } -#red-ui-workspace-tabs:not(.red-ui-workspace-focussed) { - opacity:0.8; -} +// #red-ui-workspace-tabs:not(.red-ui-workspace-focussed) { +// opacity:0.8; +// } .red-ui-workspace-disabled-icon { display: none; } @@ -179,7 +179,7 @@ border: none; background: none; bottom: 14px; - right: 12px; + right: 200px; padding: 0; } .red-ui-component-footer { @@ -277,3 +277,57 @@ button.red-ui-footer-button-toggle { font-size: 13px; margin-bottom: 2px; } +:root { + --red-ui-scrollbar-width: 12px; + --red-ui-scrollbar-handle-size: 40px; + --red-ui-scrollbar-handle-background: rgb(128,128,128); +} + +.red-ui-workspace-scrollbar { + position: absolute; +} +.red-ui-workspace-scrollbar-handle { + position: absolute; + background: var(--red-ui-scrollbar-handle-background); + opacity: 0.7; + border-radius: 4px; + box-sizing: border-box; + border: 1px solid rgba(255,255,255,1); + cursor: pointer; + overflow: visible; + &:hover { + opacity: 1; + } + .red-ui-workspace-scrollbar-handle-target { + position: absolute; + top: -5px; + left: -5px; + right: -5px; + bottom: -5px; + } +} +#red-ui-workspace-scroll-v { + top: 2px; + bottom: 14px; + right: 0; + width: var(--red-ui-scrollbar-width); + .red-ui-workspace-scrollbar-handle { + top: 0; + left: 2px; + width: 8px; + height: var(--red-ui-scrollbar-handle-size); + } +} +#red-ui-workspace-scroll-h { + left: 2px; + right: 0; + bottom: 2px; + height: var(--red-ui-scrollbar-width); + .red-ui-workspace-scrollbar-handle { + left: 0; + top: 2px; + height: 8px; + width: var(--red-ui-scrollbar-handle-size); + } + // background: var(--red-ui-scrollbar-background); +} \ No newline at end of file diff --git a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss index 7bc79f35a..c72fc2b0c 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/workspaceToolbar.scss @@ -23,7 +23,7 @@ font-size: 12px; line-height: 18px; position: absolute; - top: 35px; + top: 0; left:0; right: 0; padding: 7px; @@ -53,6 +53,15 @@ .button-group { @include mixins.disable-selection; + .button:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + .button:not(:last-child) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .button:first-child { margin-right: 0; } From c3915a6c4d92bc2a485b73d596581f641a8bec49 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 24 Feb 2026 12:47:21 +0000 Subject: [PATCH 2/8] Fix lint --- packages/node_modules/@node-red/editor-client/src/js/red.js | 4 ++-- 1 file changed, 2 insertions(+), 2 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 15227c7ca..902662ac7 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 @@ -538,7 +538,7 @@ var RED = (function() { node.dirty = true; RED.view.redrawStatus(node); } - }); + }) RED.comms.subscribe("notification/plugin/#",function(topic,msg) { if (topic == "notification/plugin/added") { RED.settings.refreshSettings(function(err, data) { @@ -903,7 +903,7 @@ var RED = (function() { function buildEditor(options) { const header = $('
      ').appendTo(options.target); - const logo = $('').appendTo(header); + let logo = $('').appendTo(header); $('
      ').appendTo(header); $('
        ').appendTo(header); $('
        ').appendTo(header); From 6a74428edd4740e4b8a164848f065aa4f579b194 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 25 Feb 2026 09:58:46 +0000 Subject: [PATCH 3/8] Various CSS tidy-ups --- .../editor-client/src/js/ui/tab-info.js | 31 ++++++----------- .../editor-client/src/sass/mixins.scss | 2 +- .../editor-client/src/sass/palette.scss | 1 - .../editor-client/src/sass/sidebar.scss | 34 +++++++++++++------ .../editor-client/src/sass/tab-info.scss | 24 ++++--------- .../editor-client/src/sass/workspace.scss | 2 +- .../core/common/lib/debug/debug-utils.js | 6 ++-- 7 files changed, 45 insertions(+), 55 deletions(-) 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 1daf8abc1..6fd3ef682 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 @@ -58,47 +58,36 @@ RED.sidebar.info = (function() { "display": "flex", "flex-direction": "column" }).appendTo(stackContainer); - propertiesPanelHeader = $("
        ", {class:"red-ui-palette-header red-ui-info-header"}).css({ + propertiesPanelHeader = $("
        ", {class:"red-ui-sidebar-header"}).css({ "flex":"0 0 auto" }).appendTo(propertiesPanel); - propertiesPanelHeaderIcon = $("").appendTo(propertiesPanelHeader); - propertiesPanelHeaderLabel = $("").appendTo(propertiesPanelHeader); + propertiesPanelHeaderIcon = $('').appendTo(propertiesPanelHeader); + propertiesPanelHeaderLabel = $('').appendTo(propertiesPanelHeader); - propertiesPanelHeaderCopyLink = $('').css({ - position: 'absolute', - top: '6px', - right: '32px' - }).on("click", function(evt) { + const buttons = $('').appendTo(propertiesPanelHeader); + propertiesPanelHeaderCopyLink = $('').on("click", function(evt) { RED.actions.invoke('core:copy-item-url',selectedObject) - }).appendTo(propertiesPanelHeader); + }).appendTo(buttons); RED.popover.tooltip(propertiesPanelHeaderCopyLink,RED._("sidebar.info.copyItemUrl")); - propertiesPanelHeaderHelp = $('').css({ - position: 'absolute', - top: '6px', - right: '56px' - }).on("click", function(evt) { + propertiesPanelHeaderHelp = $('').on("click", function(evt) { evt.preventDefault(); evt.stopPropagation(); if (selectedObject) { RED.sidebar.help.show(selectedObject.type); } - }).appendTo(propertiesPanelHeader); + }).appendTo(buttons); RED.popover.tooltip(propertiesPanelHeaderHelp,RED._("sidebar.help.showHelp")); - propertiesPanelHeaderReveal = $('').css({ - position: 'absolute', - top: '6px', - right: '8px' - }).on("click", function(evt) { + propertiesPanelHeaderReveal = $('').on("click", function(evt) { evt.preventDefault(); evt.stopPropagation(); if (selectedObject) { RED.sidebar.info.outliner.reveal(selectedObject); RED.view.reveal(selectedObject.id); } - }).appendTo(propertiesPanelHeader); + }).appendTo(buttons); RED.popover.tooltip(propertiesPanelHeaderReveal,RED._("sidebar.help.showInOutline")); 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 28eeba3c2..8bdcdc1fc 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 @@ -140,7 +140,7 @@ vertical-align: middle; } .button-group:not(:last-child) { - margin-right: 10px; + margin-right: 4px; } 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 f53a36db9..864f6225e 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 @@ -263,7 +263,6 @@ width: 24px; height: 20px; line-height: 20px; - margin-top: 1px; // width: 30px; // height: 25px; border-radius: 3px; 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 ce63461d0..491e4a181 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 @@ -63,7 +63,7 @@ flex-grow: 0; flex-shrink: 0; border-radius: 6px; - border: 1px solid var(--red-ui-secondary-border-color); + border: 1px solid var(--red-ui-primary-border-color); overflow: hidden; &:first-child { margin-top: 0; } &.red-ui-sidebar-section-bottom { @@ -73,10 +73,9 @@ } .red-ui-sidebar-left .red-ui-sidebar-section { margin-left: 0; - border-color: var(--red-ui-primary-border-color); } .red-ui-sidebar-right .red-ui-sidebar-section { - margin-right: 0 + margin-right: 0; } @@ -215,14 +214,27 @@ padding: 2px 8px; } +.red-ui-sidebar-banner { /* Currently unused... */ + background: var(--red-ui-primary-background); + color: var(--red-ui-primary-text-color); + font-size: 8px; + padding: 0 3px; + text-align: right; + user-select: none; + cursor: grab; +} .sidebar-header, /* Deprecated -> red-ui-sidebar-header */ .red-ui-sidebar-header { + font-size: 13px; color: var(--red-ui-primary-text-color); - text-align: right; - padding: 8px 10px; + padding: 4px; background: var(--red-ui-primary-background); border-bottom: 1px solid var(--red-ui-secondary-border-color); white-space: nowrap; + display: flex; + justify-content: end; + align-items: center; + gap: 3px; } /* Deprecated -> red-ui-footer-button */ @@ -239,9 +251,9 @@ button.sidebar-header-button, /* Deprecated -> red-ui-sidebar-header-button */ a.red-ui-sidebar-header-button, button.red-ui-sidebar-header-button { @include mixins.workspace-button; - font-size: 13px; - line-height: 13px; - padding: 5px 8px; + font-size: 11px; + line-height: 11px; + padding: 3px 5px; &.toggle { @include mixins.workspace-button-toggle; } @@ -252,9 +264,9 @@ button.sidebar-header-button-toggle, /* Deprecated -> red-ui-sidebar-header-butt a.red-ui-sidebar-header-button-toggle, button.red-ui-sidebar-header-button-toggle { @include mixins.workspace-button-toggle; - font-size: 13px; - line-height: 13px; - padding: 5px 8px; + font-size: 11px; + line-height: 11px; + padding: 3px 5px; } .sidebar-header-button:not(:first-child), /* Deprecated -> red-ui-sidebar-header-button */ 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 5af00579f..8cbf8b0d3 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 @@ -23,27 +23,17 @@ .red-ui-sidebar-info hr { margin: 10px 0; } -.red-ui-info-header { - padding-left: 9px; - line-height: 21px; - cursor: default; - border-bottom: 1px solid var(--red-ui-secondary-border-color); - > * { - vertical-align: middle - } - > span { - display: inline-block; - margin-left: 5px; - overflow-wrap: anywhere; - } -} + table.red-ui-info-table { - font-size: 14px; + font-size: 13px; margin: 0 0 10px; width: 100%; } table.red-ui-info-table tr:not(.blank) { - border-top: 1px solid var(--red-ui-secondary-border-color); + &:not(:first-child) { + border-top: 1px solid var(--red-ui-secondary-border-color); + } + line-height: 23px; border-bottom: 1px solid var(--red-ui-secondary-border-color); } .red-ui-help-property-expand { @@ -360,7 +350,7 @@ div.red-ui-info-table { .red-ui-info-outline-item { display: inline-flex; padding: 0; - font-size: 13px; + font-size: 12px; border: none; &:not(.red-ui-node-list-item) .red-ui-palette-icon-fa { position: relative; 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 69b1e7760..efc7cfb9c 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,7 +20,7 @@ margin: 0; overflow: hidden; @include mixins.component-border; - border-top-left-radius: 8px; + border-top-left-radius: 6px; border-right: none; border-bottom: none; transition: left 0.1s ease-in-out; diff --git a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js index 20a62ea5c..fe99395f4 100644 --- a/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js +++ b/packages/node_modules/@node-red/nodes/core/common/lib/debug/debug-utils.js @@ -45,11 +45,11 @@ RED.debug = (function() { ''+ ''+ ''+ - ' '+ + ' '+ ''+ ''+ - ' all' + - ''+ + ' all' + + ''+ '
        ').appendTo(content); var footerToolbar = $('
        '+ From 83d79ef58a679cfa037e141c46d1310ca9a9286e Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Mon, 2 Mar 2026 17:19:12 +0000 Subject: [PATCH 4/8] Move sidebar buttons to the bottom --- .../@node-red/editor-client/src/js/red.js | 3 +- .../editor-client/src/js/ui/sidebar.js | 36 +++++--- .../editor-client/src/js/ui/statusBar.js | 11 +-- .../@node-red/editor-client/src/js/ui/tray.js | 3 +- .../editor-client/src/js/ui/workspaces.js | 11 ++- .../editor-client/src/sass/header.scss | 9 +- .../editor-client/src/sass/sidebar.scss | 83 ++++++++++--------- .../editor-client/src/sass/tabs.scss | 4 +- .../editor-client/src/sass/workspace.scss | 43 +++++++--- 9 files changed, 126 insertions(+), 77 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 902662ac7..bbdf41765 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 @@ -903,7 +903,8 @@ var RED = (function() { function buildEditor(options) { const header = $('
        ').appendTo(options.target); - let logo = $('').appendTo(header); + const logoContainer = $('').appendTo(header); + let logo = $('').appendTo(logoContainer); $('
        ').appendTo(header); $('
          ').appendTo(header); $('
          ').appendTo(header); 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 a1f73e13d..959cc110c 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 @@ -255,9 +255,9 @@ RED.sidebar = (function() { function setupSidebarTabs(sidebar) { const tabBar = $('
          ').addClass('red-ui-sidebar-' + sidebar.direction); if (sidebar.direction === 'right') { - tabBar.insertAfter(sidebar.container); + tabBar.appendTo("#red-ui-workspace-footer"); } else if (sidebar.direction === 'left') { - tabBar.insertBefore(sidebar.container); + tabBar.prependTo("#red-ui-workspace-footer"); } // TODO: make this an API object, not just a jQuery object @@ -265,7 +265,7 @@ RED.sidebar = (function() { top: setupTabSection(sidebar, tabBar, 'top'), bottom: setupTabSection(sidebar, tabBar, 'bottom') } - sidebar.tabBar = sidebar.tabBars.top.container; + sidebar.tabBar = tabBar // sidebar.tabBars.top.container; sidebar.resizeSidebarTabBar = function () { sidebar.tabBars.top.resizeSidebarTabBar(); sidebar.tabBars.bottom.resizeSidebarTabBar(); @@ -404,6 +404,7 @@ RED.sidebar = (function() { let hasHidden = false const resizeSidebarTabBar = function () { + return; let tabBarButtonsBottom = tabBarButtonsContainer.position().top + tabBarButtonsContainer.outerHeight(); const buttonHeight = tabOverflowButton.outerHeight() // Find the last visible button @@ -526,8 +527,7 @@ RED.sidebar = (function() { } sidebar.container.width(newSidebarWidth); ui.position.left -= scaleFactor * d - - // sidebar.tabs.resize(); + sidebar.tabBar.css('min-width', sidebar.container.width() - 5) RED.events.emit("sidebar:resize"); }, stop:function(event,ui) { @@ -537,12 +537,14 @@ RED.sidebar = (function() { if (sidebar.menuToggle) { RED.menu.setSelected(sidebar.menuToggle,false); } - sidebar.container.hide() + sidebar.sections.top.container.hide() + sidebar.sections.bottom.container.hide() sidebar.separator.hide() - if (sidebar.container.width() < sidebar.minimumWidth) { - sidebar.container.width(sidebar.defaultWidth); - } + // if (sidebar.container.width() < sidebar.minimumWidth) { + // sidebar.container.width(sidebar.defaultWidth); + // } } + sidebar.tabBar.css('min-width', sidebar.container.width() - 5) RED.events.emit("sidebar:resize"); } }); @@ -551,12 +553,19 @@ RED.sidebar = (function() { function toggleSidebar(sidebar, state) { if (!state) { - sidebar.container.hide() + // sidebar.container.hide() sidebar.separator.hide() + sidebar.sections.top.container.hide() + sidebar.sections.bottom.container.hide() + sidebar.container.width(0) sidebar.tabBars.top.clearSelected() sidebar.tabBars.bottom.clearSelected() } else { - sidebar.container.show() + // sidebar.container.show() + sidebar.sections.top.container.show() + sidebar.sections.bottom.container.show() + sidebar.container.width(200) + sidebar.tabBar.css('min-width', sidebar.container.width() - 5) sidebar.separator.show() if (sidebar.tabBars.top.active) { sidebar.tabBars.top.container.find('button[data-tab-id="'+sidebar.tabBars.top.active+'"]').addClass('selected') @@ -666,7 +675,7 @@ RED.sidebar = (function() { return } sidebar.sections.top.container.outerHeight(startTopSectionHeight + delta) - sidebar.tabBars.top.container.outerHeight(startTopTabSectionHeight + delta) + // sidebar.tabBars.top.container.outerHeight(startTopTabSectionHeight + delta) ui.position.top -= delta lastSeparatorPosition = ui.position.top sidebar.resizeSidebar() @@ -689,12 +698,13 @@ RED.sidebar = (function() { if (bottomSectionHeight < 90 && topSectionHeight > 90) { sidebar.sections.top.container.outerHeight(topSectionHeight - (90 - bottomSectionHeight)); } - sidebar.tabBars.top.container.height(sidebar.sections.top.container.outerHeight()) + // sidebar.tabBars.top.container.height(sidebar.sections.top.container.outerHeight()) // } else { // sidebar.tabBars.top.container.height(sidebar.sections.top.container.outerHeight() - 60) } // Trigger a resize of the tab bars to handle overflow sidebar.resizeSidebarTabBar() + sidebar.tabBar.css('min-width', sidebar.container.width() - 5) RED.events.emit("sidebar:resize"); } diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js b/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js index 89682fb07..75d42c74c 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/statusBar.js @@ -24,9 +24,9 @@ RED.statusBar = (function() { - var widgets = {}; - var leftBucket; - var rightBucket; + const widgets = {}; + let leftBucket; + let rightBucket; function addWidget(options) { widgets[options.id] = options; @@ -59,8 +59,9 @@ RED.statusBar = (function() { return { init: function() { - leftBucket = $('').appendTo("#red-ui-workspace-footer"); - rightBucket = $('').appendTo("#red-ui-workspace-footer"); + const widgetBar = $('
          ').appendTo("#red-ui-workspace-footer") + leftBucket = $('').appendTo(widgetBar); + rightBucket = $('').appendTo(widgetBar); }, add: addWidget, hide: hideWidget, 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 224bfdb4e..e44c9c567 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,7 +194,8 @@ function handleWindowResize() { let sidebarWidth = $("#red-ui-sidebar").is(":visible") ? $("#red-ui-sidebar").outerWidth() : 0; - $("#red-ui-editor-stack").css('right', sidebarWidth + $("#red-ui-sidebar + .red-ui-sidebar-tab-bar").outerWidth() + 4); + console.log('tray.handleWindowResize', sidebarWidth) + $("#red-ui-editor-stack").css('right', sidebarWidth + 4); 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/workspaces.js b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js index 38b820325..b0d653a0b 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/workspaces.js @@ -573,7 +573,7 @@ RED.workspaces = (function() { $('
          ').appendTo("#red-ui-header-tabs"); $('
          ').appendTo("#red-ui-workspace"); $('
          ').appendTo("#red-ui-workspace"); - $('').appendTo("#red-ui-workspace"); + $('').insertAfter("#red-ui-workspace"); scrollbars.v = { bar: $('
          ').appendTo("#red-ui-workspace") } scrollbars.v.handle = scrollbars.v.bar.children().first(); @@ -591,11 +591,14 @@ RED.workspaces = (function() { RED.events.on("sidebar:resize",function () { workspace_tabs.resize(); let sidebarWidth = $("#red-ui-sidebar-container").width() - const workspaceTargetWidth = $("#red-ui-workspace").width() - sidebarWidth - 10 + const workspaceTargetWidth = $("#red-ui-workspace").width() - sidebarWidth // $("#red-ui-workspace-toolbar").width(workspaceTargetWidth) - $("#red-ui-workspace-footer").width(workspaceTargetWidth) + // $("#red-ui-workspace-footer").width(workspaceTargetWidth) $("#red-ui-workspace-scroll-v").css({ right: sidebarWidth + 2}) - $("#red-ui-workspace-scroll-h").css({ width: workspaceTargetWidth - 4 }) + $("#red-ui-workspace-scroll-h").css({ width: workspaceTargetWidth - 15 }) + + const paletteWidth = $("#red-ui-sidebar-left").width() + $("#red-ui-header-logo").width(paletteWidth - 5) // const workspacePosition = $("#red-ui-workspace").position() // $("#red-ui-header-tabs").css({ left: workspacePosition.left, width: workspaceTargetWidth }) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/header.scss b/packages/node_modules/@node-red/editor-client/src/sass/header.scss index dbba356cf..f27be4d98 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/header.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/header.scss @@ -35,9 +35,14 @@ justify-content: space-between; align-items: center; - span.red-ui-header-logo { - min-width: 235px; + #red-ui-header-logo { + flex: 0 0 auto; + display: flex; + align-items: center; margin-left: 8px; + min-width: 170px; + } + span.red-ui-header-logo { text-decoration: none; white-space: nowrap; 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 491e4a181..d28ab75d5 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,7 @@ flex-grow: 0; flex-shrink: 0; width: 315px; - margin: 4px 0; + margin: 4px 0 40px 0; box-sizing: border-box; z-index: 12; display: flex; @@ -38,12 +38,13 @@ } .red-ui-sidebar-left { background: var(--red-ui-primary-background); - margin: 0; + margin-top: 0px; + margin-left: 5px; border: none; z-index: 10; } .red-ui-sidebar-right { - margin-left: 0; + margin-right: 5px; } .red-ui-sidebar-footer { @@ -55,17 +56,17 @@ } .red-ui-sidebar-section { - margin: 4px; + margin: 8px 4px 0 4px; display: flex; flex-direction: column; min-height: 80px; - background: rgba(0,0,255,0.2); + // background: rgba(0,0,255,0.2); flex-grow: 0; flex-shrink: 0; border-radius: 6px; border: 1px solid var(--red-ui-primary-border-color); overflow: hidden; - &:first-child { margin-top: 0; } + &.red-ui-sidebar-section-top { margin-top: 4px; } &.red-ui-sidebar-section-bottom { flex-grow: 1; flex-shrink: 1; @@ -73,6 +74,7 @@ } .red-ui-sidebar-left .red-ui-sidebar-section { margin-left: 0; + &.red-ui-sidebar-section-top { margin-top: 0; } } .red-ui-sidebar-right .red-ui-sidebar-section { margin-right: 0; @@ -103,11 +105,10 @@ .red-ui-sidebar-separator-handle { position: absolute; - // background: rgba(255,140,0,0.2); top: 0; left: -6px; width: 12px; - height: 100%; + height: calc(100% - 40px);; z-index: 20; } } @@ -131,27 +132,24 @@ } .red-ui-sidebar-tab-bar { - flex: 0 0 auto; display: flex; - flex-direction: column; + flex-direction: row; + flex: 0 0 auto; align-items: center; - margin: 0 4px 4px; + flex-wrap: nowrap; + margin: 0; z-index: 12; overflow: hidden; + // background: rgba(243, 160, 204, 0.617); &.red-ui-sidebar-left { z-index: 10; - margin-right: 0; - margin-left: 0; - background: var(--red-ui-primary-background); + margin-left: 5px; + background: none; } &.red-ui-sidebar-right { - margin-left: 0; - // Account for the RH sidebar having an extra top margin - padding-top: 4px; - margin-right: 0; - margin-bottom: 0; border-bottom: none; + justify-content: flex-end; } button { @@ -162,8 +160,8 @@ align-items: center; justify-content: center; padding: 0; - height: 22px; - width: 22px; + height: 28px; + width: 28px; &:not(.selected):not(:hover) { i { opacity: 0.7; @@ -179,30 +177,37 @@ .red-ui-sidebar-tab-bar-buttons { display: flex; - width: 100%; // background-color: var(--red-ui-primary-background); - // background: rgba(255, 0, 0, 0.1); - padding: 6px; + // background: rgba(233, 255, 91, 0.555); + // outline: 1px solid red; + height: 100%; + padding: 0 6px; box-sizing: border-box; - flex-direction: column; + flex-direction: row; align-items: center; - gap: 10px; - flex-grow: 1; + gap: 8px; + flex-grow: 0; // height: 50%; - &:first-child { - // background: rgba(255,0,0,0.1); - flex-grow: 0; - flex-shrink: 0; - } - &:last-child { - // background: rgba(255,255,0,0.1); - flex-grow: 1; - flex-shrink: 1; - } + // &:first-child { + // // background: rgba(255,0,0,0.1); + // flex-grow: 0; + // flex-shrink: 0; + // } + // &:last-child { + // // background: rgba(255,255,0,0.1); + // flex-grow: 1; + // flex-shrink: 1; + // } } &.red-ui-sidebar-dragging-tab .red-ui-sidebar-tab-bar-buttons:last-child { - border-top: 2px dashed var(--red-ui-form-input-border-color); + border-left: 2px dashed var(--red-ui-form-input-border-color); margin-top: -2px; + width: 28px; + height: 28px; + } + + .red-ui-sidebar-right & { + justify-items: flex-end; } } @@ -234,7 +239,7 @@ display: flex; justify-content: end; align-items: center; - gap: 3px; + gap: 6px; } /* Deprecated -> red-ui-footer-button */ diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index 4c7dbc125..2a94fefa3 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -434,7 +434,9 @@ i.red-ui-tab-icon { #red-ui-header-tabs { flex: 1 1 100%; - + border-right: 1px solid var(--red-ui-secondary-border-color); + padding-right: 5px; + .red-ui-tabs { background: var(--red-ui-header-background); border: none; 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 efc7cfb9c..2436304ef 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 @@ -176,11 +176,17 @@ opacity: 0.6; } #red-ui-workspace-footer { + display: flex; + flex-direction: row; + gap: 10px; + align-items: center; border: none; background: none; - bottom: 14px; - right: 200px; + bottom: 8px; + right: 0; + left: 0; padding: 0; + // background: rgba(230,230,255,0.8); } .red-ui-component-footer { @include mixins.component-footer; @@ -210,28 +216,42 @@ a.red-ui-footer-button-toggle, button.red-ui-footer-button-toggle { @include mixins.component-footer-button-toggle; } +#red-ui-statusbar { + display: flex; + flex-direction: row; + align-items: center; + gap: 4px; + // background: rgba(62, 236, 53, 0.8); + flex: 1 1 100%; + height: 100%; + padding-left: 5px; +} .red-ui-statusbar-widget { + line-height: 1em; margin: 0 2px; display: inline-block; vertical-align: middle; height: 100%; - line-height: 20px; + & .red-ui-footer-button, + & .red-ui-footer-button-toggle { + min-width: 28px; + height: 28px; + } } .red-ui-statusbar-bucket { - position: absolute; - top: 0; - bottom: 0; + display: flex; + align-items: center; + flex: 1 1 auto; } .red-ui-statusbar-bucket-left { - left: 6px; + flex: 0 0 auto; .red-ui-statusbar-widget:first-child { margin-left: 0; } } .red-ui-statusbar-bucket-right { - right: 6px; .red-ui-statusbar-widget:last-child { margin-right: 0; } @@ -280,11 +300,12 @@ button.red-ui-footer-button-toggle { :root { --red-ui-scrollbar-width: 12px; --red-ui-scrollbar-handle-size: 40px; - --red-ui-scrollbar-handle-background: rgb(128,128,128); + --red-ui-scrollbar-handle-background: rgb(200, 200, 200); } .red-ui-workspace-scrollbar { position: absolute; + // background: rgba(223, 236, 230, 0.8); } .red-ui-workspace-scrollbar-handle { position: absolute; @@ -308,7 +329,7 @@ button.red-ui-footer-button-toggle { } #red-ui-workspace-scroll-v { top: 2px; - bottom: 14px; + bottom: 46px; right: 0; width: var(--red-ui-scrollbar-width); .red-ui-workspace-scrollbar-handle { @@ -321,7 +342,7 @@ button.red-ui-footer-button-toggle { #red-ui-workspace-scroll-h { left: 2px; right: 0; - bottom: 2px; + bottom: 36px; height: var(--red-ui-scrollbar-width); .red-ui-workspace-scrollbar-handle { left: 0; From bbaa922dd0a01b6063fb3e65c3e4707e954e3fdf Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 3 Mar 2026 07:16:20 -0800 Subject: [PATCH 5/8] Update styling of tabs to floating active tab and ghost inactive tabs --- .../editor-client/src/sass/tabs.scss | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index 2a94fefa3..868525cfb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -488,13 +488,58 @@ i.red-ui-tab-icon { display: flex; align-items: center; border: none; + gap: 2px; + li { min-width: 60px; max-width: 150px; flex: 1 1 100%; - border-color: var(--red-ui-header-button-border); - border-radius: 4px; - margin-top: 0; + border-color: transparent; + margin: 0; + background: transparent; + + a.red-ui-tab-label { + padding-left: 0; + text-align: center; + } + + &:not(.active) .red-ui-tabs-fade { + display: none; + } + + &:not(.active) { + border-radius: 0; + } + + &:not(:first-child):not(.active) { + box-shadow: -1px 0 0 rgba(0,0,0,0.15); + } + + &:not(:first-child):not(.active):hover, + &.active + li:not(:first-child), + &:hover + li:not(:first-child):not(.active) { + box-shadow: none; + } + + &.active { + background: var(--red-ui-secondary-background); + box-shadow: 0 1px 4px rgba(0,0,0,0.15); + border-color: var(--red-ui-primary-border-color); + border-radius: 4px; + } + + &:not(.active):hover::after { + content: ''; + position: absolute; + inset: 0 0 0 1px; + background: rgba(0,0,0,0.06); + border-radius: 4px; + pointer-events: none; + } + + &:not(.active) a:hover { + background: transparent; + } } } } From 7ca3622648fc26faa050f45c842274e8d51b5f8e Mon Sep 17 00:00:00 2001 From: Noley Holland Date: Tue, 3 Mar 2026 10:07:31 -0800 Subject: [PATCH 6/8] Update styling of subflow icon and add LH padding to flow names --- .../node_modules/@node-red/editor-client/src/sass/tabs.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index 868525cfb..0bec5c1fb 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -499,8 +499,12 @@ i.red-ui-tab-icon { background: transparent; a.red-ui-tab-label { - padding-left: 0; + padding: 0 6px; text-align: center; + + i.red-ui-tab-icon:not(.fa) { + margin-left: 0; + } } &:not(.active) .red-ui-tabs-fade { From a156743e0631a11896f3c8e055624dada04102d4 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Mar 2026 09:40:55 +0000 Subject: [PATCH 7/8] Apply suggestion from @knolleary --- packages/node_modules/@node-red/editor-client/src/sass/tabs.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss index 0bec5c1fb..0b0785965 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/tabs.scss @@ -501,6 +501,7 @@ i.red-ui-tab-icon { a.red-ui-tab-label { padding: 0 6px; text-align: center; + width: auto; i.red-ui-tab-icon:not(.fa) { margin-left: 0; From 037585e586c9dfd3b3338f567d10fe8c31c5d447 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 4 Mar 2026 18:21:42 +0000 Subject: [PATCH 8/8] Tidy up status bar placement and persistence of sidebar state --- .../editor-client/src/js/settings.js | 28 +- .../editor-client/src/js/ui/sidebar.js | 335 +++++++++--------- .../editor-client/src/js/ui/tab-info.js | 2 +- .../@node-red/editor-client/src/js/ui/tray.js | 1 - .../editor-client/src/js/ui/view-navigator.js | 14 +- .../@node-red/editor-client/src/js/ui/view.js | 112 +++--- .../editor-client/src/sass/editor.scss | 3 +- .../editor-client/src/sass/sidebar.scss | 37 +- .../editor-client/src/sass/workspace.scss | 18 +- 9 files changed, 288 insertions(+), 262 deletions(-) diff --git a/packages/node_modules/@node-red/editor-client/src/js/settings.js b/packages/node_modules/@node-red/editor-client/src/js/settings.js index 85c930bfb..f7048df71 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/settings.js +++ b/packages/node_modules/@node-red/editor-client/src/js/settings.js @@ -29,7 +29,16 @@ RED.settings = (function () { } }; - var set = function (key, value) { + /** + * Set a setting in the user settings within the runtime. + * Calls to this function are debounced to avoid excessive calls to the runtime when multiple settings are changed in quick succession. + * The flush parameter can be set to true to bypass the debounce and immediately save the settings to the runtime. + * @param {string} key + * @param {*} value + * @param {boolean} flush + * @returns + */ + var set = function (key, value, flush) { if (!hasLocalStorage()) { return; } @@ -37,7 +46,7 @@ RED.settings = (function () { localStorage.setItem(key+this.authTokensSuffix, JSON.stringify(value)); } else { RED.utils.setMessageProperty(userSettings,key,value); - saveUserSettings(); + saveUserSettings(flush); } }; @@ -188,13 +197,12 @@ RED.settings = (function () { }); } - function saveUserSettings() { + function saveUserSettings(flush) { if (RED.user.hasPermission("settings.write")) { if (pendingSave) { clearTimeout(pendingSave); } - pendingSave = setTimeout(function() { - pendingSave = null; + const save = () => { $.ajax({ method: 'POST', contentType: 'application/json', @@ -206,7 +214,15 @@ RED.settings = (function () { console.log("Unexpected error saving user settings:",jqXHR.status,textStatus); } }); - },300); + } + if (flush) { + save() + } else { + pendingSave = setTimeout(function() { + pendingSave = null; + save(); + }, 300); + } } } 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 959cc110c..ed3a540b6 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,7 @@ * limitations under the License. **/ RED.sidebar = (function() { - const sidebarLayoutVersion = 1 + const sidebarLayoutVersion = 3 const sidebars = { primary: { id: 'primary', @@ -37,16 +37,26 @@ RED.sidebar = (function() { } } const defaultSidebarConfiguration = { - primary: [ ['info','help','config','context'], ['debug'] ], - secondary: [ ['explorer'], ['palette'] ] + v: sidebarLayoutVersion, + primary: { top: { tabs: ['info','help','config','context'], active: 'info' }, bottom: { tabs: ['debug'], active: 'debug' } }, + secondary: { top: { tabs: ['explorer'], active: 'explorer' }, bottom: { tabs: ['palette'], active: 'palette' } } } + let lastSessionSelectedTabs = {} + const knownTabs = {}; + let sidebarsInitialised = false + function exportSidebarState () { + if (!sidebars.secondary?.tabBars?.bottom?.container) { + // This has been called whilst setting up the sidebars + // We don't have all the state yet, so not nothing to export + return + } const state = { - primary: [[], []], - secondary: [[], []], + primary: { top: { tabs: [] }, bottom: { tabs: [] } }, + secondary: { top: { tabs: [] }, bottom: { tabs: [] } }, v: sidebarLayoutVersion } function getTabButtons(tabBar) { @@ -59,22 +69,32 @@ RED.sidebar = (function() { }) return result } - state.primary[0] = getTabButtons(sidebars.primary.tabBars.top.container); - state.primary[1] = getTabButtons(sidebars.primary.tabBars.bottom.container); - state.secondary[0] = getTabButtons(sidebars.secondary.tabBars.top.container); - state.secondary[1] = getTabButtons(sidebars.secondary.tabBars.bottom.container); + state.primary.top.tabs = getTabButtons(sidebars.primary.tabBars.top.container); + state.primary.bottom.tabs = getTabButtons(sidebars.primary.tabBars.bottom.container); + state.secondary.top.tabs = getTabButtons(sidebars.secondary.tabBars.top.container); + state.secondary.bottom.tabs = getTabButtons(sidebars.secondary.tabBars.bottom.container); - RED.settings.set('editor.sidebar.state', state) + state.primary.top.hidden = !!sidebars.primary.sections.top.hidden + state.primary.bottom.hidden = !!sidebars.primary.sections.bottom.hidden + state.secondary.top.hidden = !!sidebars.secondary.sections.top.hidden + state.secondary.bottom.hidden = !!sidebars.secondary.sections.bottom.hidden + + state.primary.top.active = sidebars.primary.tabBars.top.active + state.primary.bottom.active = sidebars.primary.tabBars.bottom.active + state.secondary.top.active = sidebars.secondary.tabBars.top.active + state.secondary.bottom.active = sidebars.secondary.tabBars.bottom.active + + const newState = JSON.stringify(state) + const existingState = JSON.stringify(RED.settings.get('editor.sidebar.state')) + if (newState !== existingState) { + RED.settings.set('editor.sidebar.state', state) + } + if (state.secondary.top.tabs.length === 0) { + console.warn('wiped the floor') + } } - // We store the current sidebar tab id in localStorage as 'last-sidebar-tab' - // This is restored when the editor is reloaded. - // 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 - let lastSessionSelectedTabs = {} function addTab(title,content,closeable,visible) { var options; @@ -96,39 +116,39 @@ RED.sidebar = (function() { // Check the saved sidebar state to see if this tab should be added to the primary or secondary sidebar let savedState = RED.settings.get('editor.sidebar.state', defaultSidebarConfiguration) - if (typeof savedState.primary[0] === 'string' || typeof savedState.secondary[0] === 'string' || savedState.v === undefined) { - // This is a beta.0/1 format. Reset it for beta.2 - savedState = defaultSidebarConfiguration - RED.settings.set('editor.sidebar.state', savedState) - } + let targetSidebar = null let targetSection = null + let showSection = true if (savedState) { let sidebarState - if (savedState.secondary[0].includes(options.id)) { + if (savedState.secondary.top.tabs.includes(options.id)) { options.target = 'secondary' - sidebarState = savedState.secondary[0] + sidebarState = savedState.secondary.top targetSidebar = sidebars.secondary targetSection = 'top' - } else if (savedState.secondary[1].includes(options.id)) { + } else if (savedState.secondary.bottom.tabs.includes(options.id)) { options.target = 'secondary' - sidebarState = savedState.secondary[1] + sidebarState = savedState.secondary.bottom targetSidebar = sidebars.secondary targetSection = 'bottom' - } else if (savedState.primary[0].includes(options.id)) { + } else if (savedState.primary.top.tabs.includes(options.id)) { options.target = 'primary' - sidebarState = savedState.primary[0] + sidebarState = savedState.primary.top targetSidebar = sidebars.primary targetSection = 'top' - } else if (savedState.primary[1].includes(options.id)) { + } else if (savedState.primary.bottom.tabs.includes(options.id)) { options.target = 'primary' - sidebarState = savedState.primary[1] + sidebarState = savedState.primary.bottom targetSidebar = sidebars.primary targetSection = 'bottom' } if (targetSidebar) { + if (sidebarState.hidden) { + showSection = false + } // This tab was found in the saved sidebar state. Now find the target position for the tab button - targetTabButtonIndex = sidebarState.indexOf(options.id) + targetTabButtonIndex = sidebarState.tabs.indexOf(options.id) } } @@ -189,22 +209,27 @@ RED.sidebar = (function() { return } const targetSidebar = options.target === 'secondary' ? sidebars.secondary : sidebars.primary; - // if (targetSidebar.tabBars[options.targetSection].active === options.id && RED.menu.isSelected(targetSidebar.menuToggle)) { - // if (!targetSidebar.sections[options.targetSection].hidden) { - // targetSidebar.hideSection(options.targetSection) - // } else { - // targetSidebar.showSection(options.targetSection) - // } - RED.menu.setSelected(targetSidebar.menuToggle, false); + if (!targetSidebar.sections[options.targetSection].hidden) { + const otherSectionHidden = targetSidebar.sections[options.targetSection === 'top' ? 'bottom' : 'top'].hidden + if (otherSectionHidden) { + // Both sections are going to be hidden, so hide the sidebar first. + // We do this *before* hiding the last section so that we remember which the 'last' section was and it can be + // restored when the sidebar is shown again. + RED.menu.setSelected(targetSidebar.menuToggle, false); + } else { + // Hiding just one section, clear its active setting + targetSidebar.tabBars[targetSection].active = null + } + targetSidebar.hideSection(options.targetSection) + } else { + targetSidebar.showSection(options.targetSection) + } + exportSidebarState() } else { RED.sidebar.show(options.id) } }) - if (targetSidebar.sections[targetSection].content.children().length === 1) { - RED.sidebar.show(options.id) - } - targetSidebar.resizeSidebarTabBar() } function removeTab(id) { @@ -243,9 +268,16 @@ RED.sidebar = (function() { if (targetSidebar.sections[targetPosition].content.children().length === 1) { RED.sidebar.show(options.id) } - if (srcSidebar.sections[srcPosition].content.children().length === 0 && srcPosition === 'bottom') { + if (srcSidebar.sections[srcPosition].content.children().length === 0) { + // src has been emptied srcSidebar.hideSection(srcPosition) - } else if (targetSidebar.sections[targetPosition].hidden) { + srcSidebar.tabBars[srcPosition].container.addClass('red-ui-sidebar-tab-bar-empty') + } + if (targetSidebar.sections[targetPosition].content.children().length > 0) { + targetSidebar.tabBars[targetPosition].container.removeClass('red-ui-sidebar-tab-bar-empty') + } + + if (targetSidebar.sections[targetPosition].hidden) { targetSidebar.showSection(targetPosition) } } @@ -260,28 +292,39 @@ RED.sidebar = (function() { tabBar.prependTo("#red-ui-workspace-footer"); } - // TODO: make this an API object, not just a jQuery object + // TODO: consider an explicit toggle button for the sidebars... + // const toggleSidebarButton = $('') + // toggleSidebarButton.on('click', function() { + // RED.menu.toggleSelected(sidebar.menuToggle); + // }) sidebar.tabBars = { top: setupTabSection(sidebar, tabBar, 'top'), bottom: setupTabSection(sidebar, tabBar, 'bottom') } + // if (sidebar.direction === 'right') { + // toggleSidebarButton.appendTo(tabBar); + // } else if (sidebar.direction === 'left') { + // toggleSidebarButton.prependTo(tabBar); + // } + + + sidebar.tabBar = tabBar // sidebar.tabBars.top.container; - sidebar.resizeSidebarTabBar = function () { - sidebar.tabBars.top.resizeSidebarTabBar(); - sidebar.tabBars.bottom.resizeSidebarTabBar(); - } sidebar.hideSection = function (position) { + // Track the height of the top section as that is the one that will determine the layout - but only if the other section is visible. + const otherPosition = position === 'top' ? 'bottom' : 'top' + if (!sidebar.sections[otherPosition].hidden) { + sidebar.sections.top.height = sidebar.sections.top.container.height() || 300 + } sidebar.sections[position].container.hide() sidebar.sections[position].hidden = true - const otherPosition = position === 'top' ? 'bottom' : 'top' sidebar.sections[otherPosition].container.css('flex-grow', '1') - + if (otherPosition === 'bottom') { + sidebar.sections[otherPosition].container.css('margin-top', sidebar.direction === 'left' ? '0' : '4px') + } sidebar.tabBars[position].clearSelected() - // sidebar.tabBars.top.container.css('flex-grow', '1') - // sidebar.tabBars[position].container.css('flex-grow', '0') - // sidebar.tabBars[position].container.css('height', '60px') - + sidebar.resizeSidebar() } sidebar.showSection = function (position) { @@ -289,15 +332,16 @@ RED.sidebar = (function() { sidebar.sections[position].hidden = false const otherPosition = position === 'top' ? 'bottom' : 'top' sidebar.sections[otherPosition].container.css('flex-grow', '0') - sidebar.sections[otherPosition].container.css('height', '70%') - // sidebar.tabBars.top.container.css('flex-grow', '') - // sidebar.tabBars[position].container.css('flex-grow', '') - // sidebar.tabBars[position].container.css('height', '') - // sidebar.tabBars[position].active + sidebar.sections.top.container.css('height', sidebar.sections.top.height + 'px') + sidebar.sections.bottom.container.css('height', '100%') + if (otherPosition === 'bottom') { + sidebar.sections[otherPosition].container.css('margin-top', '') + } sidebar.tabBars[position].container.find('button[data-tab-id="'+sidebar.tabBars[position].active+'"]').addClass('selected') - sidebar.resizeSidebar() } + sidebar.hideSection('top') + sidebar.hideSection('bottom') } function setupTabSection(sidebar, tabBar, position) { @@ -349,6 +393,7 @@ RED.sidebar = (function() { tabBarButtonsContainer.data('sidebar', sidebar.id) tabBarButtonsContainer.data('sidebar-position', position) tabBarButtonsContainer.sortable({ + axis: 'x', distance: 10, cancel: false, items: "button:not(.red-ui-sidebar-tab-bar-overflow-button)", @@ -356,14 +401,6 @@ RED.sidebar = (function() { connectWith: ".red-ui-sidebar-tab-bar-buttons", start: function(event, ui) { const tabId = ui.item.attr('data-tab-id'); - const tabCount = tabBarButtonsContainer.children('button:not(.red-ui-sidebar-tab-bar-overflow-button):not(.red-ui-sidebar-tab-bar-button-placeholder)').length - if (position === 'top' && tabCount === 1) { - // Call in a timeout to allow the stortable start event to complete - // processing - otherwise errors are thrown - setTimeout(function () { - tabBarButtonsContainer.sortable('cancel'); - }, 0); - } const options = knownTabs[tabId]; options.tabButtonTooltip.delete() draggingTabButton = true @@ -395,60 +432,10 @@ RED.sidebar = (function() { RED.sidebar.show(firstTab); } } - src.resizeSidebarTabBar(); - dest.resizeSidebarTabBar(); - RED.sidebar.show(tabId) } }) - let hasHidden = false - const resizeSidebarTabBar = function () { - return; - let tabBarButtonsBottom = tabBarButtonsContainer.position().top + tabBarButtonsContainer.outerHeight(); - const buttonHeight = tabOverflowButton.outerHeight() - // Find the last visible button - let bottomButton = tabBarButtonsContainer.children(":visible").last() - if (bottomButton.length === 0) { - // Nothing visible - bail out - return - } - if (tabBarButtonsBottom < bottomButton.position().top + buttonHeight) { - tabOverflowButton.show() - let tabOverflowButtonBottom = tabOverflowButton.position().top + buttonHeight * 1.5; - while (tabBarButtonsBottom < tabOverflowButtonBottom) { - const lastVisible = tabBarButtonsContainer.children(':not(".red-ui-sidebar-tab-bar-overflow-button"):visible').last() - if (lastVisible.length === 0) { - // Nothing left to hide - break - } - lastVisible.hide() - tabOverflowButtonBottom = tabOverflowButton.position().top + buttonHeight * 1.5; - } - } else { - const hiddenChildren = tabBarButtonsContainer.children(':not(".red-ui-sidebar-tab-bar-overflow-button"):hidden') - if (hiddenChildren.length > 0) { - // We may be able to show some more buttons - let tabOverflowButtonBottom = tabOverflowButton.position().top + buttonHeight * 2; - let shownCount = 0 - while (tabBarButtonsBottom > tabOverflowButtonBottom + buttonHeight) { - const firstHidden = tabBarButtonsContainer.children(':not(".red-ui-sidebar-tab-bar-overflow-button"):hidden').first() - if (firstHidden.length === 0) { - // Nothing left to show - break - } - firstHidden.show() - shownCount++ - tabOverflowButtonBottom = tabOverflowButton.position().top + buttonHeight * 2; - } - if (hiddenChildren.length - shownCount <= 0) { - // We were able to show all of the hidden buttons - // so hide the overflow button again - tabOverflowButton.hide() - } - } - } - } return { container: tabBarButtonsContainer, addButton: function(button, position) { @@ -460,8 +447,7 @@ RED.sidebar = (function() { }, clearSelected: function() { tabBarButtonsContainer.children('button').removeClass('selected') - }, - resizeSidebarTabBar + } } } function setupSidebarSeparator(sidebar) { @@ -540,9 +526,8 @@ RED.sidebar = (function() { sidebar.sections.top.container.hide() sidebar.sections.bottom.container.hide() sidebar.separator.hide() - // if (sidebar.container.width() < sidebar.minimumWidth) { - // sidebar.container.width(sidebar.defaultWidth); - // } + } else { + sidebar.width = sidebar.container.width(); } sidebar.tabBar.css('min-width', sidebar.container.width() - 5) RED.events.emit("sidebar:resize"); @@ -555,22 +540,45 @@ RED.sidebar = (function() { if (!state) { // sidebar.container.hide() sidebar.separator.hide() - sidebar.sections.top.container.hide() - sidebar.sections.bottom.container.hide() + // Remember which sections were hidden (or not) before we hide the sidebar - so we can restore that state when we show the sidebar again + sidebar.sections.top.wasHidden = sidebar.sections.top.hidden + sidebar.sections.bottom.wasHidden = sidebar.sections.bottom.hidden + sidebar.hideSection('top') + sidebar.hideSection('bottom') sidebar.container.width(0) - sidebar.tabBars.top.clearSelected() - sidebar.tabBars.bottom.clearSelected() + if (sidebarsInitialised) { + exportSidebarState() + } } else { // sidebar.container.show() - sidebar.sections.top.container.show() - sidebar.sections.bottom.container.show() - sidebar.container.width(200) + if (!sidebar.sections.top.hidden) { + + sidebar.sections.top.container.show() + } + if (!sidebar.sections.bottom.hidden) { + sidebar.sections.bottom.container.show() + } + if (sidebar.sections.top.hidden && sidebar.sections.bottom.hidden) { + const topHasContent = sidebar.sections.top.content.children().length > 0 + const bottomHasContent = sidebar.sections.bottom.content.children().length > 0 + if (!topHasContent && !bottomHasContent) { + // Nothing to show - keep the sidebar hidden + return + } + if (sidebar.tabBars.top.active) { + showSidebar(sidebar.tabBars.top.active) + } + if (sidebar.tabBars.bottom.active) { + showSidebar(sidebar.tabBars.bottom.active) + } + } + sidebar.container.width(sidebar.width || sidebar.defaultWidth) sidebar.tabBar.css('min-width', sidebar.container.width() - 5) sidebar.separator.show() - if (sidebar.tabBars.top.active) { + if (sidebar.tabBars.top.active && !sidebar.sections.top.hidden) { sidebar.tabBars.top.container.find('button[data-tab-id="'+sidebar.tabBars.top.active+'"]').addClass('selected') } - if (sidebar.tabBars.bottom.active) { + if (sidebar.tabBars.bottom.active && !sidebar.sections.bottom.hidden) { sidebar.tabBars.bottom.container.find('button[data-tab-id="'+sidebar.tabBars.bottom.active+'"]').addClass('selected') } } @@ -579,15 +587,17 @@ RED.sidebar = (function() { function showSidebar(id, skipShowSidebar) { if (id === ":first") { + sidebarsInitialised = true // Show the last selected tab for each sidebar Object.keys(sidebars).forEach(function(sidebarKey) { const sidebar = sidebars[sidebarKey]; ['top','bottom'].forEach(function(position) { - let lastTabId = lastSessionSelectedTabs[sidebarKey + '-' + position]; - if (!lastTabId) { - lastTabId = sidebar.tabBars[position].container.children('button').first().attr('data-tab-id'); + if (lastSessionSelectedTabs[sidebarKey]?.[position]?.hidden !== true) { + let lastTabId = lastSessionSelectedTabs[sidebarKey]?.[position]?.active; + if (lastTabId) { + showSidebar(lastTabId) + } } - showSidebar(lastTabId, true) }) }) return @@ -609,17 +619,18 @@ RED.sidebar = (function() { } else { targetSidebar.sections[targetSection].footer.hide(); } - RED.settings.setLocal("last-sidebar-tab-" + targetSidebar.id+'-'+targetSection, tabOptions.id) - // TODO: find which tabBar the button is in - targetSidebar.tabBars[targetSection].clearSelected() - targetSidebar.tabBars[targetSection].container.find('button[data-tab-id="'+id+'"]').addClass('selected') targetSidebar.tabBars[targetSection].active = id + if (!skipShowSidebar && targetSidebar.sections[targetSection].hidden) { + targetSidebar.showSection(targetSection) + } if (!skipShowSidebar && !RED.menu.isSelected(targetSidebar.menuToggle)) { RED.menu.setSelected(targetSidebar.menuToggle,true); } - if (targetSidebar.sections[targetSection].hidden) { - targetSidebar.showSection(targetSection) + if (!targetSidebar.sections[targetSection].hidden) { + targetSidebar.tabBars[targetSection].clearSelected() + targetSidebar.tabBars[targetSection].container.find('button[data-tab-id="'+id+'"]').addClass('selected') } + exportSidebarState() } } } @@ -687,7 +698,6 @@ RED.sidebar = (function() { // sidebar.shade = $('
          ').appendTo(sidebar.container); sidebar.separator = setupSidebarSeparator(sidebar); - setupSidebarTabs(sidebar) sidebar.resizeSidebar = function () { // Resize sidebar sections as needed const topSectionHeight = sidebar.sections.top.container.outerHeight() @@ -698,24 +708,15 @@ RED.sidebar = (function() { if (bottomSectionHeight < 90 && topSectionHeight > 90) { sidebar.sections.top.container.outerHeight(topSectionHeight - (90 - bottomSectionHeight)); } - // sidebar.tabBars.top.container.height(sidebar.sections.top.container.outerHeight()) - // } else { - // sidebar.tabBars.top.container.height(sidebar.sections.top.container.outerHeight() - 60) } // Trigger a resize of the tab bars to handle overflow - sidebar.resizeSidebarTabBar() sidebar.tabBar.css('min-width', sidebar.container.width() - 5) RED.events.emit("sidebar:resize"); } $(window).on("resize", sidebar.resizeSidebar) - if (sidebar.defaultTopHeight > 0) { - if (sidebar.defaultTopHeight === 1) { - sidebar.hideSection('bottom') - } else { - sidebar.sections.top.container.outerHeight(sidebarHeight * sidebar.defaultTopHeight); - } - } + + setupSidebarTabs(sidebar) sidebar.resizeSidebar() } @@ -737,15 +738,25 @@ RED.sidebar = (function() { RED.menu.toggleSelected(sidebars.secondary.menuToggle); } else { toggleSidebar(sidebars.secondary, state); - } + } }); - // 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 + '-top'] = RED.settings.getLocal("last-sidebar-tab-" + sidebarKey + '-top'); - lastSessionSelectedTabs[sidebarKey + '-bottom'] = RED.settings.getLocal("last-sidebar-tab-" + sidebarKey + '-bottom'); - }) + // Load any saved sidebar state + lastSessionSelectedTabs = RED.settings.get('editor.sidebar.state', defaultSidebarConfiguration) + if (typeof lastSessionSelectedTabs.primary[0] === 'string' || typeof lastSessionSelectedTabs.secondary[0] === 'string' || lastSessionSelectedTabs.v === undefined || lastSessionSelectedTabs.v !== sidebarLayoutVersion) { + // This is a beta.0/1 format. Reset it for beta.2 + lastSessionSelectedTabs = defaultSidebarConfiguration + // To migrate state format, we have to delete the old entry rather than try to merge settings + // RED.settings.set debounces calls made within 300ms - we need the above call to get flushed before setting the new state value. + RED.settings.set('editor.sidebar.state', null, true) + RED.settings.set('editor.sidebar.state', lastSessionSelectedTabs) + } + + // Restore the active flags so we know which sidebars to show when the :first option is used + sidebars.primary.tabBars.top.active = lastSessionSelectedTabs?.primary?.top?.active + sidebars.primary.tabBars.bottom.active = lastSessionSelectedTabs?.primary?.bottom?.active + sidebars.secondary.tabBars.top.active = lastSessionSelectedTabs?.secondary?.top?.active + sidebars.secondary.tabBars.bottom.active = lastSessionSelectedTabs?.secondary?.bottom?.active } return { 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 6fd3ef682..2d1926ee0 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 @@ -63,7 +63,7 @@ RED.sidebar.info = (function() { }).appendTo(propertiesPanel); propertiesPanelHeaderIcon = $('').appendTo(propertiesPanelHeader); - propertiesPanelHeaderLabel = $('').appendTo(propertiesPanelHeader); + propertiesPanelHeaderLabel = $('').appendTo(propertiesPanelHeader); const buttons = $('').appendTo(propertiesPanelHeader); propertiesPanelHeaderCopyLink = $('').on("click", function(evt) { 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 e44c9c567..d17cf95fa 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,7 +194,6 @@ function handleWindowResize() { let sidebarWidth = $("#red-ui-sidebar").is(":visible") ? $("#red-ui-sidebar").outerWidth() : 0; - console.log('tray.handleWindowResize', sidebarWidth) $("#red-ui-editor-stack").css('right', sidebarWidth + 4); if (stack.length > 0) { var tray = stack[stack.length-1]; diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js b/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js index e2f0e96b2..b21ec0da7 100644 --- a/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js +++ b/packages/node_modules/@node-red/editor-client/src/js/ui/view-navigator.js @@ -16,7 +16,7 @@ RED.view.navigator = (function() { - var nav_scale = 50; + var nav_scale = 80; var nav_width = 8000/nav_scale; var nav_height = 8000/nav_scale; var navContainer; @@ -45,8 +45,8 @@ RED.view.navigator = (function() { navNode.each(function(d) { d3.select(this).attr("x",function(d) { return (d.x-d.w/2)/nav_scale }) .attr("y",function(d) { return (d.y-d.h/2)/nav_scale }) - .attr("width",function(d) { return Math.max(9,d.w/nav_scale) }) - .attr("height",function(d) { return Math.max(3,d.h/nav_scale) }) + .attr("width",function(d) { return Math.max(4,d.w/nav_scale) }) + .attr("height",function(d) { return Math.max(2,d.h/nav_scale) }) .attr("fill",function(d) { return RED.utils.getNodeColor(d.type,d._def);}) }); } @@ -132,14 +132,14 @@ RED.view.navigator = (function() { RED.statusBar.add({ id: "view-navigator", - align: "right", - element: $('') + align: "left", + element: $('') }) navContainer = $('
          ').css({ "position":"absolute", - "bottom": 25, - "right": 0, + "bottom": 35, + "left": 0, zIndex: 1 }).addClass('red-ui-navigator-container').appendTo("#red-ui-view-navigator-widget").hide(); navBox = d3.select(navContainer[0]) 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 9009da165..63a93787d 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 @@ -766,42 +766,6 @@ RED.view = (function() { } }) - RED.statusBar.add({ - id: "view-zoom-controls", - align: "right", - element: $(''+ - ''+ - ''+ - ''+ - ''+ - '') - }) - - $("#red-ui-view-zoom-out").on("click", function() { zoomOut(); }); - RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out'); - $("#red-ui-view-zoom-zero").on("click", zoomZero); - RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset'); - $("#red-ui-view-zoom-in").on("click", function() { zoomIn(); }); - RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in'); - $("#red-ui-view-zoom-fit").on("click", zoomToFitAll); - RED.popover.tooltip($("#red-ui-view-zoom-fit"),RED._('actions.zoom-fit'),'core:zoom-fit'); - // Legacy mouse wheel handler - disabled in favor of modern wheel event - // chart.on("DOMMouseScroll mousewheel", function (evt) { - // if ( evt.altKey || spacebarPressed ) { - // evt.preventDefault(); - // evt.stopPropagation(); - // // Get cursor position relative to the chart - // var offset = chart.offset(); - // var cursorPos = [ - // evt.originalEvent.pageX - offset.left, - // evt.originalEvent.pageY - offset.top - // ]; - // var move = -(evt.originalEvent.detail) || evt.originalEvent.wheelDelta; - // if (move <= 0) { zoomOut(cursorPos); } - // else { zoomIn(cursorPos); } - // } - // }); - // Modern wheel event handler for better trackpad support (pinch-to-zoom) and momentum var momentumTimer = null; var trackpadGestureTimer = null; @@ -971,33 +935,6 @@ RED.view = (function() { } }); - //add search to status-toolbar - RED.statusBar.add({ - id: "view-search-tools", - align: "left", - hidden: false, - element: $(''+ - '' + - '' + - '' + - '? of ?' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '') - }) - $("#red-ui-view-searchtools-search").on("click", searchFlows); - RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); - $("#red-ui-view-searchtools-prev").on("click", searchPrev); - RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); - $("#red-ui-view-searchtools-next").on("click", searchNext); - RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); - RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); - // Handle nodes dragged from the palette chart.droppable({ accept:".red-ui-palette-node", @@ -1245,6 +1182,55 @@ RED.view = (function() { RED.view.navigator.init(); RED.view.tools.init(); + RED.statusBar.add({ + id: "view-zoom-controls", + align: "left", + element: $(''+ + ''+ + ''+ + ''+ + ''+ + '') + }) + + $("#red-ui-view-zoom-out").on("click", function() { zoomOut(); }); + RED.popover.tooltip($("#red-ui-view-zoom-out"),RED._('actions.zoom-out'),'core:zoom-out'); + $("#red-ui-view-zoom-zero").on("click", zoomZero); + RED.popover.tooltip($("#red-ui-view-zoom-zero"),RED._('actions.zoom-reset'),'core:zoom-reset'); + $("#red-ui-view-zoom-in").on("click", function() { zoomIn(); }); + RED.popover.tooltip($("#red-ui-view-zoom-in"),RED._('actions.zoom-in'),'core:zoom-in'); + $("#red-ui-view-zoom-fit").on("click", zoomToFitAll); + RED.popover.tooltip($("#red-ui-view-zoom-fit"),RED._('actions.zoom-fit'),'core:zoom-fit'); + + //add search to status-toolbar + RED.statusBar.add({ + id: "view-search-tools", + align: "left", + hidden: false, + element: $(''+ + '' + + '' + + '' + + '? of ?' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '') + }) + $("#red-ui-view-searchtools-search").on("click", searchFlows); + RED.popover.tooltip($("#red-ui-view-searchtools-search"),RED._('actions.search-flows'),'core:search'); + $("#red-ui-view-searchtools-prev").on("click", searchPrev); + RED.popover.tooltip($("#red-ui-view-searchtools-prev"),RED._('actions.search-prev'),'core:search-previous'); + $("#red-ui-view-searchtools-next").on("click", searchNext); + RED.popover.tooltip($("#red-ui-view-searchtools-next"),RED._('actions.search-next'),'core:search-next'); + RED.popover.tooltip($("#red-ui-view-searchtools-close"),RED._('common.label.close')); + + + RED.view.annotations.register("red-ui-flow-node-docs",{ type: "badge", class: "red-ui-flow-node-docs", diff --git a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss index 90c119926..0e16b5df8 100644 --- a/packages/node_modules/@node-red/editor-client/src/sass/editor.scss +++ b/packages/node_modules/@node-red/editor-client/src/sass/editor.scss @@ -30,8 +30,7 @@ position:absolute; margin: 8px 0 8px 7px; top: 0; - bottom: 0; - //min-width: 500px; + bottom: 34px; width: auto; right: -1000px; box-sizing: border-box; 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 d28ab75d5..29ef01475 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 @@ -68,6 +68,9 @@ overflow: hidden; &.red-ui-sidebar-section-top { margin-top: 4px; } &.red-ui-sidebar-section-bottom { + &:first-child { + margin-top: 4px; + } flex-grow: 1; flex-shrink: 1; } @@ -164,12 +167,17 @@ width: 28px; &:not(.selected):not(:hover) { i { - opacity: 0.7; + opacity: 1; } } i { font-size: 13px; } + border-color: var(--red-ui-secondary-border-color); + &.selected { + background-color: var(--red-ui-secondary-color); + border-color: var(--red-ui-primary-border-color); + } } .red-ui-sidebar-tab-bar-button-placeholder { border: 1px dashed var(--red-ui-form-input-border-color) !important; @@ -179,8 +187,8 @@ display: flex; // background-color: var(--red-ui-primary-background); // background: rgba(233, 255, 91, 0.555); - // outline: 1px solid red; - height: 100%; + + height: 28px; padding: 0 6px; box-sizing: border-box; flex-direction: row; @@ -199,18 +207,33 @@ // flex-shrink: 1; // } } + .red-ui-sidebar-tab-bar-buttons.red-ui-sidebar-tab-bar-empty { + display: none; + // background: rgba(255,0,0,0.3); + } + &.red-ui-sidebar-dragging-tab .red-ui-sidebar-tab-bar-empty { + display: flex; + } &.red-ui-sidebar-dragging-tab .red-ui-sidebar-tab-bar-buttons:last-child { - border-left: 2px dashed var(--red-ui-form-input-border-color); - margin-top: -2px; - width: 28px; + border-left: 2px dashed var(--red-ui-secondary-border-color); + // margin-left: -2px; + width: 42px; height: 28px; } - + .red-ui-sidebar-tab-bar-buttons:last-child { + min-width: 28px; + border-left: 2px solid var(--red-ui-secondary-border-color); + } .red-ui-sidebar-right & { justify-items: flex-end; } } +.red-ui-sidebar-right .red-ui-sidebar-tab-bar-buttons:first-child { + padding-left: 20px; +} + + .red-ui-sidebar .button { @include mixins.workspace-button; line-height: 18px; 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 2436304ef..f350ca2aa 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 @@ -128,18 +128,6 @@ } .red-ui-workspace-locked { &.red-ui-tab { - // border-top-style: dashed; - // border-left-style: dashed; - // border-right-style: dashed; - - // a { - // font-style: italic; - // color: var(--red-ui-tab-text-color-disabled-inactive) !important; - // } - // &.active a { - // font-weight: normal; - // color: var(--red-ui-tab-text-color-disabled-active) !important; - // } .red-ui-workspace-locked-icon { display: inline; } @@ -149,7 +137,7 @@ #red-ui-navigator-canvas { position: absolute; bottom: 0; - right:0; + left:0; z-index: 101; border: 1px solid var(--red-ui-primary-border-color); background: var(--red-ui-view-navigator-background); @@ -157,6 +145,10 @@ border-radius: 4px; } +#view-zoom-controls .button-group button.red-ui-footer-button:not(:last-child) { + border-right: none; +} + .red-ui-navigator-container { transition: opacity 0.3s ease; opacity: 0;