Merge pull request #5498 from node-red/sidebar-redux

UX updates for beta 3
pull/5514/head
Nick O'Leary 2026-03-05 10:36:59 +00:00 committed by GitHub
commit 372c0ea690
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 864 additions and 493 deletions

View File

@ -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) {
@ -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,18 @@ var RED = (function() {
function buildEditor(options) {
var header = $('<div id="red-ui-header"></div>').appendTo(options.target);
var logo = $('<span class="red-ui-header-logo"></span>').appendTo(header);
const header = $('<div id="red-ui-header"></div>').appendTo(options.target);
const logoContainer = $('<div id="red-ui-header-logo"></div>').appendTo(header);
let logo = $('<span class="red-ui-header-logo"></span>').appendTo(logoContainer);
$('<div id="red-ui-header-tabs" class="hide"></div>').appendTo(header);
$('<ul class="red-ui-header-toolbar hide"></ul>').appendTo(header);
$('<div id="red-ui-header-shade" class="hide"></div>').appendTo(header);
$('<div id="red-ui-main-container">'+
'<div id="red-ui-sidebar-left"></div>'+
'<div id="red-ui-workspace"></div>'+
'<div id="red-ui-sidebar"></div>'+
'<div id="red-ui-sidebar-container">'+
'<div id="red-ui-sidebar"></div>'+
'</div>'+
'<div id="red-ui-editor-stack" tabindex="-1"></div>'+
'</div>').appendTo(options.target);

View File

@ -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);
}
}
}

View File

@ -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( "<div>" ).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 = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left" style="display:none;"><a href="#"><i class="fa fa-caret-left"></i></a></div>').prependTo(wrapper).find("a");
scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();});
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').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 = $('<div class="red-ui-tab-button red-ui-tabs-add"><a href="#"><i class="fa fa-plus"></i></a></div>').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 = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-left"><a href="#" style="display:none;"><i class="fa fa-caret-left"></i></a></div>').appendTo(wrapper).find("a");
scrollLeft.on('mousedown',function(evt) {scrollEventHandler(evt, evt.shiftKey?('-='+scrollContainer.scrollLeft()):'-=150') }).on('click',function(evt){ evt.preventDefault();});
scrollRight = $('<div class="red-ui-tab-button red-ui-tab-scroll red-ui-tab-scroll-right"><a href="#" style="display:none;"><i class="fa fa-caret-right"></i></a></div>').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 = $('<div>',{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;
},

View File

@ -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)
}
}
@ -255,33 +287,44 @@ RED.sidebar = (function() {
function setupSidebarTabs(sidebar) {
const tabBar = $('<div class="red-ui-sidebar-tab-bar"></div>').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
// TODO: consider an explicit toggle button for the sidebars...
// const toggleSidebarButton = $('<button class="red-ui-sidebar-toggle-button"><i class="fa fa-columns"></i></button>')
// toggleSidebarButton.on('click', function() {
// RED.menu.toggleSelected(sidebar.menuToggle);
// })
sidebar.tabBars = {
top: setupTabSection(sidebar, tabBar, 'top'),
bottom: setupTabSection(sidebar, tabBar, 'bottom')
}
sidebar.tabBar = sidebar.tabBars.top.container;
sidebar.resizeSidebarTabBar = function () {
sidebar.tabBars.top.resizeSidebarTabBar();
sidebar.tabBars.bottom.resizeSidebarTabBar();
}
// if (sidebar.direction === 'right') {
// toggleSidebarButton.appendTo(tabBar);
// } else if (sidebar.direction === 'left') {
// toggleSidebarButton.prependTo(tabBar);
// }
sidebar.tabBar = tabBar // sidebar.tabBars.top.container;
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,59 +432,10 @@ RED.sidebar = (function() {
RED.sidebar.show(firstTab);
}
}
src.resizeSidebarTabBar();
dest.resizeSidebarTabBar();
RED.sidebar.show(tabId)
}
})
let hasHidden = false
const resizeSidebarTabBar = function () {
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) {
@ -459,8 +447,7 @@ RED.sidebar = (function() {
},
clearSelected: function() {
tabBarButtonsContainer.children('button').removeClass('selected')
},
resizeSidebarTabBar
}
}
}
function setupSidebarSeparator(sidebar) {
@ -526,8 +513,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 +523,13 @@ 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);
}
} else {
sidebar.width = sidebar.container.width();
}
sidebar.tabBar.css('min-width', sidebar.container.width() - 5)
RED.events.emit("sidebar:resize");
}
});
@ -551,17 +538,47 @@ RED.sidebar = (function() {
function toggleSidebar(sidebar, state) {
if (!state) {
sidebar.container.hide()
// sidebar.container.hide()
sidebar.separator.hide()
sidebar.tabBars.top.clearSelected()
sidebar.tabBars.bottom.clearSelected()
// 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)
if (sidebarsInitialised) {
exportSidebarState()
}
} else {
sidebar.container.show()
// sidebar.container.show()
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')
}
}
@ -570,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
@ -600,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()
}
}
}
@ -666,7 +686,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()
@ -678,7 +698,6 @@ RED.sidebar = (function() {
// sidebar.shade = $('<div class="red-ui-sidebar-shade hide"></div>').appendTo(sidebar.container);
sidebar.separator = setupSidebarSeparator(sidebar);
setupSidebarTabs(sidebar)
sidebar.resizeSidebar = function () {
// Resize sidebar sections as needed
const topSectionHeight = sidebar.sections.top.container.outerHeight()
@ -689,23 +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()
}
@ -727,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 {

View File

@ -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 = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-left">').appendTo("#red-ui-workspace-footer");
rightBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-right">').appendTo("#red-ui-workspace-footer");
const widgetBar = $('<div id="red-ui-statusbar">').appendTo("#red-ui-workspace-footer")
leftBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-left">').appendTo(widgetBar);
rightBucket = $('<span class="red-ui-statusbar-bucket red-ui-statusbar-bucket-right">').appendTo(widgetBar);
},
add: addWidget,
hide: hideWidget,

View File

@ -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());

View File

@ -58,47 +58,36 @@ RED.sidebar.info = (function() {
"display": "flex",
"flex-direction": "column"
}).appendTo(stackContainer);
propertiesPanelHeader = $("<div>", {class:"red-ui-palette-header red-ui-info-header"}).css({
propertiesPanelHeader = $("<div>", {class:"red-ui-sidebar-header"}).css({
"flex":"0 0 auto"
}).appendTo(propertiesPanel);
propertiesPanelHeaderIcon = $("<span>").appendTo(propertiesPanelHeader);
propertiesPanelHeaderLabel = $("<span>").appendTo(propertiesPanelHeader);
propertiesPanelHeaderIcon = $('<span style="display: flex; align-items: center;"></span>').appendTo(propertiesPanelHeader);
propertiesPanelHeaderLabel = $('<span style="overflow: hidden; text-overflow: ellipsis; flex: 1 1 auto;"></span>').appendTo(propertiesPanelHeader);
propertiesPanelHeaderCopyLink = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-link"></button>').css({
position: 'absolute',
top: '6px',
right: '32px'
}).on("click", function(evt) {
const buttons = $('<span class="button-group"></span>').appendTo(propertiesPanelHeader);
propertiesPanelHeaderCopyLink = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-link"></button>').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 = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-book"></button>').css({
position: 'absolute',
top: '6px',
right: '56px'
}).on("click", function(evt) {
propertiesPanelHeaderHelp = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-book"></button>').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 = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-search"></button>').css({
position: 'absolute',
top: '6px',
right: '8px'
}).on("click", function(evt) {
propertiesPanelHeaderReveal = $('<button type="button" class="red-ui-button red-ui-button-small"><i class="fa fa-search"></button>').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"));

View File

@ -194,7 +194,7 @@
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);
$("#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) {

View File

@ -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);})
});
}
@ -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: "left",
element: $('<span id="red-ui-view-navigator-widget" style="position: relative;"><button class="red-ui-footer-button-toggle single" id="red-ui-view-navigate"><i class="fa fa-map-o"></i></button></span>')
})
navContainer = $('<div>').css({
"position":"absolute",
"bottom":$("#red-ui-workspace-footer").height() + 12,
"right": 16,
"bottom": 35,
"left": 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: $('<button class="red-ui-footer-button-toggle single" id="red-ui-view-navigate"><i class="fa fa-map-o"></i></button>')
})
$("#red-ui-view-navigate").on("click", function(evt) {
evt.preventDefault();

View File

@ -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) {
@ -765,42 +766,6 @@ RED.view = (function() {
}
})
RED.statusBar.add({
id: "view-zoom-controls",
align: "right",
element: $('<span class="button-group">'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-out"><i class="fa fa-minus"></i></button>'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-zero"><i class="fa fa-circle-o"></i></button>'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-in"><i class="fa fa-plus"></i></button>'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-fit"><i class="fa fa-compress"></i></button>'+
'</span>')
})
$("#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;
@ -824,7 +789,6 @@ RED.view = (function() {
if (evt.ctrlKey || evt.altKey || spacebarPressed) {
evt.preventDefault();
evt.stopPropagation();
var currentTime = Date.now();
var timeSinceLastEvent = currentTime - lastWheelEventTime;
@ -845,7 +809,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 +832,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 +880,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
@ -963,33 +935,6 @@ RED.view = (function() {
}
});
//add search to status-toolbar
RED.statusBar.add({
id: "view-search-tools",
align: "left",
hidden: false,
element: $('<span class="button-group">'+
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-search"><i class="fa fa-search"></i></button>' +
'</span>' +
'<span class="button-group red-ui-view-searchtools-counter">' +
'<span class="red-ui-footer-button" id="red-ui-view-searchtools-counter-label">? of ?</span>' +
'</span>' +
'<span class="button-group">' +
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-prev"><i class="fa fa-chevron-left"></i></button>' +
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-next"><i class="fa fa-chevron-right"></i></button>' +
'</span>' +
'<span class="button-group">' +
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-close"><i class="fa fa-close"></i></button>' +
'</span>')
})
$("#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",
@ -1237,6 +1182,55 @@ RED.view = (function() {
RED.view.navigator.init();
RED.view.tools.init();
RED.statusBar.add({
id: "view-zoom-controls",
align: "left",
element: $('<span class="button-group">'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-out"><i class="fa fa-minus"></i></button>'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-zero"><i class="fa fa-circle-o"></i></button>'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-in"><i class="fa fa-plus"></i></button>'+
'<button class="red-ui-footer-button" id="red-ui-view-zoom-fit"><i class="fa fa-compress"></i></button>'+
'</span>')
})
$("#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: $('<span class="button-group">'+
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-search"><i class="fa fa-search"></i></button>' +
'</span>' +
'<span class="button-group red-ui-view-searchtools-counter">' +
'<span class="red-ui-footer-button" id="red-ui-view-searchtools-counter-label">? of ?</span>' +
'</span>' +
'<span class="button-group">' +
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-prev"><i class="fa fa-chevron-left"></i></button>' +
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-next"><i class="fa fa-chevron-right"></i></button>' +
'</span>' +
'<span class="button-group">' +
'<button class="red-ui-footer-button" id="red-ui-view-searchtools-close"><i class="fa fa-close"></i></button>' +
'</span>')
})
$("#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",
@ -2960,6 +2954,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 +3059,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 +3086,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 +3140,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 +3214,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 +3226,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 +3289,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 +7873,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()]
}

View File

@ -497,17 +497,119 @@ 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() {
$('<ul id="red-ui-workspace-tabs"></ul>').appendTo("#red-ui-workspace");
$('<div id="red-ui-workspace-tabs-shade" class="hide"></div>').appendTo("#red-ui-workspace");
$('<ul id="red-ui-workspace-tabs"></ul>').appendTo("#red-ui-header-tabs");
$('<div id="red-ui-workspace-tabs-shade" class="hide"></div>').appendTo("#red-ui-header-tabs");
$('<div id="red-ui-workspace-chart" tabindex="1"></div>').appendTo("#red-ui-workspace");
$('<div id="red-ui-workspace-toolbar"></div>').appendTo("#red-ui-workspace");
$('<div id="red-ui-workspace-footer" class="red-ui-component-footer"></div>').appendTo("#red-ui-workspace");
$('<div id="red-ui-workspace-footer" class="red-ui-component-footer"></div>').insertAfter("#red-ui-workspace");
scrollbars.v = { bar: $('<div id="red-ui-workspace-scroll-v" class="red-ui-workspace-scrollbar"><div class="red-ui-workspace-scrollbar-handle"><div class="red-ui-workspace-scrollbar-handle-target"></div></div></div>').appendTo("#red-ui-workspace") }
scrollbars.v.handle = scrollbars.v.bar.children().first();
setupScrollbar(scrollbars.v, 'v')
scrollbars.h = { bar: $('<div id="red-ui-workspace-scroll-h" class="red-ui-workspace-scrollbar"><div class="red-ui-workspace-scrollbar-handle"><div class="red-ui-workspace-scrollbar-handle-target"></div></div></div>').appendTo("#red-ui-workspace") }
scrollbars.h.handle = scrollbars.h.bar.children().first();
setupScrollbar(scrollbars.h, 'h')
$('<div id="red-ui-editor-shade" class="hide"></div>').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
// $("#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 - 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 })
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 +636,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)});

View File

@ -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;

View File

@ -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;
@ -50,7 +49,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);
}

View File

@ -34,11 +34,15 @@
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 6px;
span.red-ui-header-logo {
float: left;
#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;
@ -63,12 +67,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 +87,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 +117,6 @@
& > a {
display: inline-block;
position: relative;
float: left;
line-height: 22px;
font-size: 14px;
text-decoration: none;
@ -303,11 +310,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;

View File

@ -140,7 +140,7 @@
vertical-align: middle;
}
.button-group:not(:last-child) {
margin-right: 10px;
margin-right: 4px;
}

View File

@ -263,7 +263,6 @@
width: 24px;
height: 20px;
line-height: 20px;
margin-top: 1px;
// width: 30px;
// height: 25px;
border-radius: 3px;

View File

@ -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);
margin: 4px 0 40px 0;
box-sizing: border-box;
z-index: 12;
display: flex;
@ -34,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 {
@ -51,27 +56,31 @@
}
.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-secondary-border-color);
border: 1px solid var(--red-ui-primary-border-color);
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;
}
}
.red-ui-sidebar-left .red-ui-sidebar-section {
margin-left: 0;
border-color: var(--red-ui-primary-border-color);
&.red-ui-sidebar-section-top { margin-top: 0; }
}
.red-ui-sidebar-right .red-ui-sidebar-section {
margin-right: 0
margin-right: 0;
}
@ -99,11 +108,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;
}
}
@ -127,31 +135,24 @@
}
.red-ui-sidebar-tab-bar {
background-color: var(--red-ui-secondary-background);
flex: 0 0 auto;
display: flex;
flex-direction: column;
flex-direction: row;
flex: 0 0 auto;
align-items: center;
margin: 4px;
@include mixins.component-border;
flex-wrap: nowrap;
margin: 0;
z-index: 12;
overflow: hidden;
// border: 1px solid var(--red-ui-primary-border-color);
// background: rgba(243, 160, 204, 0.617);
&.red-ui-sidebar-left {
z-index: 10;
border: none;
margin-right: 0;
margin-left: 0;
background: var(--red-ui-primary-background);
margin-left: 5px;
background: none;
}
&.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;
border-bottom: none;
justify-content: flex-end;
}
button {
@ -162,17 +163,21 @@
align-items: center;
justify-content: center;
padding: 0;
height: 22px;
width: 22px;
height: 28px;
width: 28px;
&:not(.selected):not(:hover) {
border: none;
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;
@ -180,33 +185,55 @@
.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);
height: 28px;
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-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-top: 2px dashed var(--red-ui-form-input-border-color);
margin-top: -2px;
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;
@ -215,14 +242,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: 6px;
}
/* Deprecated -> red-ui-footer-button */
@ -239,9 +279,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 +292,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 */

View File

@ -14,4 +14,4 @@
* limitations under the License.
**/
$header-height: 36px;
$header-height: 40px;

View File

@ -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;

View File

@ -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,126 @@ 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%;
border-right: 1px solid var(--red-ui-secondary-border-color);
padding-right: 5px;
.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;
gap: 2px;
li {
min-width: 60px;
max-width: 150px;
flex: 1 1 100%;
border-color: transparent;
margin: 0;
background: transparent;
a.red-ui-tab-label {
padding: 0 6px;
text-align: center;
width: auto;
i.red-ui-tab-icon:not(.fa) {
margin-left: 0;
}
}
&: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;
}
}
}
}
#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);
}

View File

@ -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};

View File

@ -20,37 +20,37 @@
margin: 0;
overflow: hidden;
@include mixins.component-border;
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
border-top-left-radius: 6px;
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;
}
@ -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;
@ -176,11 +168,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: 12px;
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 +208,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;
}
@ -277,3 +289,58 @@ 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(200, 200, 200);
}
.red-ui-workspace-scrollbar {
position: absolute;
// background: rgba(223, 236, 230, 0.8);
}
.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: 46px;
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: 36px;
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);
}

View File

@ -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;
}

View File

@ -45,11 +45,11 @@ RED.debug = (function() {
'<a id="red-ui-sidebar-debug-pause" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-pause"></i></a>'+
'</span>'+
'<span class="button-group">'+
'<a id="red-ui-sidebar-debug-filter" style="padding-right: 5px" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span> <i style="padding-left: 5px;" class="fa fa-caret-down"></i></a>'+
'<a id="red-ui-sidebar-debug-filter" style="padding-right: 3px" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-filter"></i> <span></span> <i style="padding-left: 5px;" class="fa fa-caret-down"></i></a>'+
'</span>'+
'<span class="button-group">'+
'<a id="red-ui-sidebar-debug-clear" style="border-right: none; padding-right: 6px" class="red-ui-sidebar-header-button" href="#" data-clear-type="all"><i class="fa fa-trash"></i> <span>all</span></a>' +
'<a id="red-ui-sidebar-debug-clear-opts" style="padding: 5px; border-left: none;" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'<a id="red-ui-sidebar-debug-clear" style="border-right: none; padding-right: 2px" class="red-ui-sidebar-header-button" href="#" data-clear-type="all"><i class="fa fa-trash"></i> <span>all</span></a>' +
'<a id="red-ui-sidebar-debug-clear-opts" style="padding: 3px; border-left: none;" class="red-ui-sidebar-header-button" href="#"><i class="fa fa-caret-down"></i></a>'+
'</span></div>').appendTo(content);
var footerToolbar = $('<div>'+