frontend: implement dynamic theme configuration and loading in meeting component
This commit is contained in:
parent
e418735322
commit
c499dbf6e3
@ -1,6 +1,6 @@
|
|||||||
import { Clipboard } from '@angular/cdk/clipboard';
|
import { Clipboard } from '@angular/cdk/clipboard';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Component, OnInit, Signal } from '@angular/core';
|
import { Component, effect, OnInit, Signal } from '@angular/core';
|
||||||
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
import { FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators } from '@angular/forms';
|
||||||
import { MatButtonModule, MatIconButton } from '@angular/material/button';
|
import { MatButtonModule, MatIconButton } from '@angular/material/button';
|
||||||
import { MatCardModule } from '@angular/material/card';
|
import { MatCardModule } from '@angular/material/card';
|
||||||
@ -44,10 +44,8 @@ import {
|
|||||||
MeetSignalType
|
MeetSignalType
|
||||||
} from '@lib/typings/ce/event.model';
|
} from '@lib/typings/ce/event.model';
|
||||||
import {
|
import {
|
||||||
ApiDirectiveModule,
|
|
||||||
ParticipantService as ComponentParticipantService,
|
ParticipantService as ComponentParticipantService,
|
||||||
DataPacket_Kind,
|
DataPacket_Kind,
|
||||||
LeaveButtonDirective,
|
|
||||||
OpenViduComponentsUiModule,
|
OpenViduComponentsUiModule,
|
||||||
OpenViduService,
|
OpenViduService,
|
||||||
OpenViduThemeService,
|
OpenViduThemeService,
|
||||||
@ -135,6 +133,21 @@ export class MeetingComponent implements OnInit {
|
|||||||
protected configService: GlobalConfigService
|
protected configService: GlobalConfigService
|
||||||
) {
|
) {
|
||||||
this.features = this.featureConfService.features;
|
this.features = this.featureConfService.features;
|
||||||
|
|
||||||
|
// Change theme variables when custom theme is enabled
|
||||||
|
effect(async () => {
|
||||||
|
if (this.features().hasCustomTheme) {
|
||||||
|
const theme = this.features().themeConfig;
|
||||||
|
this.ovThemeService.updateThemeVariables({
|
||||||
|
'--ov-primary-action-color': theme?.primaryColor,
|
||||||
|
'--ov-secondary-action-color': theme?.secondaryColor,
|
||||||
|
'--ov-background-color': theme?.backgroundColor,
|
||||||
|
'--ov-surface-color': theme?.surfaceColor
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.ovThemeService.resetThemeVariables();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get roomName(): string {
|
get roomName(): string {
|
||||||
@ -283,23 +296,12 @@ export class MeetingComponent implements OnInit {
|
|||||||
await this.generateParticipantToken();
|
await this.generateParticipantToken();
|
||||||
await this.addParticipantNameToUrl();
|
await this.addParticipantNameToUrl();
|
||||||
await this.roomService.loadRoomConfig(this.roomId);
|
await this.roomService.loadRoomConfig(this.roomId);
|
||||||
this.showMeeting = true;
|
|
||||||
|
|
||||||
const { appearance } = await this.configService.getRoomsAppearanceConfig();
|
// The meeting view must be shown before loading the appearance config,
|
||||||
console.log('Loaded appearance config:', appearance);
|
// as it contains theme information that might be applied immediately
|
||||||
if (appearance.themes.length > 0 && appearance.themes[0].enabled) {
|
// when the meeting view is rendered
|
||||||
const theme = appearance.themes[0];
|
this.showMeeting = true;
|
||||||
this.ovThemeService.updateThemeVariables({
|
await this.configService.loadRoomsAppearanceConfig();
|
||||||
'--ov-primary-action-color': theme.primaryColor,
|
|
||||||
'--ov-secondary-action-color': theme.secondaryColor,
|
|
||||||
'--ov-background-color': theme.backgroundColor,
|
|
||||||
'--ov-surface-color': theme.surfaceColor
|
|
||||||
});
|
|
||||||
this.features().showThemeSelector = false;
|
|
||||||
} else {
|
|
||||||
this.ovThemeService.resetThemeVariables();
|
|
||||||
this.features().showThemeSelector = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
combineLatest([
|
combineLatest([
|
||||||
this.ovComponentsParticipantService.remoteParticipants$,
|
this.ovComponentsParticipantService.remoteParticipants$,
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { computed, Injectable, signal } from '@angular/core';
|
import { computed, Injectable, signal } from '@angular/core';
|
||||||
import {
|
import {
|
||||||
|
MeetAppearanceConfig,
|
||||||
MeetRoomConfig,
|
MeetRoomConfig,
|
||||||
ParticipantPermissions,
|
ParticipantPermissions,
|
||||||
ParticipantRole,
|
ParticipantRole,
|
||||||
@ -32,6 +33,15 @@ export interface ApplicationFeatures {
|
|||||||
canModerateRoom: boolean;
|
canModerateRoom: boolean;
|
||||||
canRecordRoom: boolean;
|
canRecordRoom: boolean;
|
||||||
canRetrieveRecordings: boolean;
|
canRetrieveRecordings: boolean;
|
||||||
|
|
||||||
|
// Appearance
|
||||||
|
hasCustomTheme: boolean;
|
||||||
|
themeConfig?: {
|
||||||
|
primaryColor?: string;
|
||||||
|
secondaryColor?: string;
|
||||||
|
backgroundColor?: string;
|
||||||
|
surfaceColor?: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,7 +64,10 @@ const DEFAULT_FEATURES: ApplicationFeatures = {
|
|||||||
|
|
||||||
canModerateRoom: false,
|
canModerateRoom: false,
|
||||||
canRecordRoom: false,
|
canRecordRoom: false,
|
||||||
canRetrieveRecordings: false
|
canRetrieveRecordings: false,
|
||||||
|
|
||||||
|
hasCustomTheme: false,
|
||||||
|
themeConfig: undefined
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -72,6 +85,7 @@ export class FeatureConfigurationService {
|
|||||||
protected participantPermissions = signal<ParticipantPermissions | undefined>(undefined);
|
protected participantPermissions = signal<ParticipantPermissions | undefined>(undefined);
|
||||||
protected participantRole = signal<ParticipantRole | undefined>(undefined);
|
protected participantRole = signal<ParticipantRole | undefined>(undefined);
|
||||||
protected recordingPermissions = signal<RecordingPermissions | undefined>(undefined);
|
protected recordingPermissions = signal<RecordingPermissions | undefined>(undefined);
|
||||||
|
protected appearanceConfig = signal<MeetAppearanceConfig | undefined>(undefined);
|
||||||
|
|
||||||
// Computed signal to derive features based on current configurations
|
// Computed signal to derive features based on current configurations
|
||||||
public readonly features = computed<ApplicationFeatures>(() =>
|
public readonly features = computed<ApplicationFeatures>(() =>
|
||||||
@ -79,7 +93,8 @@ export class FeatureConfigurationService {
|
|||||||
this.roomConfig(),
|
this.roomConfig(),
|
||||||
this.participantPermissions(),
|
this.participantPermissions(),
|
||||||
this.participantRole(),
|
this.participantRole(),
|
||||||
this.recordingPermissions()
|
this.recordingPermissions(),
|
||||||
|
this.appearanceConfig()
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -120,10 +135,11 @@ export class FeatureConfigurationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a specific feature is enabled
|
* Updates appearance config
|
||||||
*/
|
*/
|
||||||
isFeatureEnabled(featureName: keyof ApplicationFeatures): boolean {
|
setAppearanceConfig(config: MeetAppearanceConfig): void {
|
||||||
return this.features()[featureName];
|
this.log.d('Updating appearance config', config);
|
||||||
|
this.appearanceConfig.set(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -133,7 +149,8 @@ export class FeatureConfigurationService {
|
|||||||
roomConfig?: MeetRoomConfig,
|
roomConfig?: MeetRoomConfig,
|
||||||
participantPerms?: ParticipantPermissions,
|
participantPerms?: ParticipantPermissions,
|
||||||
role?: ParticipantRole,
|
role?: ParticipantRole,
|
||||||
recordingPerms?: RecordingPermissions
|
recordingPerms?: RecordingPermissions,
|
||||||
|
appearanceConfig?: MeetAppearanceConfig
|
||||||
): ApplicationFeatures {
|
): ApplicationFeatures {
|
||||||
// Start with default configuration
|
// Start with default configuration
|
||||||
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
|
const features: ApplicationFeatures = { ...DEFAULT_FEATURES };
|
||||||
@ -178,6 +195,24 @@ export class FeatureConfigurationService {
|
|||||||
features.canRetrieveRecordings = recordingPerms.canRetrieveRecordings;
|
features.canRetrieveRecordings = recordingPerms.canRetrieveRecordings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply appearance configuration
|
||||||
|
if (appearanceConfig && appearanceConfig.themes.length > 0) {
|
||||||
|
const theme = appearanceConfig.themes[0];
|
||||||
|
const hasEnabledTheme = theme.enabled;
|
||||||
|
|
||||||
|
features.hasCustomTheme = hasEnabledTheme;
|
||||||
|
features.showThemeSelector = !hasEnabledTheme;
|
||||||
|
|
||||||
|
if (hasEnabledTheme) {
|
||||||
|
features.themeConfig = {
|
||||||
|
primaryColor: theme.primaryColor,
|
||||||
|
secondaryColor: theme.secondaryColor,
|
||||||
|
backgroundColor: theme.backgroundColor,
|
||||||
|
surfaceColor: theme.surfaceColor
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.log.d('Calculated features', features);
|
this.log.d('Calculated features', features);
|
||||||
return features;
|
return features;
|
||||||
}
|
}
|
||||||
@ -189,5 +224,7 @@ export class FeatureConfigurationService {
|
|||||||
this.roomConfig.set(undefined);
|
this.roomConfig.set(undefined);
|
||||||
this.participantPermissions.set(undefined);
|
this.participantPermissions.set(undefined);
|
||||||
this.participantRole.set(undefined);
|
this.participantRole.set(undefined);
|
||||||
|
this.recordingPermissions.set(undefined);
|
||||||
|
this.appearanceConfig.set(undefined);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { HttpService } from '@lib/services';
|
import { FeatureConfigurationService, HttpService } from '@lib/services';
|
||||||
import { AuthMode, MeetAppearanceConfig, SecurityConfig, WebhookConfig } from '@lib/typings/ce';
|
import { AuthMode, MeetAppearanceConfig, SecurityConfig, WebhookConfig } from '@lib/typings/ce';
|
||||||
import { LoggerService } from 'openvidu-components-angular';
|
import { LoggerService } from 'openvidu-components-angular';
|
||||||
|
|
||||||
@ -15,7 +15,8 @@ export class GlobalConfigService {
|
|||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected loggerService: LoggerService,
|
protected loggerService: LoggerService,
|
||||||
protected httpService: HttpService
|
protected httpService: HttpService,
|
||||||
|
protected featureConfService: FeatureConfigurationService
|
||||||
) {
|
) {
|
||||||
this.log = this.loggerService.get('OpenVidu Meet - GlobalConfigService');
|
this.log = this.loggerService.get('OpenVidu Meet - GlobalConfigService');
|
||||||
}
|
}
|
||||||
@ -66,6 +67,16 @@ export class GlobalConfigService {
|
|||||||
return await this.httpService.getRequest<{ appearance: MeetAppearanceConfig }>(path);
|
return await this.httpService.getRequest<{ appearance: MeetAppearanceConfig }>(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async loadRoomsAppearanceConfig(): Promise<void> {
|
||||||
|
try {
|
||||||
|
const config = await this.getRoomsAppearanceConfig();
|
||||||
|
this.featureConfService.setAppearanceConfig(config.appearance);
|
||||||
|
} catch (error) {
|
||||||
|
this.log.e('Error loading rooms appearance config:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async saveRoomsAppearanceConfig(config: MeetAppearanceConfig) {
|
async saveRoomsAppearanceConfig(config: MeetAppearanceConfig) {
|
||||||
const path = `${this.GLOBAL_CONFIG_API}/rooms/appearance`;
|
const path = `${this.GLOBAL_CONFIG_API}/rooms/appearance`;
|
||||||
await this.httpService.putRequest(path, { appearance: config });
|
await this.httpService.putRequest(path, { appearance: config });
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user