Skip to content

Commit 637bfac

Browse files
authored
[454 + 1538] Default theme presets + fixes for dark themes (#2193)
1 parent 7932384 commit 637bfac

13 files changed

Lines changed: 395 additions & 25 deletions

File tree

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
[
2+
{
3+
"id": "dashboard-themes-tab",
4+
"type": "tab",
5+
"label": "Themes",
6+
"disabled": false,
7+
"info": ""
8+
},
9+
{
10+
"id": "dashboard-ui-base",
11+
"type": "ui-base",
12+
"name": "UI Name",
13+
"path": "/dashboard",
14+
"includeClientData": true,
15+
"acceptsClientConfig": [
16+
"ui-notification",
17+
"ui-control"
18+
]
19+
},
20+
{
21+
"id": "dashboard-theme-light",
22+
"type": "ui-theme",
23+
"name": "Light",
24+
"colors": {
25+
"surface": "#fcfcfd",
26+
"primary": "#0d74ce",
27+
"bgPage": "#f9f9fb",
28+
"groupBg": "#ffffff",
29+
"groupOutline": "#d9d9e0"
30+
}
31+
},
32+
{
33+
"id": "dashboard-theme-dark",
34+
"type": "ui-theme",
35+
"name": "Dark",
36+
"colors": {
37+
"surface": "#18191b",
38+
"primary": "#0d74ce",
39+
"bgPage": "#111113",
40+
"groupBg": "#212225",
41+
"groupOutline": "#363a3f"
42+
}
43+
},
44+
{
45+
"id": "dashboard-ui-page-1",
46+
"type": "ui-page",
47+
"name": "Light",
48+
"ui": "dashboard-ui-base",
49+
"path": "/page1",
50+
"icon": "",
51+
"layout": "grid",
52+
"theme": "dashboard-theme-light",
53+
"order": 1,
54+
"className": "",
55+
"visible": "true",
56+
"disabled": false
57+
},
58+
{
59+
"id": "dashboard-ui-page-2",
60+
"type": "ui-page",
61+
"name": "Dark",
62+
"ui": "dashboard-ui-base",
63+
"path": "/page2",
64+
"icon": "",
65+
"layout": "grid",
66+
"theme": "dashboard-theme-dark",
67+
"order": 2,
68+
"className": "",
69+
"visible": "true",
70+
"disabled": false
71+
},
72+
{
73+
"id": "dashboard-group-1",
74+
"type": "ui-group",
75+
"name": "G1",
76+
"page": "dashboard-ui-page-1",
77+
"width": "6",
78+
"height": "1",
79+
"order": 1,
80+
"showTitle": true,
81+
"className": "",
82+
"visible": "true",
83+
"disabled": "false"
84+
},
85+
{
86+
"id": "dashboard-group-2",
87+
"type": "ui-group",
88+
"name": "G2",
89+
"page": "dashboard-ui-page-2",
90+
"width": "6",
91+
"height": "1",
92+
"order": 1,
93+
"showTitle": true,
94+
"className": "",
95+
"visible": "true",
96+
"disabled": "false"
97+
},
98+
{
99+
"id": "dashboard-text-1",
100+
"type": "ui-text",
101+
"z": "dashboard-themes-tab",
102+
"group": "dashboard-group-1",
103+
"order": 1,
104+
"width": 6,
105+
"height": 1,
106+
"name": "",
107+
"label": "Themed",
108+
"format": "",
109+
"layout": "row-left",
110+
"style": false,
111+
"font": "",
112+
"fontSize": 16,
113+
"color": "",
114+
"className": "",
115+
"wires": []
116+
},
117+
{
118+
"id": "dashboard-text-2",
119+
"type": "ui-text",
120+
"z": "dashboard-themes-tab",
121+
"group": "dashboard-group-2",
122+
"order": 1,
123+
"width": 6,
124+
"height": 1,
125+
"name": "",
126+
"label": "Themed",
127+
"format": "",
128+
"layout": "row-left",
129+
"style": false,
130+
"font": "",
131+
"fontSize": 16,
132+
"color": "",
133+
"className": "",
134+
"wires": []
135+
}
136+
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
describe('Node-RED Dashboard 2.0 - Theme', () => {
2+
beforeEach(() => {
3+
cy.deployFixture('dashboard-themes')
4+
})
5+
6+
it('applies a light theme — the page renders on the light background', () => {
7+
cy.visit('/dashboard/page1')
8+
cy.get('.nrdb-app').should('have.css', 'background-color', 'rgb(249, 249, 251)')
9+
})
10+
11+
it('applies a dark theme — the page renders on the dark background', () => {
12+
cy.visit('/dashboard/page2')
13+
cy.get('.nrdb-app').should('have.css', 'background-color', 'rgb(17, 17, 19)')
14+
})
15+
})

nodes/config/locales/en-US/ui_base.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,15 @@
6767
"themes": {
6868
"header": "Themes",
6969
"theme": "Theme",
70-
"defaultTheme": "Default Theme"
70+
"defaultTheme": "Default Theme",
71+
"presetHint": "Theme preset",
72+
"presets": {
73+
"light": "Light",
74+
"dark": "Dark",
75+
"dracula": "Dracula",
76+
"nord": "Nord",
77+
"sepia": "Sepia"
78+
}
7179
},
7280
"colors": {
7381
"light": "var(--nrdb-node-light)",

nodes/config/locales/en-US/ui_theme.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"ui-theme": {
33
"label": {
44
"themeName": "Theme Name",
5+
"preset": "Preset",
6+
"custom": "Custom",
57
"colors": "Colors",
68
"dashboard": "Dashboard",
79
"header": "Header",

nodes/config/ui_base.html

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -911,7 +911,36 @@
911911
return pageNode
912912
}
913913

914-
function createThemeNode ({ name } = {}) {
914+
const THEME_PRESETS = {
915+
version: 1,
916+
presets: {
917+
light: {
918+
colors: { surface: '#fcfcfd', primary: '#0d74ce', bgPage: '#f9f9fb', groupBg: '#ffffff', groupOutline: '#d9d9e0' },
919+
sizes: { density: 'default', pagePadding: '12px', groupGap: '12px', groupBorderRadius: '4px', widgetGap: '12px' }
920+
},
921+
dark: {
922+
colors: { surface: '#18191b', primary: '#0d74ce', bgPage: '#111113', groupBg: '#212225', groupOutline: '#363a3f' },
923+
sizes: { density: 'default', pagePadding: '12px', groupGap: '12px', groupBorderRadius: '4px', widgetGap: '12px' }
924+
},
925+
dracula: {
926+
colors: { surface: '#21222c', primary: '#bd93f9', bgPage: '#282a36', groupBg: '#343746', groupOutline: '#44475a' },
927+
sizes: { density: 'default', pagePadding: '12px', groupGap: '12px', groupBorderRadius: '4px', widgetGap: '12px' }
928+
},
929+
nord: {
930+
colors: { surface: '#3b4252', primary: '#88c0d0', bgPage: '#2e3440', groupBg: '#3b4252', groupOutline: '#4c566a' },
931+
sizes: { density: 'default', pagePadding: '12px', groupGap: '12px', groupBorderRadius: '4px', widgetGap: '12px' }
932+
},
933+
sepia: {
934+
colors: { surface: '#f2e5bc', primary: '#af3a03', bgPage: '#ebdbb2', groupBg: '#fbf1c7', groupOutline: '#d5c4a1' },
935+
sizes: { density: 'default', pagePadding: '12px', groupGap: '12px', groupBorderRadius: '4px', widgetGap: '12px' }
936+
}
937+
}
938+
}
939+
window.NRDB_THEME_PRESETS = THEME_PRESETS
940+
941+
let themePreset = Object.keys(THEME_PRESETS.presets)[0]
942+
943+
function createThemeNode ({ name, preset = 'light' } = {}) {
915944
if (RED._db2debug) { console.log('dashboard 2: ui_base.html: createThemeNode', name) }
916945
const theme = RED.nodes.getType('ui-theme')
917946
let themeName = name
@@ -932,6 +961,11 @@
932961
...mapDefaults(theme.defaults),
933962
name: themeName || c_('themes.defaultTheme')
934963
}
964+
const presetDef = THEME_PRESETS.presets[preset]
965+
if (presetDef) {
966+
themeNode.colors = { ...presetDef.colors }
967+
themeNode.sizes = { ...presetDef.sizes }
968+
}
935969
return themeNode
936970
}
937971

@@ -1945,11 +1979,32 @@
19451979
// button group
19461980
const buttonGroup = $('<div>', { class: 'nrdb2-sb-list-button-group' }).appendTo(themeHeader)
19471981

1948-
// add theme button
1982+
const presetKeys = Object.keys(THEME_PRESETS.presets)
1983+
const presetLabel = (k) => c_('themes.presets.' + k)
1984+
const themePresetButton = $('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"></a>').appendTo(buttonGroup)
1985+
function renderThemePresetButton () {
1986+
themePresetButton.text(presetLabel(themePreset) + ' ')
1987+
$('<i class="fa fa-caret-down"></i>').appendTo(themePresetButton)
1988+
}
1989+
renderThemePresetButton()
1990+
const themePresetTooltip = RED.popover.tooltip(themePresetButton, c_('themes.presetHint'))
1991+
themePresetButton.click(function (evt) {
1992+
evt.preventDefault()
1993+
themePresetTooltip.close(true)
1994+
RED.popover.menu({
1995+
options: presetKeys.map((key) => ({
1996+
label: presetLabel(key),
1997+
onselect: function () { themePreset = key; renderThemePresetButton() }
1998+
}))
1999+
}).show({ target: themePresetButton })
2000+
})
19492001
$('<a href="#" class="editor-button editor-button-small nr-db-sb-list-header-button"><i class="fa fa-plus"></i> ' + c_('themes.theme') + '</a>')
19502002
.click(function (evt) {
1951-
themesOL.editableList('addItem')
19522003
evt.preventDefault()
2004+
const theme = createThemeNode({ preset: themePreset })
2005+
addNode(theme)
2006+
themesOL.editableList('addItem', theme)
2007+
RED.editor.editConfig('', theme.type, theme.id)
19532008
})
19542009
.appendTo(buttonGroup)
19552010

nodes/config/ui_theme.html

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@
99
// colors
1010
colors: {
1111
value: {
12-
surface: '#ffffff',
13-
primary: '#0094CE',
14-
bgPage: '#eeeeee',
12+
surface: '#fcfcfd',
13+
primary: '#0d74ce',
14+
bgPage: '#f9f9fb',
1515
groupBg: '#ffffff',
16-
groupOutline: '#cccccc'
16+
groupOutline: '#d9d9e0'
1717
}
1818
},
1919
// sizes
@@ -35,13 +35,13 @@
3535
if (!this.colors) {
3636
this.colors = {}
3737
// set default values
38-
this.colors.surface = '#ffffff'
39-
this.colors.primary = '#0094CE'
38+
this.colors.surface = '#fcfcfd'
39+
this.colors.primary = '#0d74ce'
4040
// pages
41-
this.colors.bgPage = '#eeeeee'
41+
this.colors.bgPage = '#f9f9fb'
4242
// groups
4343
this.colors.groupBg = '#ffffff'
44-
this.colors.groupOutline = '#cccccc'
44+
this.colors.groupOutline = '#d9d9e0'
4545
}
4646

4747
if (!this.sizes) {
@@ -93,6 +93,47 @@
9393
})
9494
wrapper.css('background-color', colorPicker.val())
9595
})
96+
97+
const presets = window.NRDB_THEME_PRESETS
98+
const presetSelect = $('#nrdb-theme-preset')
99+
const presetColorKeys = ['surface', 'primary', 'bgPage', 'groupBg', 'groupOutline']
100+
const presetName = (key) => RED._('@flowfuse/node-red-dashboard/ui-base:ui-base.themes.presets.' + key)
101+
let applyingPreset = false
102+
if (presets) {
103+
Object.keys(presets.presets).forEach((key) => {
104+
presetSelect.append($('<option>').val(key).text(presetName(key)))
105+
})
106+
}
107+
presetSelect.append($('<option>').val('custom').text(RED._('@flowfuse/node-red-dashboard/ui-theme:ui-theme.label.custom')))
108+
const matchPreset = function () {
109+
if (!presets) { return 'custom' }
110+
for (const key of Object.keys(presets.presets)) {
111+
const c = presets.presets[key].colors
112+
if (presetColorKeys.every((k) => ($('#node-config-input-' + k).val() || '').toLowerCase() === (c[k] || '').toLowerCase())) {
113+
return key
114+
}
115+
}
116+
return 'custom'
117+
}
118+
const applyPreset = function (key) {
119+
const def = presets && presets.presets[key]
120+
if (!def) { return }
121+
applyingPreset = true
122+
Object.keys(def.colors).forEach((k) => $('#node-config-input-' + k).val(def.colors[k]).trigger('change'))
123+
Object.keys(def.sizes).forEach((k) => $('#node-config-input-' + k).val(def.sizes[k]))
124+
applyingPreset = false
125+
}
126+
presetSelect.val(matchPreset())
127+
presetSelect.on('change', function () {
128+
const key = presetSelect.val()
129+
if (key === 'custom') { return }
130+
applyPreset(key)
131+
})
132+
presetColorKeys.forEach((k) => {
133+
$('#node-config-input-' + k).on('change input', function () {
134+
if (!applyingPreset) { presetSelect.val('custom') }
135+
})
136+
})
96137
},
97138
oneditsave: function () {
98139
const colors = {}
@@ -121,6 +162,10 @@
121162
<label for="node-config-input-name"><i class="fa fa-tag"></i> <span data-i18n="node-red:common.label.name"></label>
122163
<input type="text" id="node-config-input-name" data-i18n="[placeholder]node-red:common.label.name">
123164
</div>
165+
<div class="form-row">
166+
<label for="nrdb-theme-preset"><i class="fa fa-paint-brush"></i> <span data-i18n="ui-theme.label.preset"></span></label>
167+
<select id="nrdb-theme-preset"></select>
168+
</div>
124169
<h3 style="margin: 0; margin-bottom: 6px; border-bottom: 1px solid #eee; padding-bottom: 3px;" data-i18n="ui-theme.label.colors"></h3>
125170
<div class="form-row" style="margin-bottom: 3px;">
126171
<h4 style="margin-bottom: 0;"><i class="fa fa-bar-chart"></i> <span data-i18n="ui-theme.label.dashboard"></h4>

0 commit comments

Comments
 (0)