diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
index d318f476cb..7d4d2ba5e7 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/deploy.js
@@ -67,6 +67,11 @@ RED.deploy = (function() {
''+
''+
'').prependTo(".red-ui-header-toolbar");
+
+ // Add dark mode toggle button
+ $('
').prependTo(".red-ui-header-toolbar");
const mainMenuItems = [
{id:"deploymenu-item-full",toggle:"deploy-type",icon:"red/images/deploy-full.svg",label:RED._("deploy.full"),sublabel:RED._("deploy.fullDesc"),selected: true, onselect:function(s) { if(s){changeDeploymentType("full")}}},
{id:"deploymenu-item-flow",toggle:"deploy-type",icon:"red/images/deploy-flows.svg",label:RED._("deploy.modifiedFlows"),sublabel:RED._("deploy.modifiedFlowsDesc"), onselect:function(s) {if(s){changeDeploymentType("flows")}}},
@@ -103,6 +108,17 @@ RED.deploy = (function() {
save();
});
+ // Add theme toggle functionality
+ $('#red-ui-header-button-theme-toggle').on("click", function(event) {
+ event.preventDefault();
+ toggleTheme();
+ });
+
+ // Initialize theme on load
+ if (type == "default") {
+ setTimeout(initTheme, 100); // Delay to ensure DOM is ready
+ }
+
RED.actions.add("core:deploy-flows",save);
if (type === "default") {
if (RED.settings.runtimeState && RED.settings.runtimeState.ui === true) {
@@ -699,11 +715,75 @@ RED.deploy = (function() {
}, delta);
});
}
+
+ var darkModeMediaQuery = null;
+ var darkModeListener = null;
+
+ function applyTheme(theme) {
+ // Remove any existing media query listener
+ if (darkModeMediaQuery && darkModeListener) {
+ darkModeMediaQuery.removeEventListener('change', darkModeListener);
+ darkModeListener = null;
+ }
+
+ let actualTheme = theme;
+
+ if (theme === 'auto') {
+ // Set up media query listener for automatic mode
+ darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ actualTheme = darkModeMediaQuery.matches ? 'dark' : 'light';
+
+ // Create listener function
+ darkModeListener = function(e) {
+ const newTheme = e.matches ? 'dark' : 'light';
+ document.body.setAttribute('data-theme', newTheme);
+ updateThemeIcon(newTheme);
+ };
+
+ darkModeMediaQuery.addEventListener('change', darkModeListener);
+ }
+
+ // Apply the theme
+ document.body.setAttribute('data-theme', actualTheme);
+ localStorage.setItem('nodeRedTheme', theme);
+ updateThemeIcon(actualTheme);
+ }
+
+ function updateThemeIcon(theme) {
+ const toggleIcon = $('#red-ui-header-button-theme-toggle i');
+ toggleIcon.removeClass('fa-moon fa-sun');
+ toggleIcon.addClass(theme === 'dark' ? 'fa-sun' : 'fa-moon');
+ }
+
+ function toggleTheme() {
+ const savedThemeSetting = localStorage.getItem('nodeRedTheme') || 'dark';
+
+ // Cycle through: dark -> light -> auto -> dark
+ let newTheme;
+ if (savedThemeSetting === 'dark') {
+ newTheme = 'light';
+ } else if (savedThemeSetting === 'light') {
+ newTheme = 'auto';
+ } else {
+ newTheme = 'dark';
+ }
+
+ applyTheme(newTheme);
+ }
+
+ function initTheme() {
+ // Initialize theme from localStorage or default to dark
+ const savedTheme = localStorage.getItem('nodeRedTheme') || 'dark';
+ applyTheme(savedTheme);
+ }
+
return {
init: init,
setDeployInflight: function(state) {
deployInflight = state;
- }
+ },
+ initTheme: initTheme,
+ applyTheme: applyTheme
}
})();
diff --git a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js
index 3bc99e6026..b21d7adf60 100644
--- a/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js
+++ b/packages/node_modules/@node-red/editor-client/src/js/ui/userSettings.js
@@ -116,11 +116,29 @@ RED.userSettings = (function() {
{setting:"editor-language",local: true, label:"menu.label.view.language",options:function(done){ done([{val:'',text:RED._('menu.label.view.browserDefault')}].concat(RED.settings.theme("languages").map(localeToName).sort(compText))) }},
]
},
- // {
- // options: [
- // {setting:"theme", label:"Theme",options:function(done){ done([{val:'',text:'default'}].concat(RED.settings.theme("themes"))) }},
- // ]
- // },
+ {
+ title: "Appearance",
+ options: [
+ {
+ setting:"nodeRedTheme",
+ local: true,
+ label:"Theme",
+ default: 'dark',
+ options:function(done){
+ done([
+ {val:'light',text:'Light theme'},
+ {val:'dark',text:'Dark theme'},
+ {val:'auto',text:'Automatic (follow system)'}
+ ])
+ },
+ onchange: function(val) {
+ if (RED.deploy && RED.deploy.applyTheme) {
+ RED.deploy.applyTheme(val);
+ }
+ }
+ },
+ ]
+ },
{
title: "menu.label.view.view",
options: [
@@ -182,6 +200,9 @@ RED.userSettings = (function() {
var initialState;
if (opt.local) {
initialState = localStorage.getItem(opt.setting);
+ if (initialState === null && opt.hasOwnProperty('default')) {
+ initialState = opt.default;
+ }
} else if (opt.global) {
initialState = RED.settings.get(opt.setting);
} else {
@@ -229,6 +250,14 @@ RED.userSettings = (function() {
var opt = allSettings[id];
if (opt.local) {
localStorage.setItem(opt.setting,value);
+ // Call onchange callback for local settings too
+ var callback = opt.onchange;
+ if (typeof callback === 'string') {
+ callback = RED.actions.get(callback);
+ }
+ if (callback) {
+ callback.call(opt,value);
+ }
} else if (opt.global) {
RED.settings.set(opt.setting, value)
} else {
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 9f34d8fde9..8bfe53f4ce 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
@@ -307,3 +307,85 @@
--red-ui-user-profile-colors-#{"" + $current-color}: #{map.get(colors.$user-profile-colors, $current-color)};
}
}
+
+// Dark theme overrides
+[data-theme="dark"] {
+ // Main backgrounds
+ --red-ui-primary-background: #1e1e1e;
+ --red-ui-secondary-background: #2d2d2d;
+ --red-ui-secondary-background-selected: #3a3a3a;
+ --red-ui-secondary-background-inactive: #272727;
+ --red-ui-secondary-background-hover: #353535;
+ --red-ui-secondary-background-disabled: #232323;
+ --red-ui-tertiary-background: #333333;
+
+ // Text colors
+ --red-ui-primary-text-color: #ffffff;
+ --red-ui-secondary-text-color: #cccccc;
+ --red-ui-secondary-text-color-focus: #dddddd;
+ --red-ui-secondary-text-color-hover: #dddddd;
+ --red-ui-secondary-text-color-active: #dddddd;
+ --red-ui-secondary-text-color-selected: #dddddd;
+ --red-ui-secondary-text-color-inactive: #dddddd;
+ --red-ui-secondary-text-color-disabled: #666666;
+ --red-ui-secondary-text-color-disabled-active: #888888;
+ --red-ui-secondary-text-color-disabled-inactive: #777777;
+ --red-ui-tertiary-text-color: #999999;
+ --red-ui-header-text-color: #ffffff;
+
+ // Border colors
+ --red-ui-primary-border-color: #404040;
+ --red-ui-secondary-border-color: #555555;
+ --red-ui-tertiary-border-color: #484848;
+
+ // View/Canvas
+ --red-ui-view-background: #2d2d2d;
+ --red-ui-view-grid-color: #404040;
+
+ // Palette
+ --red-ui-palette-header-background: #1e1e1e;
+ --red-ui-palette-header-color: #ffffff;
+ --red-ui-palette-content-background: #2d2d2d;
+
+ // Forms
+ --red-ui-form-background: #2d2d2d;
+ --red-ui-form-text-color: #ffffff;
+ --red-ui-form-input-background: #333333;
+ --red-ui-form-input-border-color: #555555;
+ --red-ui-form-button-background: #3a3a3a;
+
+ // Button backgrounds for dark theme
+ --red-ui-workspace-button-background: #3a3a3a;
+ --red-ui-workspace-button-background-hover: #4a4a4a;
+ --red-ui-workspace-button-background-active: #505050;
+ --red-ui-workspace-button-background-disabled: #2a2a2a;
+
+ // Button text colors
+ --red-ui-workspace-button-color: #cccccc;
+ --red-ui-workspace-button-color-hover: #ffffff;
+ --red-ui-workspace-button-color-active: #ffffff;
+ --red-ui-workspace-button-color-disabled: #666666;
+ --red-ui-workspace-button-color-selected: #ffffff;
+ --red-ui-workspace-button-color-focus: #ffffff;
+
+ // Primary button colors
+ --red-ui-workspace-button-color-primary: #ffffff;
+ --red-ui-workspace-button-background-primary: #AD1625;
+ --red-ui-workspace-button-background-primary-hover: #8E0A1E;
+
+ // List items
+ --red-ui-list-item-color: #ffffff;
+ --red-ui-list-item-secondary-color: #cccccc;
+ --red-ui-list-item-background: #2d2d2d;
+ --red-ui-list-item-background-hover: #353535;
+ --red-ui-list-item-background-selected: #404040;
+
+ // Tabs
+ --red-ui-tab-background: #2d2d2d;
+ --red-ui-tab-background-active: #2d2d2d;
+ --red-ui-tab-background-selected: #3a3a3a;
+ --red-ui-tab-background-inactive: #272727;
+ --red-ui-tab-background-hover: #353535;
+ --red-ui-tab-text-color-active: #ffffff;
+ --red-ui-tab-text-color-inactive: #cccccc;
+}