ov-components: Implement centralized template management for videoconference components

master
Carlos Santos 2025-07-22 19:24:07 +02:00
parent 04b8b741e2
commit 22af5c7df6
7 changed files with 709 additions and 196 deletions

View File

@ -25,6 +25,7 @@ import {
} from '../../models/panel.model'; } from '../../models/panel.model';
import { PanelService } from '../../services/panel/panel.service'; import { PanelService } from '../../services/panel/panel.service';
import { BackgroundEffect } from '../../models/background-effect.model'; import { BackgroundEffect } from '../../models/background-effect.model';
import { TemplateManagerService, PanelTemplateConfiguration } from '../../services/template/template-manager.service';
/** /**
* *
@ -75,42 +76,20 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild(ParticipantsPanelDirective) @ContentChild(ParticipantsPanelDirective)
set externalParticipantPanel(externalParticipantsPanel: ParticipantsPanelDirective) { set externalParticipantPanel(externalParticipantsPanel: ParticipantsPanelDirective) {
// This directive will has value only when PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel' this._externalParticipantPanel = externalParticipantsPanel;
// is inside of the PANEL component tagged with '*ovPanel'
if (externalParticipantsPanel) { if (externalParticipantsPanel) {
this.participantsPanelTemplate = externalParticipantsPanel.template; this.updateTemplatesAndMarkForCheck();
} }
} }
// TODO: backgroundEffectsPanel does not provides customization
// @ContentChild(BackgroundEffectsPanelDirective)
// set externalBackgroundEffectsPanel(externalBackgroundEffectsPanel: BackgroundEffectsPanelDirective) {
// This directive will has value only when BACKGROUND EFFECTS PANEL component tagged with '*ovBackgroundEffectsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
// if (externalBackgroundEffectsPanel) {
// this.backgroundEffectsPanelTemplate = externalBackgroundEffectsPanel.template;
// }
// }
// TODO: settingsPanel does not provides customization
// @ContentChild(SettingsPanelDirective)
// set externalSettingsPanel(externalSettingsPanel: SettingsPanelDirective) {
// This directive will has value only when SETTINGS PANEL component tagged with '*ovSettingsPanel'
// is inside of the PANEL component tagged with '*ovPanel'
// if (externalSettingsPanel) {
// this.settingsPanelTemplate = externalSettingsPanel.template;
// }
// }
/** /**
* @ignore * @ignore
*/ */
@ContentChild(ActivitiesPanelDirective) @ContentChild(ActivitiesPanelDirective)
set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) { set externalActivitiesPanel(externalActivitiesPanel: ActivitiesPanelDirective) {
// This directive will has value only when ACTIVITIES PANEL component tagged with '*ovActivitiesPanel' this._externalActivitiesPanel = externalActivitiesPanel;
// is inside of the PANEL component tagged with '*ovPanel'
if (externalActivitiesPanel) { if (externalActivitiesPanel) {
this.activitiesPanelTemplate = externalActivitiesPanel.template; this.updateTemplatesAndMarkForCheck();
} }
} }
@ -119,10 +98,9 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild(ChatPanelDirective) @ContentChild(ChatPanelDirective)
set externalChatPanel(externalChatPanel: ChatPanelDirective) { set externalChatPanel(externalChatPanel: ChatPanelDirective) {
// This directive will has value only when CHAT PANEL component tagged with '*ovChatPanel' this._externalChatPanel = externalChatPanel;
// is inside of the PANEL component tagged with '*ovPanel'
if (externalChatPanel) { if (externalChatPanel) {
this.chatPanelTemplate = externalChatPanel.template; this.updateTemplatesAndMarkForCheck();
} }
} }
@ -131,10 +109,9 @@ export class PanelComponent implements OnInit {
*/ */
@ContentChild(AdditionalPanelsDirective) @ContentChild(AdditionalPanelsDirective)
set externalAdditionalPanels(externalAdditionalPanels: AdditionalPanelsDirective) { set externalAdditionalPanels(externalAdditionalPanels: AdditionalPanelsDirective) {
// This directive will has value only when ADDITIONAL PANELS component tagged with '*ovPanelAdditionalPanels' this._externalAdditionalPanels = externalAdditionalPanels;
// is inside of the PANEL component tagged with '*ovPanel'
if (externalAdditionalPanels) { if (externalAdditionalPanels) {
this.additionalPanelsTemplate = externalAdditionalPanels.template; this.updateTemplatesAndMarkForCheck();
} }
} }
@ -195,6 +172,19 @@ export class PanelComponent implements OnInit {
* @internal * @internal
*/ */
isExternalPanelOpened: boolean; isExternalPanelOpened: boolean;
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: PanelTemplateConfiguration = {};
// Store directive references for template setup
private _externalParticipantPanel?: ParticipantsPanelDirective;
private _externalChatPanel?: ChatPanelDirective;
private _externalActivitiesPanel?: ActivitiesPanelDirective;
private _externalAdditionalPanels?: AdditionalPanelsDirective;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
private panelEmitersHandler: Map< private panelEmitersHandler: Map<
@ -207,19 +197,66 @@ export class PanelComponent implements OnInit {
*/ */
constructor( constructor(
private panelService: PanelService, private panelService: PanelService,
private cd: ChangeDetectorRef private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
) {} ) {}
/** /**
* @ignore * @ignore
*/ */
ngOnInit(): void { ngOnInit(): void {
this.setupTemplates();
this.subscribeToPanelToggling(); this.subscribeToPanelToggling();
this.panelEmitersHandler.set(PanelType.CHAT, this.onChatPanelStatusChanged); this.panelEmitersHandler.set(PanelType.CHAT, this.onChatPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.PARTICIPANTS, this.onParticipantsPanelStatusChanged); this.panelEmitersHandler.set(PanelType.PARTICIPANTS, this.onParticipantsPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.SETTINGS, this.onSettingsPanelStatusChanged); this.panelEmitersHandler.set(PanelType.SETTINGS, this.onSettingsPanelStatusChanged);
this.panelEmitersHandler.set(PanelType.ACTIVITIES, this.onActivitiesPanelStatusChanged); this.panelEmitersHandler.set(PanelType.ACTIVITIES, this.onActivitiesPanelStatusChanged);
} }
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupPanelTemplates(
this._externalParticipantPanel,
this._externalChatPanel,
this._externalActivitiesPanel,
this._externalAdditionalPanels
);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
/**
* @internal
* Applies the template configuration to component properties
*/
private applyTemplateConfiguration(): void {
if (this.templateConfig.participantsPanelTemplate) {
this.participantsPanelTemplate = this.templateConfig.participantsPanelTemplate;
}
if (this.templateConfig.chatPanelTemplate) {
this.chatPanelTemplate = this.templateConfig.chatPanelTemplate;
}
if (this.templateConfig.activitiesPanelTemplate) {
this.activitiesPanelTemplate = this.templateConfig.activitiesPanelTemplate;
}
if (this.templateConfig.additionalPanelsTemplate) {
this.additionalPanelsTemplate = this.templateConfig.additionalPanelsTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
/** /**
* @ignore * @ignore
*/ */

View File

@ -4,6 +4,7 @@ import { ParticipantPanelItemElementsDirective } from '../../../../directives/te
import { ParticipantModel } from '../../../../models/participant.model'; import { ParticipantModel } from '../../../../models/participant.model';
import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service'; import { OpenViduComponentsConfigService } from '../../../../services/config/directive-config.service';
import { ParticipantService } from '../../../../services/participant/participant.service'; import { ParticipantService } from '../../../../services/participant/participant.service';
import { TemplateManagerService, ParticipantPanelItemTemplateConfiguration } from '../../../../services/template/template-manager.service';
/** /**
* *
@ -35,13 +36,21 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
*/ */
@ContentChild(ParticipantPanelItemElementsDirective) @ContentChild(ParticipantPanelItemElementsDirective)
set externalItemElements(externalItemElements: ParticipantPanelItemElementsDirective) { set externalItemElements(externalItemElements: ParticipantPanelItemElementsDirective) {
// This directive will has value only when ITEM ELEMENTS component tagget with '*ovParticipantPanelItemElements' directive this._externalItemElements = externalItemElements;
// is inside of the P PANEL ITEM component tagged with '*ovParticipantPanelItem' directive
if (externalItemElements) { if (externalItemElements) {
this.participantPanelItemElementsTemplate = externalItemElements.template; this.updateTemplatesAndMarkForCheck();
} }
} }
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: ParticipantPanelItemTemplateConfiguration = {};
// Store directive references for template setup
private _externalItemElements?: ParticipantPanelItemElementsDirective;
/** /**
* The participant to be displayed * The participant to be displayed
* @ignore * @ignore
@ -62,13 +71,15 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
constructor( constructor(
private libService: OpenViduComponentsConfigService, private libService: OpenViduComponentsConfigService,
private participantService: ParticipantService, private participantService: ParticipantService,
private cd: ChangeDetectorRef private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
) {} ) {}
/** /**
* @ignore * @ignore
*/ */
ngOnInit(): void { ngOnInit(): void {
this.setupTemplates();
this.subscribeToParticipantPanelItemDirectives(); this.subscribeToParticipantPanelItemDirectives();
} }
@ -88,6 +99,38 @@ export class ParticipantPanelItemComponent implements OnInit, OnDestroy {
} }
} }
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupParticipantPanelItemTemplates(
this._externalItemElements
);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
/**
* @internal
* Applies the template configuration to component properties
*/
private applyTemplateConfiguration(): void {
if (this.templateConfig.participantPanelItemElementsTemplate) {
this.participantPanelItemElementsTemplate = this.templateConfig.participantPanelItemElementsTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
private subscribeToParticipantPanelItemDirectives() { private subscribeToParticipantPanelItemDirectives() {
this.muteButtonSub = this.libService.participantItemMuteButton$.subscribe((value: boolean) => { this.muteButtonSub = this.libService.participantItemMuteButton$.subscribe((value: boolean) => {
this.showMuteButton = value; this.showMuteButton = value;

View File

@ -15,6 +15,7 @@ import { PanelService } from '../../../../services/panel/panel.service';
import { ParticipantPanelItemDirective } from '../../../../directives/template/openvidu-components-angular.directive'; import { ParticipantPanelItemDirective } from '../../../../directives/template/openvidu-components-angular.directive';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ParticipantModel } from '../../../../models/participant.model'; import { ParticipantModel } from '../../../../models/participant.model';
import { TemplateManagerService, ParticipantsPanelTemplateConfiguration } from '../../../../services/template/template-manager.service';
/** /**
* The **ParticipantsPanelComponent** is hosted inside of the {@link PanelComponent}. * The **ParticipantsPanelComponent** is hosted inside of the {@link PanelComponent}.
@ -53,13 +54,21 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
*/ */
@ContentChild(ParticipantPanelItemDirective) @ContentChild(ParticipantPanelItemDirective)
set externalParticipantPanelItem(externalParticipantPanelItem: ParticipantPanelItemDirective) { set externalParticipantPanelItem(externalParticipantPanelItem: ParticipantPanelItemDirective) {
// This directive will has value only when PARTICIPANT PANEL ITEM component tagged with '*ovParticipantPanelItem' this._externalParticipantPanelItem = externalParticipantPanelItem;
// is inside of the PARTICIPANTS PANEL component tagged with '*ovParticipantsPanel'
if (externalParticipantPanelItem) { if (externalParticipantPanelItem) {
this.participantPanelItemTemplate = externalParticipantPanelItem.template; this.updateTemplatesAndMarkForCheck();
} }
} }
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: ParticipantsPanelTemplateConfiguration = {};
// Store directive references for template setup
private _externalParticipantPanelItem?: ParticipantPanelItemDirective;
private localParticipantSubs: Subscription; private localParticipantSubs: Subscription;
private remoteParticipantsSubs: Subscription; private remoteParticipantsSubs: Subscription;
@ -69,13 +78,16 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
constructor( constructor(
private participantService: ParticipantService, private participantService: ParticipantService,
private panelService: PanelService, private panelService: PanelService,
private cd: ChangeDetectorRef private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
) {} ) {}
/** /**
* @ignore * @ignore
*/ */
ngOnInit(): void { ngOnInit(): void {
this.setupTemplates();
this.localParticipantSubs = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => { this.localParticipantSubs = this.participantService.localParticipant$.subscribe((p: ParticipantModel | undefined) => {
if (p) { if (p) {
this.localParticipant = p; this.localParticipant = p;
@ -109,6 +121,39 @@ export class ParticipantsPanelComponent implements OnInit, OnDestroy, AfterViewI
} }
} }
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupParticipantsPanelTemplates(
this._externalParticipantPanelItem,
this.defaultParticipantPanelItemTemplate
);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
/**
* @internal
* Applies the template configuration to component properties
*/
private applyTemplateConfiguration(): void {
if (this.templateConfig.participantPanelItemTemplate) {
this.participantPanelItemTemplate = this.templateConfig.participantPanelItemTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
/** /**
* @ignore * @ignore
*/ */

View File

@ -48,6 +48,7 @@ import {
} from 'livekit-client'; } from 'livekit-client';
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model'; import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model';
import { RecordingStatus } from '../../models/recording.model'; import { RecordingStatus } from '../../models/recording.model';
import { TemplateManagerService, SessionTemplateConfiguration } from '../../services/template/template-manager.service';
/** /**
* @internal * @internal
@ -103,6 +104,12 @@ export class SessionComponent implements OnInit, OnDestroy {
drawer: MatDrawerContainer; drawer: MatDrawerContainer;
loading: boolean = true; loading: boolean = true;
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: SessionTemplateConfiguration = {};
private shouldDisconnectRoomWhenComponentIsDestroyed: boolean = true; private shouldDisconnectRoomWhenComponentIsDestroyed: boolean = true;
private readonly SIDENAV_WIDTH_LIMIT_MODE = 790; private readonly SIDENAV_WIDTH_LIMIT_MODE = 790;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
@ -123,9 +130,11 @@ export class SessionComponent implements OnInit, OnDestroy {
private translateService: TranslateService, private translateService: TranslateService,
// private captionService: CaptionService, // private captionService: CaptionService,
private backgroundService: VirtualBackgroundService, private backgroundService: VirtualBackgroundService,
private cd: ChangeDetectorRef private cd: ChangeDetectorRef,
private templateManagerService: TemplateManagerService
) { ) {
this.log = this.loggerSrv.get('SessionComponent'); this.log = this.loggerSrv.get('SessionComponent');
this.setupTemplates();
} }
@HostListener('window:beforeunload') @HostListener('window:beforeunload')
@ -251,6 +260,18 @@ export class SessionComponent implements OnInit, OnDestroy {
}); });
} }
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupSessionTemplates(
this.toolbarTemplate,
this.panelTemplate,
this.layoutTemplate
);
}
async ngOnDestroy() { async ngOnDestroy() {
if (this.shouldDisconnectRoomWhenComponentIsDestroyed) { if (this.shouldDisconnectRoomWhenComponentIsDestroyed) {
await this.disconnectRoom(ParticipantLeftReason.LEAVE); await this.disconnectRoom(ParticipantLeftReason.LEAVE);

View File

@ -44,6 +44,7 @@ import { ParticipantService } from '../../services/participant/participant.servi
import { PlatformService } from '../../services/platform/platform.service'; import { PlatformService } from '../../services/platform/platform.service';
import { RecordingService } from '../../services/recording/recording.service'; import { RecordingService } from '../../services/recording/recording.service';
import { StorageService } from '../../services/storage/storage.service'; import { StorageService } from '../../services/storage/storage.service';
import { TemplateManagerService, ToolbarTemplateConfiguration } from '../../services/template/template-manager.service';
import { TranslateService } from '../../services/translate/translate.service'; import { TranslateService } from '../../services/translate/translate.service';
import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service'; import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.service';
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model'; import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model';
@ -77,10 +78,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/ */
@ContentChild(ToolbarAdditionalButtonsDirective) @ContentChild(ToolbarAdditionalButtonsDirective)
set externalAdditionalButtons(externalAdditionalButtons: ToolbarAdditionalButtonsDirective) { set externalAdditionalButtons(externalAdditionalButtons: ToolbarAdditionalButtonsDirective) {
// This directive will has value only when ADDITIONAL BUTTONS component (tagged with '*ovToolbarAdditionalButtons' directive) this._externalAdditionalButtons = externalAdditionalButtons;
// is inside of the TOOLBAR component tagged with '*ovToolbar' directive
if (externalAdditionalButtons) { if (externalAdditionalButtons) {
this.toolbarAdditionalButtonsTemplate = externalAdditionalButtons.template; this.updateTemplatesAndMarkForCheck();
} }
} }
@ -89,10 +89,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/ */
@ContentChild(ToolbarAdditionalPanelButtonsDirective) @ContentChild(ToolbarAdditionalPanelButtonsDirective)
set externalAdditionalPanelButtons(externalAdditionalPanelButtons: ToolbarAdditionalPanelButtonsDirective) { set externalAdditionalPanelButtons(externalAdditionalPanelButtons: ToolbarAdditionalPanelButtonsDirective) {
// This directive will has value only when ADDITIONAL PANEL BUTTONS component tagged with '*ovToolbarAdditionalPanelButtons' directive this._externalAdditionalPanelButtons = externalAdditionalPanelButtons;
// is inside of the TOOLBAR component tagged with '*ovToolbar' directive
if (externalAdditionalPanelButtons) { if (externalAdditionalPanelButtons) {
this.toolbarAdditionalPanelButtonsTemplate = externalAdditionalPanelButtons.template; this.updateTemplatesAndMarkForCheck();
} }
} }
@ -355,6 +354,16 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
*/ */
recordingTime: Date; recordingTime: Date;
/**
* @internal
* Template configuration managed by the service
*/
templateConfig: ToolbarTemplateConfiguration = {};
// Store directive references for template setup
private _externalAdditionalButtons?: ToolbarAdditionalButtonsDirective;
private _externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective;
private log: ILogger; private log: ILogger;
private destroy$ = new Subject<void>(); private destroy$ = new Subject<void>();
private currentWindowHeight = window.innerHeight; private currentWindowHeight = window.innerHeight;
@ -379,7 +388,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
private broadcastingService: BroadcastingService, private broadcastingService: BroadcastingService,
private translateService: TranslateService, private translateService: TranslateService,
private storageSrv: StorageService, private storageSrv: StorageService,
private cdkOverlayService: CdkOverlayService private cdkOverlayService: CdkOverlayService,
private templateManagerService: TemplateManagerService
) { ) {
this.log = this.loggerSrv.get('ToolbarComponent'); this.log = this.loggerSrv.get('ToolbarComponent');
} }
@ -413,6 +423,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.hasVideoDevices = this.oVDevicesService.hasVideoDeviceAvailable(); this.hasVideoDevices = this.oVDevicesService.hasVideoDeviceAvailable();
this.hasAudioDevices = this.oVDevicesService.hasAudioDeviceAvailable(); this.hasAudioDevices = this.oVDevicesService.hasAudioDeviceAvailable();
this.setupTemplates();
this.subscribeToToolbarDirectives(); this.subscribeToToolbarDirectives();
this.subscribeToUserMediaProperties(); this.subscribeToUserMediaProperties();
@ -436,6 +447,42 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.cdkOverlayService.setSelector('body'); this.cdkOverlayService.setSelector('body');
} }
/**
* @internal
* Sets up all templates using the template manager service
*/
private setupTemplates(): void {
this.templateConfig = this.templateManagerService.setupToolbarTemplates(
this._externalAdditionalButtons,
this._externalAdditionalPanelButtons
);
// Apply templates to component properties for backward compatibility
this.applyTemplateConfiguration();
}
/**
* @internal
* Applies the template configuration to component properties
*/
private applyTemplateConfiguration(): void {
if (this.templateConfig.toolbarAdditionalButtonsTemplate) {
this.toolbarAdditionalButtonsTemplate = this.templateConfig.toolbarAdditionalButtonsTemplate;
}
if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) {
this.toolbarAdditionalPanelButtonsTemplate = this.templateConfig.toolbarAdditionalPanelButtonsTemplate;
}
}
/**
* @internal
* Updates templates and triggers change detection
*/
private updateTemplatesAndMarkForCheck(): void {
this.setupTemplates();
this.cd.markForCheck();
}
/** /**
* @internal * @internal
*/ */

View File

@ -1,5 +1,5 @@
import { animate, style, transition, trigger } from '@angular/animations'; import { animate, style, transition, trigger } from '@angular/animations';
import { AfterViewInit, Component, ContentChild, EventEmitter, OnDestroy, Output, TemplateRef, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, EventEmitter, OnDestroy, Output, TemplateRef, ViewChild } from '@angular/core';
import { Subject, filter, skip, take, takeUntil } from 'rxjs'; import { Subject, filter, skip, take, takeUntil } from 'rxjs';
import { import {
ActivitiesPanelDirective, ActivitiesPanelDirective,
@ -24,6 +24,7 @@ import { DeviceService } from '../../services/device/device.service';
import { LoggerService } from '../../services/logger/logger.service'; import { LoggerService } from '../../services/logger/logger.service';
import { OpenViduService } from '../../services/openvidu/openvidu.service'; import { OpenViduService } from '../../services/openvidu/openvidu.service';
import { StorageService } from '../../services/storage/storage.service'; import { StorageService } from '../../services/storage/storage.service';
import { TemplateManagerService, TemplateConfiguration, ExternalDirectives, DefaultTemplates } from '../../services/template/template-manager.service';
import { Room } from 'livekit-client'; import { Room } from 'livekit-client';
import { ParticipantLeftEvent, ParticipantModel } from '../../models/participant.model'; import { ParticipantLeftEvent, ParticipantModel } from '../../models/participant.model';
import { CustomDevice } from '../../models/device.model'; import { CustomDevice } from '../../models/device.model';
@ -218,6 +219,12 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
*/ */
openviduAngularPreJoinTemplate: TemplateRef<any>; openviduAngularPreJoinTemplate: TemplateRef<any>;
/**
* @internal
* Template configuration managed by TemplateManagerService
*/
private templateConfig: TemplateConfiguration;
/** /**
* Provides event notifications that fire when the local participant is ready to join to the room. * Provides event notifications that fire when the local participant is ready to join to the room.
* This event emits the participant name as data. * This event emits the participant name as data.
@ -445,7 +452,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
private deviceSrv: DeviceService, private deviceSrv: DeviceService,
private openviduService: OpenViduService, private openviduService: OpenViduService,
private actionService: ActionService, private actionService: ActionService,
private libService: OpenViduComponentsConfigService private libService: OpenViduComponentsConfigService,
private templateManagerService: TemplateManagerService
) { ) {
this.log = this.loggerSrv.get('VideoconferenceComponent'); this.log = this.loggerSrv.get('VideoconferenceComponent');
@ -499,161 +507,69 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
* @internal * @internal
*/ */
private setupTemplates(): void { private setupTemplates(): void {
this.setupToolbarTemplate(); const externalDirectives: ExternalDirectives = {
this.setupPanelTemplate(); toolbar: this.externalToolbar,
this.setupLayoutTemplate(); toolbarAdditionalButtons: this.externalToolbarAdditionalButtons,
this.setupPreJoinTemplate(); toolbarAdditionalPanelButtons: this.externalToolbarAdditionalPanelButtons,
additionalPanels: this.externalAdditionalPanels,
panel: this.externalPanel,
chatPanel: this.externalChatPanel,
activitiesPanel: this.externalActivitiesPanel,
participantsPanel: this.externalParticipantsPanel,
participantPanelItem: this.externalParticipantPanelItem,
participantPanelItemElements: this.externalParticipantPanelItemElements,
layout: this.externalLayout,
stream: this.externalStream,
preJoin: this.externalPreJoin
};
const defaultTemplates: DefaultTemplates = {
toolbar: this.defaultToolbarTemplate,
panel: this.defaultPanelTemplate,
chatPanel: this.defaultChatPanelTemplate,
participantsPanel: this.defaultParticipantsPanelTemplate,
activitiesPanel: this.defaultActivitiesPanelTemplate,
participantPanelItem: this.defaultParticipantPanelItemTemplate,
layout: this.defaultLayoutTemplate,
stream: this.defaultStreamTemplate
};
// Use the template manager service to set up all templates
this.templateConfig = this.templateManagerService.setupTemplates(externalDirectives, defaultTemplates);
// Apply the configuration to the component properties
this.applyTemplateConfiguration();
} }
/** /**
* @internal * @internal
* Applies the template configuration to component properties
*/ */
private setupToolbarTemplate(): void { private applyTemplateConfiguration(): void {
if (this.externalToolbar) { this.openviduAngularToolbarTemplate = this.templateConfig.toolbarTemplate;
this.log.d('Setting EXTERNAL TOOLBAR'); this.openviduAngularPanelTemplate = this.templateConfig.panelTemplate;
this.openviduAngularToolbarTemplate = this.externalToolbar.template; this.openviduAngularChatPanelTemplate = this.templateConfig.chatPanelTemplate;
} else { this.openviduAngularParticipantsPanelTemplate = this.templateConfig.participantsPanelTemplate;
this.log.d('Setting DEFAULT TOOLBAR'); this.openviduAngularActivitiesPanelTemplate = this.templateConfig.activitiesPanelTemplate;
this.setupToolbarAdditionalButtons(); this.openviduAngularParticipantPanelItemTemplate = this.templateConfig.participantPanelItemTemplate;
this.openviduAngularToolbarTemplate = this.defaultToolbarTemplate; this.openviduAngularLayoutTemplate = this.templateConfig.layoutTemplate;
} this.openviduAngularStreamTemplate = this.templateConfig.streamTemplate;
}
/** // Optional templates
* @internal if (this.templateConfig.toolbarAdditionalButtonsTemplate) {
*/ this.openviduAngularToolbarAdditionalButtonsTemplate = this.templateConfig.toolbarAdditionalButtonsTemplate;
private setupToolbarAdditionalButtons(): void {
if (this.externalToolbarAdditionalButtons) {
this.log.d('Setting EXTERNAL TOOLBAR ADDITIONAL BUTTONS');
this.openviduAngularToolbarAdditionalButtonsTemplate = this.externalToolbarAdditionalButtons.template;
} }
if (this.externalToolbarAdditionalPanelButtons) { if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) {
this.log.d('Setting EXTERNAL TOOLBAR ADDITIONAL PANEL BUTTONS'); this.openviduAngularToolbarAdditionalPanelButtonsTemplate = this.templateConfig.toolbarAdditionalPanelButtonsTemplate;
this.openviduAngularToolbarAdditionalPanelButtonsTemplate = this.externalToolbarAdditionalPanelButtons.template;
} }
} if (this.templateConfig.additionalPanelsTemplate) {
this.openviduAngularAdditionalPanelsTemplate = this.templateConfig.additionalPanelsTemplate;
/**
* @internal
*/
private setupPanelTemplate(): void {
if (this.externalPanel) {
this.log.d('Setting EXTERNAL PANEL');
this.openviduAngularPanelTemplate = this.externalPanel.template;
} else {
this.log.d('Setting DEFAULT PANEL');
this.setupParticipantsPanel();
this.setupChatPanel();
this.setupActivitiesPanel();
this.setupAdditionalPanels();
this.openviduAngularPanelTemplate = this.defaultPanelTemplate;
} }
} if (this.templateConfig.participantPanelItemElementsTemplate) {
this.openviduAngularParticipantPanelItemElementsTemplate = this.templateConfig.participantPanelItemElementsTemplate;
/**
* @internal
*/
private setupParticipantsPanel(): void {
if (this.externalParticipantsPanel) {
this.openviduAngularParticipantsPanelTemplate = this.externalParticipantsPanel.template;
this.log.d('Setting EXTERNAL PARTICIPANTS PANEL');
} else {
this.log.d('Setting DEFAULT PARTICIPANTS PANEL');
this.setupParticipantPanelItem();
this.openviduAngularParticipantsPanelTemplate = this.defaultParticipantsPanelTemplate;
} }
} if (this.templateConfig.preJoinTemplate) {
this.openviduAngularPreJoinTemplate = this.templateConfig.preJoinTemplate;
/**
* @internal
*/
private setupParticipantPanelItem(): void {
if (this.externalParticipantPanelItem) {
this.log.d('Setting EXTERNAL P ITEM');
this.openviduAngularParticipantPanelItemTemplate = this.externalParticipantPanelItem.template;
} else {
if (this.externalParticipantPanelItemElements) {
this.log.d('Setting EXTERNAL PARTICIPANT PANEL ITEM ELEMENT');
this.openviduAngularParticipantPanelItemElementsTemplate = this.externalParticipantPanelItemElements.template;
}
this.openviduAngularParticipantPanelItemTemplate = this.defaultParticipantPanelItemTemplate;
this.log.d('Setting DEFAULT P ITEM');
}
}
/**
* @internal
*/
private setupChatPanel(): void {
if (this.externalChatPanel) {
this.log.d('Setting EXTERNAL CHAT PANEL');
this.openviduAngularChatPanelTemplate = this.externalChatPanel.template;
} else {
this.log.d('Setting DEFAULT CHAT PANEL');
this.openviduAngularChatPanelTemplate = this.defaultChatPanelTemplate;
}
}
/**
* @internal
*/
private setupActivitiesPanel(): void {
if (this.externalActivitiesPanel) {
this.log.d('Setting EXTERNAL ACTIVITIES PANEL');
this.openviduAngularActivitiesPanelTemplate = this.externalActivitiesPanel.template;
} else {
this.log.d('Setting DEFAULT ACTIVITIES PANEL');
this.openviduAngularActivitiesPanelTemplate = this.defaultActivitiesPanelTemplate;
}
}
/**
* @internal
*/
private setupAdditionalPanels(): void {
if (this.externalAdditionalPanels) {
this.log.d('Setting EXTERNAL ADDITIONAL PANELS');
this.openviduAngularAdditionalPanelsTemplate = this.externalAdditionalPanels.template;
}
}
/**
* @internal
*/
private setupLayoutTemplate(): void {
if (this.externalLayout) {
this.log.d('Setting EXTERNAL LAYOUT');
this.openviduAngularLayoutTemplate = this.externalLayout.template;
} else {
this.log.d('Setting DEFAULT LAYOUT');
this.setupStreamTemplate();
this.openviduAngularLayoutTemplate = this.defaultLayoutTemplate;
}
}
/**
* @internal
*/
private setupStreamTemplate(): void {
if (this.externalStream) {
this.log.d('Setting EXTERNAL STREAM');
this.openviduAngularStreamTemplate = this.externalStream.template;
} else {
this.log.d('Setting DEFAULT STREAM');
this.openviduAngularStreamTemplate = this.defaultStreamTemplate;
}
}
/**
* @internal
*/
private setupPreJoinTemplate(): void {
if (this.externalPreJoin) {
this.log.d('Setting EXTERNAL PREJOIN');
this.openviduAngularPreJoinTemplate = this.externalPreJoin.template;
} else {
this.log.d('Setting DEFAULT PREJOIN');
// Keep the default behavior - no template is set
} }
} }

View File

@ -0,0 +1,404 @@
import { Injectable, TemplateRef } from '@angular/core';
import { ILogger } from '../../models/logger.model';
import { LoggerService } from '../logger/logger.service';
import {
ActivitiesPanelDirective,
AdditionalPanelsDirective,
ChatPanelDirective,
LayoutDirective,
PanelDirective,
ParticipantPanelItemDirective,
ParticipantPanelItemElementsDirective,
ParticipantsPanelDirective,
PreJoinDirective,
StreamDirective,
ToolbarAdditionalButtonsDirective,
ToolbarAdditionalPanelButtonsDirective,
ToolbarDirective
} from '../../directives/template/openvidu-components-angular.directive';
/**
* Configuration object for all templates in the videoconference component
*/
export interface TemplateConfiguration {
// Toolbar templates
toolbarTemplate: TemplateRef<any>;
toolbarAdditionalButtonsTemplate?: TemplateRef<any>;
toolbarAdditionalPanelButtonsTemplate?: TemplateRef<any>;
// Panel templates
panelTemplate: TemplateRef<any>;
chatPanelTemplate: TemplateRef<any>;
participantsPanelTemplate: TemplateRef<any>;
activitiesPanelTemplate: TemplateRef<any>;
additionalPanelsTemplate?: TemplateRef<any>;
// Participant templates
participantPanelItemTemplate: TemplateRef<any>;
participantPanelItemElementsTemplate?: TemplateRef<any>;
// Layout templates
layoutTemplate: TemplateRef<any>;
streamTemplate: TemplateRef<any>;
// PreJoin template
preJoinTemplate?: TemplateRef<any>;
}
/**
* Configuration object for panel component templates
*/
export interface PanelTemplateConfiguration {
participantsPanelTemplate?: TemplateRef<any>;
chatPanelTemplate?: TemplateRef<any>;
activitiesPanelTemplate?: TemplateRef<any>;
additionalPanelsTemplate?: TemplateRef<any>;
backgroundEffectsPanelTemplate?: TemplateRef<any>;
settingsPanelTemplate?: TemplateRef<any>;
}
/**
* Configuration object for toolbar component templates
*/
export interface ToolbarTemplateConfiguration {
toolbarAdditionalButtonsTemplate?: TemplateRef<any>;
toolbarAdditionalPanelButtonsTemplate?: TemplateRef<any>;
}
/**
* Configuration object for participants panel component templates
*/
export interface ParticipantsPanelTemplateConfiguration {
participantPanelItemTemplate?: TemplateRef<any>;
}
/**
* Configuration object for participant panel item component templates
*/
export interface ParticipantPanelItemTemplateConfiguration {
participantPanelItemElementsTemplate?: TemplateRef<any>;
}
/**
* Configuration object for session component templates
*/
export interface SessionTemplateConfiguration {
toolbarTemplate?: TemplateRef<any>;
panelTemplate?: TemplateRef<any>;
layoutTemplate?: TemplateRef<any>;
}
/**
* External directives provided by the consumer
*/
export interface ExternalDirectives {
toolbar?: ToolbarDirective;
toolbarAdditionalButtons?: ToolbarAdditionalButtonsDirective;
toolbarAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective;
additionalPanels?: AdditionalPanelsDirective;
panel?: PanelDirective;
chatPanel?: ChatPanelDirective;
activitiesPanel?: ActivitiesPanelDirective;
participantsPanel?: ParticipantsPanelDirective;
participantPanelItem?: ParticipantPanelItemDirective;
participantPanelItemElements?: ParticipantPanelItemElementsDirective;
layout?: LayoutDirective;
stream?: StreamDirective;
preJoin?: PreJoinDirective;
}
/**
* Default templates provided by the component
*/
export interface DefaultTemplates {
toolbar: TemplateRef<any>;
panel: TemplateRef<any>;
chatPanel: TemplateRef<any>;
participantsPanel: TemplateRef<any>;
activitiesPanel: TemplateRef<any>;
participantPanelItem: TemplateRef<any>;
layout: TemplateRef<any>;
stream: TemplateRef<any>;
}
/**
* Service responsible for managing and configuring templates for the videoconference component.
* This service centralizes all template setup logic, making the main component cleaner and more maintainable.
*/
@Injectable({
providedIn: 'root'
})
export class TemplateManagerService {
private log: ILogger;
constructor(private loggerSrv: LoggerService) {
this.log = this.loggerSrv.get('TemplateManagerService');
}
/**
* Sets up all templates based on external directives and default templates
*/
setupTemplates(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateConfiguration {
this.log.d('Setting up templates...');
const config: TemplateConfiguration = {
toolbarTemplate: this.setupToolbarTemplate(externalDirectives, defaultTemplates),
panelTemplate: this.setupPanelTemplate(externalDirectives, defaultTemplates),
layoutTemplate: this.setupLayoutTemplate(externalDirectives, defaultTemplates),
preJoinTemplate: this.setupPreJoinTemplate(externalDirectives),
// Individual templates
chatPanelTemplate: this.setupChatPanelTemplate(externalDirectives, defaultTemplates),
participantsPanelTemplate: this.setupParticipantsPanelTemplate(externalDirectives, defaultTemplates),
activitiesPanelTemplate: this.setupActivitiesPanelTemplate(externalDirectives, defaultTemplates),
participantPanelItemTemplate: this.setupParticipantPanelItemTemplate(externalDirectives, defaultTemplates),
streamTemplate: this.setupStreamTemplate(externalDirectives, defaultTemplates)
};
// Optional templates
if (externalDirectives.toolbarAdditionalButtons) {
config.toolbarAdditionalButtonsTemplate = externalDirectives.toolbarAdditionalButtons.template;
this.log.d('Setting EXTERNAL TOOLBAR ADDITIONAL BUTTONS');
}
if (externalDirectives.toolbarAdditionalPanelButtons) {
config.toolbarAdditionalPanelButtonsTemplate = externalDirectives.toolbarAdditionalPanelButtons.template;
this.log.d('Setting EXTERNAL TOOLBAR ADDITIONAL PANEL BUTTONS');
}
if (externalDirectives.additionalPanels) {
config.additionalPanelsTemplate = externalDirectives.additionalPanels.template;
this.log.d('Setting EXTERNAL ADDITIONAL PANELS');
}
if (externalDirectives.participantPanelItemElements) {
config.participantPanelItemElementsTemplate = externalDirectives.participantPanelItemElements.template;
this.log.d('Setting EXTERNAL PARTICIPANT PANEL ITEM ELEMENTS');
}
this.log.d('Template setup completed', config);
return config;
}
/**
* Sets up the toolbar template
*/
private setupToolbarTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.toolbar) {
this.log.d('Setting EXTERNAL TOOLBAR');
return externalDirectives.toolbar.template;
} else {
this.log.d('Setting DEFAULT TOOLBAR');
return defaultTemplates.toolbar;
}
}
/**
* Sets up the panel template
*/
private setupPanelTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.panel) {
this.log.d('Setting EXTERNAL PANEL');
return externalDirectives.panel.template;
} else {
this.log.d('Setting DEFAULT PANEL');
return defaultTemplates.panel;
}
}
/**
* Sets up the layout template
*/
private setupLayoutTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.layout) {
this.log.d('Setting EXTERNAL LAYOUT');
return externalDirectives.layout.template;
} else {
this.log.d('Setting DEFAULT LAYOUT');
return defaultTemplates.layout;
}
}
/**
* Sets up the prejoin template
*/
private setupPreJoinTemplate(externalDirectives: ExternalDirectives): TemplateRef<any> | undefined {
if (externalDirectives.preJoin) {
this.log.d('Setting EXTERNAL PREJOIN');
return externalDirectives.preJoin.template;
} else {
this.log.d('Setting DEFAULT PREJOIN (none)');
return undefined;
}
}
/**
* Sets up the chat panel template
*/
private setupChatPanelTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.chatPanel) {
this.log.d('Setting EXTERNAL CHAT PANEL');
return externalDirectives.chatPanel.template;
} else {
this.log.d('Setting DEFAULT CHAT PANEL');
return defaultTemplates.chatPanel;
}
}
/**
* Sets up the participants panel template
*/
private setupParticipantsPanelTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.participantsPanel) {
this.log.d('Setting EXTERNAL PARTICIPANTS PANEL');
return externalDirectives.participantsPanel.template;
} else {
this.log.d('Setting DEFAULT PARTICIPANTS PANEL');
return defaultTemplates.participantsPanel;
}
}
/**
* Sets up the activities panel template
*/
private setupActivitiesPanelTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.activitiesPanel) {
this.log.d('Setting EXTERNAL ACTIVITIES PANEL');
return externalDirectives.activitiesPanel.template;
} else {
this.log.d('Setting DEFAULT ACTIVITIES PANEL');
return defaultTemplates.activitiesPanel;
}
}
/**
* Sets up the participant panel item template
*/
private setupParticipantPanelItemTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.participantPanelItem) {
this.log.d('Setting EXTERNAL PARTICIPANT PANEL ITEM');
return externalDirectives.participantPanelItem.template;
} else {
this.log.d('Setting DEFAULT PARTICIPANT PANEL ITEM');
return defaultTemplates.participantPanelItem;
}
}
/**
* Sets up the stream template
*/
private setupStreamTemplate(
externalDirectives: ExternalDirectives,
defaultTemplates: DefaultTemplates
): TemplateRef<any> {
if (externalDirectives.stream) {
this.log.d('Setting EXTERNAL STREAM');
return externalDirectives.stream.template;
} else {
this.log.d('Setting DEFAULT STREAM');
return defaultTemplates.stream;
}
}
/**
* Sets up templates for the PanelComponent
*/
setupPanelTemplates(
externalParticipantsPanel?: ParticipantsPanelDirective,
externalChatPanel?: ChatPanelDirective,
externalActivitiesPanel?: ActivitiesPanelDirective,
externalAdditionalPanels?: AdditionalPanelsDirective
): PanelTemplateConfiguration {
this.log.d('Setting up panel templates...');
return {
participantsPanelTemplate: externalParticipantsPanel?.template,
chatPanelTemplate: externalChatPanel?.template,
activitiesPanelTemplate: externalActivitiesPanel?.template,
additionalPanelsTemplate: externalAdditionalPanels?.template
};
}
/**
* Sets up templates for the ToolbarComponent
*/
setupToolbarTemplates(
externalAdditionalButtons?: ToolbarAdditionalButtonsDirective,
externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective
): ToolbarTemplateConfiguration {
this.log.d('Setting up toolbar templates...');
return {
toolbarAdditionalButtonsTemplate: externalAdditionalButtons?.template,
toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template
};
}
/**
* Sets up templates for the ParticipantsPanelComponent
*/
setupParticipantsPanelTemplates(
externalParticipantPanelItem?: ParticipantPanelItemDirective,
defaultParticipantPanelItem?: TemplateRef<any>
): ParticipantsPanelTemplateConfiguration {
this.log.d('Setting up participants panel templates...');
return {
participantPanelItemTemplate: externalParticipantPanelItem?.template || defaultParticipantPanelItem
};
}
/**
* Sets up templates for the ParticipantPanelItemComponent
*/
setupParticipantPanelItemTemplates(
externalParticipantPanelItemElements?: ParticipantPanelItemElementsDirective
): ParticipantPanelItemTemplateConfiguration {
this.log.d('Setting up participant panel item templates...');
return {
participantPanelItemElementsTemplate: externalParticipantPanelItemElements?.template
};
}
/**
* Sets up templates for the SessionComponent
*/
setupSessionTemplates(
toolbarTemplate?: TemplateRef<any>,
panelTemplate?: TemplateRef<any>,
layoutTemplate?: TemplateRef<any>
): SessionTemplateConfiguration {
this.log.d('Setting up session templates...');
return {
toolbarTemplate,
panelTemplate,
layoutTemplate
};
}
}