diff --git a/main/http_server/axe-os/src/app/components/design/design-component.scss b/main/http_server/axe-os/src/app/components/design/design-component.scss index 73c063b380..c36994ba55 100644 --- a/main/http_server/axe-os/src/app/components/design/design-component.scss +++ b/main/http_server/axe-os/src/app/components/design/design-component.scss @@ -10,7 +10,7 @@ position: relative; .selected-icon { - color: white; + color: contrast-color(var(--dot-bg)); font-size: 1rem; position: absolute; top: 50%; diff --git a/main/http_server/axe-os/src/app/components/design/theme-config.component.html b/main/http_server/axe-os/src/app/components/design/theme-config.component.html new file mode 100644 index 0000000000..eca3f4c1ba --- /dev/null +++ b/main/http_server/axe-os/src/app/components/design/theme-config.component.html @@ -0,0 +1,44 @@ +
+
+
+
Color Scheme
+
+
+ + +
+
+ + +
+
+
+ +
+
Theme Color
+
+
+ +
{{theme.name}}
+
+
+
+ +
+
Custom Color
+
+ + {{currentColor}} +
+
+
+
diff --git a/main/http_server/axe-os/src/app/components/design/theme-config.component.ts b/main/http_server/axe-os/src/app/components/design/theme-config.component.ts index 40bad8cfab..b36ac852f0 100644 --- a/main/http_server/axe-os/src/app/components/design/theme-config.component.ts +++ b/main/http_server/axe-os/src/app/components/design/theme-config.component.ts @@ -7,215 +7,82 @@ import { ThemeService } from '../../services/theme.service'; interface ThemeOption { name: string; primaryColor: string; - accentColors: { - [key: string]: string; - }; } @Component({ selector: 'app-theme-config', - template: ` -
-
-
-
Color Scheme
-
-
- - -
-
- - -
-
-
- -
-
Theme Colors
-
-
- -
{{theme.name}}
-
-
-
-
-
- `, + templateUrl: './theme-config.component.html', styleUrls: ['./design-component.scss'] }) export class ThemeConfigComponent implements OnInit { selectedScheme: string; currentColor: string = ''; themes: ThemeOption[] = [ - { - name: 'Orange', - primaryColor: '#F7931A', - accentColors: { - '--primary-color': '#F7931A', - '--primary-color-text': '#ffffff', - '--highlight-bg': '#F7931A', - '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(247,147,26,0.2)', - // PrimeNG Slider - '--slider-bg': '#dee2e6', - '--slider-range-bg': '#F7931A', - '--slider-handle-bg': '#F7931A', - // Progress Bar - '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#F7931A', - // PrimeNG Checkbox - '--checkbox-border': '#F7931A', - '--checkbox-bg': '#F7931A', - '--checkbox-hover-bg': '#e58617', - // PrimeNG Button - '--button-bg': '#F7931A', - '--button-hover-bg': '#e58617', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #F7931A', - // Toggle button - '--togglebutton-bg': '#F7931A', - '--togglebutton-border': '1px solid #F7931A', - '--togglebutton-hover-bg': '#e58617', - '--togglebutton-hover-border': '1px solid #e58617', - '--togglebutton-text-color': '#ffffff' - } - }, - { - name: 'Red', - primaryColor: '#F80421', - accentColors: { - '--primary-color': '#F80421', - '--primary-color-text': '#ffffff', - '--highlight-bg': '#F80421', - '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(255,64,50,0.2)', - // PrimeNG Slider - '--slider-bg': '#dee2e6', - '--slider-range-bg': '#F80421', - '--slider-handle-bg': '#F80421', - // Progress Bar - '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#F80421', - // PrimeNG Checkbox - '--checkbox-border': '#F80421', - '--checkbox-bg': '#F80421', - '--checkbox-hover-bg': '#e63c2e', - // PrimeNG Button - '--button-bg': '#F80421', - '--button-hover-bg': '#e63c2e', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #F80421', - // Toggle button - '--togglebutton-bg': '#F80421', - '--togglebutton-border': '1px solid #F80421', - '--togglebutton-hover-bg': '#e63c2e', - '--togglebutton-hover-border': '1px solid #e63c2e', - '--togglebutton-text-color': '#ffffff' - } - }, - { - name: 'Blue', - primaryColor: '#2196f3', - accentColors: { - '--primary-color': '#2196f3', - '--primary-color-text': '#ffffff', - '--highlight-bg': '#2196f3', - '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(33,150,243,0.2)', - // PrimeNG Slider - '--slider-bg': '#dee2e6', - '--slider-range-bg': '#2196f3', - '--slider-handle-bg': '#2196f3', - // Progress Bar - '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#2196f3', - // PrimeNG Checkbox - '--checkbox-border': '#2196f3', - '--checkbox-bg': '#2196f3', - '--checkbox-hover-bg': '#1e88e5', - // PrimeNG Button - '--button-bg': '#2196f3', - '--button-hover-bg': '#1e88e5', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #2196f3', - // Toggle button - '--togglebutton-bg': '#2196f3', - '--togglebutton-border': '1px solid #2196f3', - '--togglebutton-hover-bg': '#1e88e5', - '--togglebutton-hover-border': '1px solid #1e88e5', - '--togglebutton-text-color': '#ffffff' - } - }, - { - name: 'Green', - primaryColor: '#4caf50', - accentColors: { - '--primary-color': '#4caf50', - '--primary-color-text': '#ffffff', - '--highlight-bg': '#4caf50', - '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(76,175,80,0.2)', - // PrimeNG Slider - '--slider-bg': '#dee2e6', - '--slider-range-bg': '#4caf50', - '--slider-handle-bg': '#4caf50', - // Progress Bar - '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#4caf50', - // PrimeNG Checkbox - '--checkbox-border': '#4caf50', - '--checkbox-bg': '#4caf50', - '--checkbox-hover-bg': '#43a047', - // PrimeNG Button - '--button-bg': '#4caf50', - '--button-hover-bg': '#43a047', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #4caf50', - // Toggle button - '--togglebutton-bg': '#4caf50', - '--togglebutton-border': '1px solid #4caf50', - '--togglebutton-hover-bg': '#43a047', - '--togglebutton-hover-border': '1px solid #43a047', - '--togglebutton-text-color': '#ffffff' - } - }, - { - name: 'Purple', - primaryColor: '#b340fa', - accentColors: { - '--primary-color': '#b340fa', - '--primary-color-text': '#ffffff', - '--highlight-bg': '#b340fa', - '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(156,39,176,0.2)', - // PrimeNG Slider - '--slider-bg': '#dee2e6', - '--slider-range-bg': '#b340fa', - '--slider-handle-bg': '#b340fa', - // Progress Bar - '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#b340fa', - // PrimeNG Checkbox - '--checkbox-border': '#b340fa', - '--checkbox-bg': '#b340fa', - '--checkbox-hover-bg': '#8e24aa', - // PrimeNG Button - '--button-bg': '#b340fa', - '--button-hover-bg': '#8e24aa', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #b340fa', - // Toggle button - '--togglebutton-bg': '#b340fa', - '--togglebutton-border': '1px solid #b340fa', - '--togglebutton-hover-bg': '#8e24aa', - '--togglebutton-hover-border': '1px solid #8e24aa', - '--togglebutton-text-color': '#ffffff' - } - } + // === Reds & Oranges (energetic & warm) === + { name: 'Bright Red', primaryColor: '#F80421' }, + { name: 'Coral Red', primaryColor: '#FF5252' }, + { name: 'Tomato Red', primaryColor: '#FF6347' }, + { name: 'Burnt Orange', primaryColor: '#E64A19' }, + { name: 'Vivid Orange', primaryColor: '#FF7A00' }, + { name: 'Bitcoin Orange', primaryColor: '#F7931A' }, + + // === Yellows & Golds === + { name: 'Golden Amber', primaryColor: '#FFB300' }, + { name: 'Metallic Gold', primaryColor: '#E6B800' }, + { name: 'Lemon Yellow', primaryColor: '#FFDD00' }, + { name: 'Chartreuse', primaryColor: '#C0D000' }, + + // === Greens === + { name: 'Emerald Green', primaryColor: '#4CAF50' }, + { name: 'Lime Green', primaryColor: '#8BC34A' }, + { name: 'Mint Green', primaryColor: '#4DB6AC' }, + { name: 'Spring Green', primaryColor: '#66FF99' }, + { name: 'Olive', primaryColor: '#808000' }, + + // === Teals & Cyans === + { name: 'Forest Teal', primaryColor: '#00796B' }, + { name: 'Bright Cyan', primaryColor: '#00BCD4' }, + { name: 'Turquoise', primaryColor: '#40E0D0' }, + { name: 'Aqua', primaryColor: '#00FFFF' }, + + // === Blues === + { name: 'Sky Blue', primaryColor: '#29B6F6' }, + { name: 'Light Blue', primaryColor: '#81D4FA' }, + { name: 'Deep Blue', primaryColor: '#1976D2' }, + { name: 'Royal Blue', primaryColor: '#2A5CDB' }, + { name: 'Indigo', primaryColor: '#3F51B5' }, + { name: 'Navy Blue', primaryColor: '#1A3C7E' }, + { name: 'Steel Blue', primaryColor: '#4682B4' }, + + // === Purples & Violets === + { name: 'Hot Pink', primaryColor: '#E91E63' }, + { name: 'Rose Pink', primaryColor: '#FF4081' }, + { name: 'Magenta Purple', primaryColor: '#9C27B0' }, + { name: 'Deep Violet', primaryColor: '#512DA8' }, + { name: 'Lavender', primaryColor: '#9575CD' }, + { name: 'Plum', primaryColor: '#9B2A6E' }, + { name: 'Orchid', primaryColor: '#DA70D6' }, + + // === Browns & Neutrals === + { name: 'Earth Brown', primaryColor: '#795548' }, + { name: 'Warm Taupe', primaryColor: '#8D6E63' }, + { name: 'Chocolate', primaryColor: '#5D4037' }, + { name: 'Slate Grey', primaryColor: '#607D8B' }, + { name: 'Blue Grey', primaryColor: '#455A64' }, + { name: 'Cool Grey', primaryColor: '#78909C' }, + + // === Additional Distinct Accents === + { name: 'Peach', primaryColor: '#FFAB91' }, + { name: 'Apricot', primaryColor: '#FFD8B1' }, + { name: 'Sand', primaryColor: '#E0C9A8' }, + { name: 'Terracotta', primaryColor: '#E07A5F' }, + { name: 'Rust', primaryColor: '#B74C2E' }, + { name: 'Crimson', primaryColor: '#B71C1C' }, + { name: 'Berry', primaryColor: '#8E24AA' }, + { name: 'Periwinkle', primaryColor: '#8C9EFF' }, + { name: 'Electric Purple', primaryColor: '#BB86FC' }, + { name: 'Neon Green', primaryColor: '#39FF14' }, + { name: 'Acid Lime', primaryColor: '#CCFF00' } ]; private destroy$ = new Subject(); @@ -236,9 +103,10 @@ export class ThemeConfigComponent implements OnInit { if (settings.colorScheme) { this.selectedScheme = settings.colorScheme; } - if (settings.accentColors) { - this.applyThemeColors(settings.accentColors); - this.currentColor = settings.accentColors['--primary-color']; + if (settings.primaryColor) { + this.currentColor = settings.primaryColor.toUpperCase(); + const accentColors = ThemeService.generateThemeVariables(this.currentColor); + this.applyThemeColors(accentColors); } } }, @@ -263,7 +131,7 @@ export class ThemeConfigComponent implements OnInit { config.colorScheme = scheme; this.layoutService.config.set(config); - this.themeService.saveThemeSettings({ colorScheme: scheme }) + this.themeService.saveThemeSettings({ colorScheme: scheme, primaryColor: this.currentColor }) .pipe(takeUntil(this.destroy$)) .subscribe({ error: (error) => console.error('Error saving theme settings:', error) @@ -271,12 +139,28 @@ export class ThemeConfigComponent implements OnInit { } changeTheme(theme: ThemeOption) { - this.applyThemeColors(theme.accentColors); this.currentColor = theme.primaryColor; + const accentColors = ThemeService.generateThemeVariables(this.currentColor); + this.applyThemeColors(accentColors); + + this.themeService.saveThemeSettings({ + colorScheme: this.selectedScheme, + primaryColor: this.currentColor + }).pipe(takeUntil(this.destroy$)) + .subscribe({ + error: (error) => console.error('Error saving theme settings:', error) + }); + } + + onCustomColorChange(event: any) { + const color = event.target.value; + this.currentColor = color.toUpperCase(); + const accentColors = ThemeService.generateThemeVariables(this.currentColor); + this.applyThemeColors(accentColors); this.themeService.saveThemeSettings({ colorScheme: this.selectedScheme, - accentColors: theme.accentColors + primaryColor: this.currentColor }).pipe(takeUntil(this.destroy$)) .subscribe({ error: (error) => console.error('Error saving theme settings:', error) diff --git a/main/http_server/axe-os/src/app/components/home/home.component.html b/main/http_server/axe-os/src/app/components/home/home.component.html index 6fcaddad2e..c1f7284843 100644 --- a/main/http_server/axe-os/src/app/components/home/home.component.html +++ b/main/http_server/axe-os/src/app/components/home/home.component.html @@ -74,7 +74,7 @@ - + {{ info[i.key] | hashSuffix }} @@ -584,7 +584,7 @@

Hashrate R {{ i + 1 }} {{ domain | hashSuffix }} diff --git a/main/http_server/axe-os/src/app/components/home/home.component.scss b/main/http_server/axe-os/src/app/components/home/home.component.scss index 3a5789407d..6ae23c0131 100644 --- a/main/http_server/axe-os/src/app/components/home/home.component.scss +++ b/main/http_server/axe-os/src/app/components/home/home.component.scss @@ -17,6 +17,8 @@ td { transition: background-color 2s ease-in-out; + background-color: var(--heatmap-bg); + color: contrast-color(var(--primary-color)); } } diff --git a/main/http_server/axe-os/src/app/layout/service/app.layout.service.ts b/main/http_server/axe-os/src/app/layout/service/app.layout.service.ts index 9fa231f42d..11d5aac906 100644 --- a/main/http_server/axe-os/src/app/layout/service/app.layout.service.ts +++ b/main/http_server/axe-os/src/app/layout/service/app.layout.service.ts @@ -100,40 +100,23 @@ export class LayoutService { ...this._config, colorScheme: settings.colorScheme, }; - // Apply accent colors if they exist - if (settings.accentColors) { - Object.entries(settings.accentColors).forEach(([key, value]) => { - document.documentElement.style.setProperty(key, value); - }); - } + // Apply accent colors dynamically + const accentColors = ThemeService.generateThemeVariables(settings.primaryColor); + Object.entries(accentColors).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); } else { // Save default red dark theme if no settings exist + const defaultPrimary = '#F80421'; this.themeService.saveThemeSettings({ colorScheme: 'dark', - accentColors: { - '--primary-color': '#F80421', - '--primary-color-text': '#ffffff', - '--highlight-bg': '#F80421', - '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(248,4,33,0.2)', - '--slider-bg': '#dee2e6', - '--slider-range-bg': '#F80421', - '--slider-handle-bg': '#F80421', - '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#F80421', - '--checkbox-border': '#F80421', - '--checkbox-bg': '#F80421', - '--checkbox-hover-bg': '#df031d', - '--button-bg': '#F80421', - '--button-hover-bg': '#df031d', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #F80421', - '--togglebutton-bg': '#F80421', - '--togglebutton-border': '1px solid #F80421', - '--togglebutton-hover-bg': '#df031d', - '--togglebutton-hover-border': '1px solid #df031d', - '--togglebutton-text-color': '#ffffff' - } + primaryColor: defaultPrimary }).subscribe(); + + const accentColors = ThemeService.generateThemeVariables(defaultPrimary); + Object.entries(accentColors).forEach(([key, value]) => { + document.documentElement.style.setProperty(key, value); + }); } // Update signal with config this.config.set(this._config); @@ -215,8 +198,9 @@ export class LayoutService { // Load theme settings from NVS this.themeService.getThemeSettings().subscribe( settings => { - if (settings && settings.accentColors) { - Object.entries(settings.accentColors).forEach(([key, value]) => { + if (settings && settings.primaryColor) { + const accentColors = ThemeService.generateThemeVariables(settings.primaryColor); + Object.entries(accentColors).forEach(([key, value]) => { document.documentElement.style.setProperty(key, value); }); } diff --git a/main/http_server/axe-os/src/app/services/theme.service.ts b/main/http_server/axe-os/src/app/services/theme.service.ts index 64c9094e22..5ea0b0bad6 100644 --- a/main/http_server/axe-os/src/app/services/theme.service.ts +++ b/main/http_server/axe-os/src/app/services/theme.service.ts @@ -6,9 +6,7 @@ import { catchError, tap } from 'rxjs/operators'; export interface ThemeSettings { colorScheme: string; - accentColors?: { - [key: string]: string; - }; + primaryColor: string; } @Injectable({ @@ -17,35 +15,64 @@ export interface ThemeSettings { export class ThemeService { private readonly mockSettings: ThemeSettings = { colorScheme: 'dark', - accentColors: { - '--primary-color': '#F80421', + primaryColor: '#F80421' + }; + + static generateThemeVariables(primaryColor: string): { [key: string]: string } { + const hoverColor = this.shadeColor(primaryColor, -10); + const focusRingColor = this.hexToRgba(primaryColor, 0.2); + + return { + '--primary-color': primaryColor, '--primary-color-text': '#ffffff', - '--highlight-bg': '#F80421', + '--highlight-bg': primaryColor, '--highlight-text-color': '#ffffff', - '--focus-ring': '0 0 0 0.2rem rgba(255,64,50,0.2)', - // PrimeNG Slider + '--focus-ring': `0 0 0 0.2rem ${focusRingColor}`, '--slider-bg': '#dee2e6', - '--slider-range-bg': '#F80421', - '--slider-handle-bg': '#F80421', - // Progress Bar + '--slider-range-bg': primaryColor, + '--slider-handle-bg': primaryColor, '--progressbar-bg': '#dee2e6', - '--progressbar-value-bg': '#F80421', - // PrimeNG Checkbox - '--checkbox-border': '#F80421', - '--checkbox-bg': '#F80421', - '--checkbox-hover-bg': '#e63c2e', - // PrimeNG Button - '--button-bg': '#F80421', - '--button-hover-bg': '#e63c2e', - '--button-focus-shadow': '0 0 0 2px #ffffff, 0 0 0 4px #F80421', - // Toggle button - '--togglebutton-bg': '#F80421', - '--togglebutton-border': '1px solid #F80421', - '--togglebutton-hover-bg': '#e63c2e', - '--togglebutton-hover-border': '1px solid #e63c2e', + '--progressbar-value-bg': primaryColor, + '--checkbox-border': primaryColor, + '--checkbox-bg': primaryColor, + '--checkbox-hover-bg': hoverColor, + '--button-bg': primaryColor, + '--button-hover-bg': hoverColor, + '--button-focus-shadow': `0 0 0 2px #ffffff, 0 0 0 4px ${primaryColor}`, + '--togglebutton-bg': primaryColor, + '--togglebutton-border': `1px solid ${primaryColor}`, + '--togglebutton-hover-bg': hoverColor, + '--togglebutton-hover-border': `1px solid ${hoverColor}`, '--togglebutton-text-color': '#ffffff' - } - }; + }; + } + + private static shadeColor(color: string, percent: number): string { + let R = parseInt(color.substring(1, 3), 16); + let G = parseInt(color.substring(3, 5), 16); + let B = parseInt(color.substring(5, 7), 16); + + R = Math.floor(R * (100 + percent) / 100); + G = Math.floor(G * (100 + percent) / 100); + B = Math.floor(B * (100 + percent) / 100); + + R = Math.min(255, Math.max(0, R)); + G = Math.min(255, Math.max(0, G)); + B = Math.min(255, Math.max(0, B)); + + const RR = R.toString(16).padStart(2, '0'); + const GG = G.toString(16).padStart(2, '0'); + const BB = B.toString(16).padStart(2, '0'); + + return "#" + RR + GG + BB; + } + + private static hexToRgba(hex: string, alpha: number): string { + const r = parseInt(hex.substring(1, 3), 16); + const g = parseInt(hex.substring(3, 5), 16); + const b = parseInt(hex.substring(5, 7), 16); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; + } private themeSettingsSubject = new BehaviorSubject(this.mockSettings); private themeSettings$ = this.themeSettingsSubject.asObservable(); diff --git a/main/http_server/axe-os/src/styles.scss b/main/http_server/axe-os/src/styles.scss index ab6cc268c9..78f99e2362 100644 --- a/main/http_server/axe-os/src/styles.scss +++ b/main/http_server/axe-os/src/styles.scss @@ -259,3 +259,7 @@ button.color-dot { font-family: monospace; font-size: medium; } + +.text-primary { + color: var(--primary-color) !important; +} diff --git a/main/http_server/theme_api.c b/main/http_server/theme_api.c index 6f1428cff7..dda0a470c4 100644 --- a/main/http_server/theme_api.c +++ b/main/http_server/theme_api.c @@ -23,21 +23,16 @@ static esp_err_t theme_get_handler(httpd_req_t *req) set_cors_headers(req); char *scheme = nvs_config_get_string(NVS_CONFIG_THEME_SCHEME); - char *colors = nvs_config_get_string(NVS_CONFIG_THEME_COLORS); + char *primary_color = nvs_config_get_string(NVS_CONFIG_THEME_COLOR); cJSON *root = cJSON_CreateObject(); cJSON_AddStringToObject(root, "colorScheme", scheme); - - // Parse stored colors JSON string - cJSON *colors_json = cJSON_Parse(colors); - if (colors_json) { - cJSON_AddItemToObject(root, "accentColors", colors_json); - } + cJSON_AddStringToObject(root, "primaryColor", primary_color); esp_err_t res = HTTP_send_json(req, root, &theme_prebuffer_len); free(scheme); - free(colors); + free(primary_color); cJSON_Delete(root); @@ -69,10 +64,8 @@ static esp_err_t theme_post_handler(httpd_req_t *req) if ((item = cJSON_GetObjectItem(root, "colorScheme")) != NULL) { nvs_config_set_string(NVS_CONFIG_THEME_SCHEME, item->valuestring); } - if ((item = cJSON_GetObjectItem(root, "accentColors")) != NULL) { - char *colors_str = cJSON_Print(item); - nvs_config_set_string(NVS_CONFIG_THEME_COLORS, colors_str); - free(colors_str); + if ((item = cJSON_GetObjectItem(root, "primaryColor")) != NULL) { + nvs_config_set_string(NVS_CONFIG_THEME_COLOR, item->valuestring); } cJSON_Delete(root); diff --git a/main/http_server/theme_api.h b/main/http_server/theme_api.h index f5f7114b83..e2b4bd9168 100644 --- a/main/http_server/theme_api.h +++ b/main/http_server/theme_api.h @@ -4,29 +4,7 @@ #include "esp_http_server.h" #define DEFAULT_THEME "dark" -#define DEFAULT_COLORS "{ "\ - "\"--primary-color\":\"#F80421\", "\ - "\"--primary-color-text\":\"#ffffff\", "\ - "\"--highlight-bg\":\"#F80421\", "\ - "\"--highlight-text-color\":\"#ffffff\", "\ - "\"--focus-ring\":\"0 0 0 0.2rem rgba(248,4,33,0.2)\", "\ - "\"--slider-bg\":\"#dee2e6\", "\ - "\"--slider-range-bg\":\"#F80421\", "\ - "\"--slider-handle-bg\":\"#F80421\", "\ - "\"--progressbar-bg\":\"#dee2e6\", "\ - "\"--progressbar-value-bg\":\"#F80421\", "\ - "\"--checkbox-border\":\"#F80421\", "\ - "\"--checkbox-bg\":\"#F80421\", "\ - "\"--checkbox-hover-bg\":\"#df031d\", "\ - "\"--button-bg\":\"#F80421\", "\ - "\"--button-hover-bg\":\"#df031d\", "\ - "\"--button-focus-shadow\":\"0 0 0 2px #ffffff, 0 0 0 4px #F80421\", "\ - "\"--togglebutton-bg\":\"#F80421\", "\ - "\"--togglebutton-border\":\"1px solid #F80421\", "\ - "\"--togglebutton-hover-bg\":\"#df031d\", "\ - "\"--togglebutton-hover-border\":\"1px solid #df031d\", "\ - "\"--togglebutton-text-color\":\"#ffffff\" "\ - "}" +#define DEFAULT_COLOR "#F80421" // Register theme API endpoints esp_err_t register_theme_api_endpoints(httpd_handle_t server, void* ctx); diff --git a/main/nvs_config.c b/main/nvs_config.c index 2513a8be86..d076e10a30 100644 --- a/main/nvs_config.c +++ b/main/nvs_config.c @@ -13,6 +13,7 @@ #include "display.h" #include "theme_api.h" #include "scoreboard.h" +#include "cJSON.h" #define NVS_CONFIG_NAMESPACE "main" #define NVS_STR_LIMIT (4000 - 1) // See nvs_set_str @@ -92,7 +93,7 @@ static Settings settings[NVS_CONFIG_COUNT] = { [NVS_CONFIG_SELF_TEST] = {.nvs_key_name = "selftest", .type = TYPE_BOOL}, [NVS_CONFIG_SWARM] = {.nvs_key_name = "swarmconfig", .type = TYPE_STR}, [NVS_CONFIG_THEME_SCHEME] = {.nvs_key_name = "themescheme", .type = TYPE_STR, .default_value = {.str = DEFAULT_THEME}}, - [NVS_CONFIG_THEME_COLORS] = {.nvs_key_name = "themecolors", .type = TYPE_STR, .default_value = {.str = DEFAULT_COLORS}}, + [NVS_CONFIG_THEME_COLOR] = {.nvs_key_name = "themecolor", .type = TYPE_STR, .default_value = {.str = DEFAULT_COLOR}}, [NVS_CONFIG_SCOREBOARD] = {.nvs_key_name = "scoreboard", .type = TYPE_STR, .array_size = MAX_SCOREBOARD}, [NVS_CONFIG_BOARD_VERSION] = {.nvs_key_name = "boardversion", .type = TYPE_STR, .default_value = {.str = "000"}}, @@ -173,6 +174,30 @@ static void nvs_config_init_fallback(NvsConfigKey key, Settings * setting) } } } + if (key == NVS_CONFIG_THEME_COLOR) { + if (nvs_find_key(handle, setting->nvs_key_name, NULL) == ESP_ERR_NVS_NOT_FOUND) { + size_t len = 0; + esp_err_t ret = nvs_get_str(handle, "themecolors", NULL, &len); + if (ret == ESP_OK && len > 1) { + char *buf = malloc(len); + if (buf) { + ret = nvs_get_str(handle, "themecolors", buf, &len); + if (ret == ESP_OK) { + cJSON *root = cJSON_Parse(buf); + if (root) { + cJSON *primary = cJSON_GetObjectItem(root, "--primary-color"); + if (primary && primary->valuestring) { + ESP_LOGI(TAG, "Migrating NVS config themecolors to %s (%s)", setting->nvs_key_name, primary->valuestring); + nvs_set_str(handle, setting->nvs_key_name, primary->valuestring); + } + cJSON_Delete(root); + } + } + free(buf); + } + } + } + } } static void nvs_config_apply_fallback(NvsConfigKey key, Settings * setting) diff --git a/main/nvs_config.h b/main/nvs_config.h index e761ca4975..63ad302c75 100644 --- a/main/nvs_config.h +++ b/main/nvs_config.h @@ -52,7 +52,7 @@ typedef enum { NVS_CONFIG_SELF_TEST, NVS_CONFIG_SWARM, NVS_CONFIG_THEME_SCHEME, - NVS_CONFIG_THEME_COLORS, + NVS_CONFIG_THEME_COLOR, NVS_CONFIG_SCOREBOARD, NVS_CONFIG_BOARD_VERSION,