frontend: refactor color management logic to prevent infinite loops and improve theme color updates

This commit is contained in:
Carlos Santos 2025-09-30 18:16:41 +02:00
parent 883a08a130
commit 116e709f25

View File

@ -62,11 +62,27 @@ export class ConfigComponent implements OnInit {
baseThemeOptions: MeetRoomThemeMode[] = [MeetRoomThemeMode.LIGHT, MeetRoomThemeMode.DARK]; baseThemeOptions: MeetRoomThemeMode[] = [MeetRoomThemeMode.LIGHT, MeetRoomThemeMode.DARK];
// Color picker configuration // Color picker configuration
colorFields: Array<{ key: ColorField; label: string, description: string }> = [ colorFields: Array<{ key: ColorField; label: string; description: string }> = [
{ key: 'backgroundColor', label: 'Meeting Background', description: 'The background color of your meeting screen' }, {
{ key: 'primaryColor', label: 'Control buttons', description: 'The color of the main control buttons (e.g., microphone, camera)' }, key: 'backgroundColor',
{ key: 'secondaryColor', label: 'Highlights & accents', description: 'Colors for active states, borders, and participant names' }, label: 'Meeting Background',
{ key: 'surfaceColor', label: 'Side panels & boxes', description: 'Background color for side panels and dialog boxes' } description: 'The background color of your meeting screen'
},
{
key: 'primaryColor',
label: 'Control buttons',
description: 'The color of the main control buttons (e.g., microphone, camera)'
},
{
key: 'secondaryColor',
label: 'Highlights & accents',
description: 'Colors for active states, borders, and participant names'
},
{
key: 'surfaceColor',
label: 'Side panels & boxes',
description: 'Background color for side panels and dialog boxes'
}
]; ];
private initialFormValue: MeetRoomTheme | null = null; private initialFormValue: MeetRoomTheme | null = null;
@ -87,13 +103,19 @@ export class ConfigComponent implements OnInit {
} }
}; };
private lastBaseThemeValue: MeetRoomThemeMode | null = null;
private isUpdatingColors = false; // Flag to prevent infinite loops
constructor( constructor(
private configService: GlobalConfigService, private configService: GlobalConfigService,
private notificationService: NotificationService private notificationService: NotificationService
) { ) {
// Track form changes // Track form changes
this.appearanceForm.valueChanges.subscribe(() => { this.appearanceForm.valueChanges.subscribe(() => {
this.checkForChanges(); // Prevent infinite loops when updating colors programmatically
if (!this.isUpdatingColors) {
this.checkForChanges();
}
}); });
} }
@ -130,9 +152,30 @@ export class ConfigComponent implements OnInit {
inputElement?.click(); inputElement?.click();
} }
/**
*
* Checks if a color was customized from the current theme defaults
* @param colorField
* @returns
*/
hasCustomColor(colorField: ColorField): boolean { hasCustomColor(colorField: ColorField): boolean {
const formValue = this.appearanceForm.get(colorField)?.value; const formValue = this.appearanceForm.get(colorField)?.value;
return Boolean(formValue?.trim()); const baseTheme = this.appearanceForm.get('baseTheme')?.value || MeetRoomThemeMode.LIGHT;
return formValue?.trim() !== '' && formValue !== this.defaultColors[baseTheme][colorField];
}
/**
* Checks if a color was customized from the last theme defaults
* @param colorField The color field to check
* @returns true if the color differs from the last theme's default
*/
private wasColorCustomizedFromLastTheme(colorField: ColorField): boolean {
if (!this.lastBaseThemeValue) return false;
const defaultValue = this.defaultColors[this.lastBaseThemeValue][colorField];
const currentValue = this.appearanceForm.get(colorField)?.value?.trim() || '';
return currentValue !== '' && currentValue !== defaultValue;
} }
// Configuration management // Configuration management
@ -180,27 +223,72 @@ export class ConfigComponent implements OnInit {
private storeInitialValues(): void { private storeInitialValues(): void {
this.initialFormValue = { ...this.appearanceForm.value } as MeetRoomTheme; this.initialFormValue = { ...this.appearanceForm.value } as MeetRoomTheme;
this.lastBaseThemeValue = this.initialFormValue.baseTheme;
this.hasFormChanges.set(false); this.hasFormChanges.set(false);
this.hasColorChanges.set(false); this.hasColorChanges.set(false);
} }
private checkForChanges(): void { private checkForChanges(): void {
if (!this.initialFormValue) { if (!this.initialFormValue) return;
const currentFormValue = this.appearanceForm.value;
const currentBaseTheme = currentFormValue.baseTheme || MeetRoomThemeMode.LIGHT;
this.updateFormChangeState(currentFormValue);
this.handleThemeChange(currentBaseTheme);
// Update color changes state
this.updateColorChangesState(currentFormValue);
}
private updateFormChangeState(current: any): void {
const hasChanges = JSON.stringify(current) !== JSON.stringify(this.initialFormValue);
this.hasFormChanges.set(hasChanges);
}
/**
* Handles theme change by updating non-customized colors
*/
private handleThemeChange(newBaseTheme: MeetRoomThemeMode): void {
if (newBaseTheme === this.lastBaseThemeValue) return;
const newDefaults = this.defaultColors[newBaseTheme];
// Build update object with only non-customized colors
const updatedColors = this.colorFields.reduce((acc, { key }) => {
if (!this.wasColorCustomizedFromLastTheme(key)) {
acc[key] = newDefaults[key];
}
return acc;
}, {} as Partial<MeetRoomTheme>);
// Check if there are any colors to update
if (Object.keys(updatedColors).length === 0) {
return; return;
} }
// Apply updates atomically
this.applyColorUpdates(updatedColors, newBaseTheme);
}
// Check for general form changes /**
const currentFormValue = this.appearanceForm.value; * Applies color updates without triggering infinite loops
const hasFormChangesDetected = JSON.stringify(currentFormValue) !== JSON.stringify(this.initialFormValue); */
this.hasFormChanges.set(hasFormChangesDetected); private applyColorUpdates(updatedColors: Partial<MeetRoomTheme>, newBaseTheme: MeetRoomThemeMode): void {
this.isUpdatingColors = true;
this.appearanceForm.patchValue(updatedColors);
this.lastBaseThemeValue = newBaseTheme;
this.isUpdatingColors = false;
}
// Check for color-specific changes /**
const hasColorChangesDetected = * Updates color changes state efficiently
currentFormValue.backgroundColor !== this.initialFormValue.backgroundColor || */
currentFormValue.primaryColor !== this.initialFormValue.primaryColor || private updateColorChangesState(currentFormValue: any): void {
currentFormValue.secondaryColor !== this.initialFormValue.secondaryColor || const colorKeys: ColorField[] = this.colorFields.map((field) => field.key);
currentFormValue.surfaceColor !== this.initialFormValue.surfaceColor;
this.hasColorChanges.set(hasColorChangesDetected); const hasColorChanges = colorKeys.some((key) => currentFormValue[key] !== this.initialFormValue![key]);
this.hasColorChanges.set(hasColorChanges);
} }
onToggleTheme(event: MatSlideToggleChange): void { onToggleTheme(event: MatSlideToggleChange): void {