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