diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html index 70f1f817..1356d0fc 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.html @@ -63,9 +63,10 @@ } - - - + +@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'beforeMenu') { + +} @if (showMoreOptionsButtonDirect()) { @@ -75,35 +76,17 @@ [matMenuTriggerFor]="settingsMenu" [disabled]="isConnectionLost" [class.mobile-btn]="isMobileView()" - [matBadge]="hasActiveFeatures() && isMobileView() ? '!' : ''" - matBadgeColor="accent" - matBadgeSize="small" - [matBadgeHidden]="!hasActiveFeatures() || !isMobileView()" matTooltip="{{ 'TOOLBAR.MORE_OPTIONS' | translate }}" > more_vert - - - @for (button of buttonsInMoreOptions(); track button.key) { - @if (button.show) { - - } + + @if (showAdditionalButtonsInsideMenu() && additionalButtonsPosition === 'beforeMenu') { + + } - - @if (buttonsInMoreOptions().length > 0) { - - } @if (showFullscreenButton) { --> + + + @if (showAdditionalButtonsInsideMenu() && additionalButtonsPosition === 'afterMenu') { + + + } @if (showSettingsButton) { @@ -203,20 +192,24 @@ } - - + +@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'afterMenu') { - +} @if (showLeaveButtonDirect()) { - + @if (toolbarLeaveButtonTemplate) { + + } @else { + + } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss index 20d425ae..d3c16431 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.scss @@ -39,10 +39,9 @@ // Mobile responsive styles &.mobile-btn { - margin: 3px; + padding: 8px 0; width: 36px; height: 36px; - text-align: justify; mat-icon { font-size: 20px; @@ -59,7 +58,8 @@ background-color: var(--ov-accent-action-color) !important; } - #leave-btn { + #leave-btn, + ::ng-deep #leave-btn { background-color: var(--ov-error-color) !important; border-radius: var(--ov-leave-button-radius) !important; width: 65px !important; @@ -68,7 +68,6 @@ &.mobile-btn { width: 56px !important; height: 36px !important; - margin: 3px; text-align: center; } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts index 4fa97038..1423e787 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component.ts @@ -4,231 +4,183 @@ import { BroadcastingStatus } from '../../../models/broadcasting.model'; import { ToolbarAdditionalButtonsPosition } from '../../../models/toolbar.model'; import { ViewportService } from '../../../services/viewport/viewport.service'; +/** + * @internal + */ @Component({ - selector: 'ov-toolbar-media-buttons', - templateUrl: './toolbar-media-buttons.component.html', - styleUrl: './toolbar-media-buttons.component.scss', - standalone: false + selector: 'ov-toolbar-media-buttons', + templateUrl: './toolbar-media-buttons.component.html', + styleUrl: './toolbar-media-buttons.component.scss', + standalone: false }) export class ToolbarMediaButtonsComponent { + // Camera related inputs + @Input() showCameraButton: boolean = true; + @Input() isCameraEnabled: boolean = true; + @Input() cameraMuteChanging: boolean = false; - // Camera related inputs - @Input() showCameraButton: boolean = true; - @Input() isCameraEnabled: boolean = true; - @Input() cameraMuteChanging: boolean = false; + // Microphone related inputs + @Input() showMicrophoneButton: boolean = true; + @Input() isMicrophoneEnabled: boolean = true; + @Input() microphoneMuteChanging: boolean = false; - // Microphone related inputs - @Input() showMicrophoneButton: boolean = true; - @Input() isMicrophoneEnabled: boolean = true; - @Input() microphoneMuteChanging: boolean = false; + // Screenshare related inputs + @Input() showScreenshareButton: boolean = true; + @Input() isScreenShareEnabled: boolean = false; - // Screenshare related inputs - @Input() showScreenshareButton: boolean = true; - @Input() isScreenShareEnabled: boolean = false; + // Device availability inputs + @Input() hasVideoDevices: boolean = true; + @Input() hasAudioDevices: boolean = true; - // Device availability inputs - @Input() hasVideoDevices: boolean = true; - @Input() hasAudioDevices: boolean = true; + // Connection state inputs + @Input() isConnectionLost: boolean = false; - // Connection state inputs - @Input() isConnectionLost: boolean = false; + // UI state inputs + @Input() isMinimal: boolean = false; - // UI state inputs - @Input() isMinimal: boolean = false; + // More options menu inputs + @Input() showMoreOptionsButton: boolean = true; + @Input() showFullscreenButton: boolean = true; + @Input() showRecordingButton: boolean = true; + @Input() showViewRecordingsButton: boolean = false; + @Input() showBroadcastingButton: boolean = true; + @Input() showBackgroundEffectsButton: boolean = true; + @Input() showCaptionsButton: boolean = true; + @Input() showSettingsButton: boolean = true; - // More options menu inputs - @Input() showMoreOptionsButton: boolean = true; - @Input() showFullscreenButton: boolean = true; - @Input() showRecordingButton: boolean = true; - @Input() showViewRecordingsButton: boolean = false; - @Input() showBroadcastingButton: boolean = true; - @Input() showBackgroundEffectsButton: boolean = true; - @Input() showCaptionsButton: boolean = true; - @Input() showSettingsButton: boolean = true; + // Fullscreen state + @Input() isFullscreenActive: boolean = false; - // Fullscreen state - @Input() isFullscreenActive: boolean = false; + // Recording related inputs + @Input() recordingStatus: RecordingStatus = RecordingStatus.STOPPED; + @Input() hasRoomTracksPublished: boolean = false; - // Recording related inputs - @Input() recordingStatus: RecordingStatus = RecordingStatus.STOPPED; - @Input() hasRoomTracksPublished: boolean = false; + // Broadcasting related inputs + @Input() broadcastingStatus: BroadcastingStatus = BroadcastingStatus.STOPPED; - // Broadcasting related inputs - @Input() broadcastingStatus: BroadcastingStatus = BroadcastingStatus.STOPPED; + // Captions + @Input() captionsEnabled: boolean = false; - // Captions - @Input() captionsEnabled: boolean = false; + // Leave button + @Input() showLeaveButton: boolean = true; - // Leave button - @Input() showLeaveButton: boolean = true; + // Additional buttons template + @Input() toolbarAdditionalButtonsTemplate: TemplateRef | null = null; + @Input() additionalButtonsPosition: ToolbarAdditionalButtonsPosition | undefined; - // Additional buttons template - @Input() toolbarAdditionalButtonsTemplate: TemplateRef | null = null; - @Input() additionalButtonsPosition: ToolbarAdditionalButtonsPosition | undefined; + // Leave button template + @Input() toolbarLeaveButtonTemplate: TemplateRef | null = null; - // Status enums for template usage - _recordingStatus = RecordingStatus; - _broadcastingStatus = BroadcastingStatus; + // Status enums for template usage + _recordingStatus = RecordingStatus; + _broadcastingStatus = BroadcastingStatus; - // Viewport service for responsive behavior - private viewportService = inject(ViewportService); + // Viewport service for responsive behavior + private viewportService = inject(ViewportService); - // Computed properties for responsive button grouping - readonly isMobileView = computed(() => this.viewportService.isMobile()); - readonly isTabletView = computed(() => this.viewportService.isTablet()); - readonly isDesktopView = computed(() => this.viewportService.isDesktop()); + // Computed properties for responsive button grouping + readonly isMobileView = computed(() => this.viewportService.isMobile()); + readonly isTabletView = computed(() => this.viewportService.isTablet()); + readonly isDesktopView = computed(() => this.viewportService.isDesktop()); - // Essential buttons that always stay visible - readonly showCameraButtonDirect = computed(() => - this.showCameraButton && !this.isMinimal - ); + // Essential buttons that always stay visible + readonly showCameraButtonDirect = computed(() => this.showCameraButton && !this.isMinimal); - readonly showMicrophoneButtonDirect = computed(() => - this.showMicrophoneButton && !this.isMinimal - ); + readonly showMicrophoneButtonDirect = computed(() => this.showMicrophoneButton && !this.isMinimal); - // Screenshare button - visible on tablet+ or when already active - readonly showScreenshareButtonDirect = computed(() => - this.showScreenshareButton && - !this.isMinimal && - (!this.isMobileView() || this.isScreenShareEnabled) - ); + // Screenshare button - visible on tablet+ or when already active + readonly showScreenshareButtonDirect = computed( + () => this.showScreenshareButton && !this.isMinimal && (!this.isMobileView() || this.isScreenShareEnabled) + ); - // More options button - always visible when not minimal - readonly showMoreOptionsButtonDirect = computed(() => - this.showMoreOptionsButton && !this.isMinimal - ); + // More options button - always visible when not minimal + readonly showMoreOptionsButtonDirect = computed(() => this.showMoreOptionsButton && !this.isMinimal); - // Leave button - always visible - readonly showLeaveButtonDirect = computed(() => - this.showLeaveButton - ); + // Leave button - always visible + readonly showLeaveButtonDirect = computed(() => this.showLeaveButton); - // Buttons that should be moved to "More Options" on mobile - readonly buttonsInMoreOptions = computed(() => { - const buttons: Array<{ - key: string; - show: boolean; - label: string; - icon: string; - action: () => void; - disabled?: boolean; - active?: boolean; - color?: string; - }> = []; + // Check if there are active features that should show a badge on More Options + readonly hasActiveFeatures = computed( + () => + this.isScreenShareEnabled || + this.recordingStatus === this._recordingStatus.STARTED || + this.broadcastingStatus === this._broadcastingStatus.STARTED + ); - const isMobile = this.isMobileView(); + // Check if additional buttons should be shown outside (desktop/tablet) or inside More Options (mobile) + readonly showAdditionalButtonsOutside = computed(() => { + return !this.isMobileView() && this.toolbarAdditionalButtonsTemplate; + }); - // On mobile, screenshare goes to more options when not active - if (isMobile && this.showScreenshareButton && !this.isScreenShareEnabled) { - buttons.push({ - key: 'screenshare', - show: true, - label: 'TOOLBAR.ENABLE_SCREEN', - icon: 'screen_share', - action: () => this.onScreenShareToggle(), - disabled: this.isConnectionLost - }); - } + // Check if additional buttons should be shown inside More Options menu (mobile only) + readonly showAdditionalButtonsInsideMenu = computed(() => { + return this.isMobileView() && this.toolbarAdditionalButtonsTemplate; + }); - // Replace screenshare option when active on mobile - if (isMobile && this.showScreenshareButton && this.isScreenShareEnabled) { - buttons.push({ - key: 'screenshare-replace', - show: true, - label: 'STREAM.REPLACE_SCREEN', - icon: 'picture_in_picture', - action: () => this.onScreenTrackReplace(), - disabled: this.isConnectionLost - }); + // Media button outputs + @Output() cameraToggled = new EventEmitter(); + @Output() microphoneToggled = new EventEmitter(); + @Output() screenShareToggled = new EventEmitter(); + @Output() screenTrackReplaced = new EventEmitter(); - buttons.push({ - key: 'screenshare-stop', - show: true, - label: 'TOOLBAR.DISABLE_SCREEN', - icon: 'stop_screen_share', - action: () => this.onScreenShareToggle(), - disabled: this.isConnectionLost, - color: 'warn' - }); - } + // More options menu outputs + @Output() fullscreenToggled = new EventEmitter(); + @Output() recordingToggled = new EventEmitter(); + @Output() viewRecordingsClicked = new EventEmitter(); + @Output() broadcastingToggled = new EventEmitter(); + @Output() backgroundEffectsToggled = new EventEmitter(); + @Output() captionsToggled = new EventEmitter(); + @Output() settingsToggled = new EventEmitter(); - return buttons; - }); + // Leave button output + @Output() leaveClicked = new EventEmitter(); - // Check if there are active features that should show a badge on More Options - readonly hasActiveFeatures = computed(() => - this.isScreenShareEnabled || - this.recordingStatus === this._recordingStatus.STARTED || - this.broadcastingStatus === this._broadcastingStatus.STARTED - ); + // Event handler methods + onCameraToggle(): void { + this.cameraToggled.emit(); + } - // Media button outputs - @Output() cameraToggled = new EventEmitter(); - @Output() microphoneToggled = new EventEmitter(); - @Output() screenShareToggled = new EventEmitter(); - @Output() screenTrackReplaced = new EventEmitter(); + onMicrophoneToggle(): void { + this.microphoneToggled.emit(); + } - // More options menu outputs - @Output() fullscreenToggled = new EventEmitter(); - @Output() recordingToggled = new EventEmitter(); - @Output() viewRecordingsClicked = new EventEmitter(); - @Output() broadcastingToggled = new EventEmitter(); - @Output() backgroundEffectsToggled = new EventEmitter(); - @Output() captionsToggled = new EventEmitter(); - @Output() settingsToggled = new EventEmitter(); + onScreenShareToggle(): void { + this.screenShareToggled.emit(); + } - // Leave button output - @Output() leaveClicked = new EventEmitter(); + onScreenTrackReplace(): void { + this.screenTrackReplaced.emit(); + } - // Event handler methods - onCameraToggle(): void { - this.cameraToggled.emit(); - } + onFullscreenToggle(): void { + this.fullscreenToggled.emit(); + } - onMicrophoneToggle(): void { - this.microphoneToggled.emit(); - } + onRecordingToggle(): void { + this.recordingToggled.emit(); + } - onScreenShareToggle(): void { - this.screenShareToggled.emit(); - } + onViewRecordingsClick(): void { + this.viewRecordingsClicked.emit(); + } - onScreenTrackReplace(): void { - this.screenTrackReplaced.emit(); - } + onBroadcastingToggle(): void { + this.broadcastingToggled.emit(); + } - onFullscreenToggle(): void { - this.fullscreenToggled.emit(); - } + onBackgroundEffectsToggle(): void { + this.backgroundEffectsToggled.emit(); + } - onRecordingToggle(): void { - this.recordingToggled.emit(); - } - - onViewRecordingsClick(): void { - this.viewRecordingsClicked.emit(); - } - - onBroadcastingToggle(): void { - this.broadcastingToggled.emit(); - } - - onBackgroundEffectsToggle(): void { - this.backgroundEffectsToggled.emit(); - } - - onCaptionsToggle(): void { - this.captionsToggled.emit(); - } - - onSettingsToggle(): void { - this.settingsToggled.emit(); - } - - onLeaveClick(): void { - this.leaveClicked.emit(); - } + onCaptionsToggle(): void { + this.captionsToggled.emit(); + } + onSettingsToggle(): void { + this.settingsToggled.emit(); + } + onLeaveClick(): void { + this.leaveClicked.emit(); + } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html index 8034f36a..8bdfa348 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.html @@ -69,6 +69,7 @@ [showLeaveButton]="showLeaveButton" [toolbarAdditionalButtonsTemplate]="toolbarAdditionalButtonsTemplate" [additionalButtonsPosition]="additionalButtonsPosition" + [toolbarLeaveButtonTemplate]="toolbarLeaveButtonTemplate" (cameraToggled)="toggleCamera()" (microphoneToggled)="toggleMicrophone()" (screenShareToggled)="toggleScreenShare()" diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts index 4eeb85aa..8f16c5d0 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/toolbar/toolbar.component.ts @@ -50,6 +50,7 @@ import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.servic import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model'; import { Room, RoomEvent } from 'livekit-client'; import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model'; +import { LeaveButtonDirective } from '../../directives/template/internals.directive'; /** * The **ToolbarComponent** is hosted inside of the {@link VideoconferenceComponent}. @@ -66,12 +67,18 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { /** * @ignore */ - @ContentChild('toolbarAdditionalButtons', { read: TemplateRef }) toolbarAdditionalButtonsTemplate: TemplateRef; + @ContentChild('toolbarAdditionalButtons', { read: TemplateRef }) toolbarAdditionalButtonsTemplate: TemplateRef | undefined; /** * @ignore */ - @ContentChild('toolbarAdditionalPanelButtons', { read: TemplateRef }) toolbarAdditionalPanelButtonsTemplate: TemplateRef; + @ContentChild('toolbarLeaveButton', { read: TemplateRef }) toolbarLeaveButtonTemplate: TemplateRef | undefined; + /** + * @ignore + */ + @ContentChild('toolbarAdditionalPanelButtons', { read: TemplateRef }) toolbarAdditionalPanelButtonsTemplate: + | TemplateRef + | undefined; /** * @ignore @@ -84,6 +91,17 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { } } + /** + * @ignore + */ + @ContentChild(LeaveButtonDirective) + set externalLeaveButton(externalLeaveButton: LeaveButtonDirective) { + this._externalLeaveButton = externalLeaveButton; + if (externalLeaveButton) { + this.updateTemplatesAndMarkForCheck(); + } + } + /** * @ignore */ @@ -153,12 +171,12 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { /** * @ignore */ - @ViewChild(MatMenuTrigger) public menuTrigger: MatMenuTrigger; + @ViewChild(MatMenuTrigger) public menuTrigger: MatMenuTrigger | undefined; /** * @ignore */ - room: Room; + room!: Room; /** * @ignore */ @@ -186,11 +204,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { /** * @ignore */ - hasVideoDevices: boolean; + hasVideoDevices: boolean = true; /** * @ignore */ - hasAudioDevices: boolean; + hasAudioDevices: boolean = true; /** * @ignore */ @@ -305,7 +323,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { /** * @ignore */ - additionalButtonsPosition: ToolbarAdditionalButtonsPosition; + additionalButtonsPosition: ToolbarAdditionalButtonsPosition = ToolbarAdditionalButtonsPosition.BEFORE_MENU; /** * @ignore @@ -357,7 +375,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { /** * @ignore */ - recordingTime: Date; + recordingTime: Date | undefined; /** * @internal @@ -367,6 +385,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { // Store directive references for template setup private _externalAdditionalButtons?: ToolbarAdditionalButtonsDirective; + private _externalLeaveButton?: LeaveButtonDirective; private _externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective; private log: ILogger; @@ -416,7 +435,7 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { * @ignore */ @HostListener('window:resize', ['$event']) - sizeChange(event) { + sizeChange(_: Event) { if (this.currentWindowHeight >= window.innerHeight) { // The user has exit the fullscreen mode this.currentWindowHeight = window.innerHeight; @@ -474,7 +493,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { private setupTemplates(): void { this.templateConfig = this.templateManagerService.setupToolbarTemplates( this._externalAdditionalButtons, - this._externalAdditionalPanelButtons + this._externalAdditionalPanelButtons, + this._externalLeaveButton ); // Apply templates to component properties for backward compatibility @@ -492,6 +512,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) { this.toolbarAdditionalPanelButtonsTemplate = this.templateConfig.toolbarAdditionalPanelButtonsTemplate; } + if (this.templateConfig.toolbarLeaveButtonTemplate) { + this.toolbarLeaveButtonTemplate = this.templateConfig.toolbarLeaveButtonTemplate; + } } /** @@ -518,11 +541,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { this.microphoneMuteChanging = false; const isMicrophoneEnabled = this.participantService.isMyMicrophoneEnabled(); await this.participantService.setMicrophoneEnabled(!isMicrophoneEnabled); - } catch (error) { - this.log.e('There was an error toggling microphone:', error.code, error.message); + } catch (error: unknown) { + this.log.e('There was an error toggling microphone:', (error as any).code, (error as any).message); this.actionService.openDialog( this.translateService.translate('ERRORS.TOGGLE_MICROPHONE'), - error?.error || error?.message || error + (error as any)?.error || (error as any)?.message || error ); } finally { this.microphoneMuteChanging = false; @@ -541,8 +564,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { } await this.participantService.setCameraEnabled(!isCameraEnabled); } catch (error) { - this.log.e('There was an error toggling camera:', error.code, error.message); - this.actionService.openDialog(this.translateService.translate('ERRORS.TOGGLE_CAMERA'), error?.error || error?.message || error); + this.log.e('There was an error toggling camera:', (error as any).code, (error as any).message); + this.actionService.openDialog( + this.translateService.translate('ERRORS.TOGGLE_CAMERA'), + (error as any)?.error || (error as any)?.message || error + ); } finally { this.cameraMuteChanging = false; } @@ -579,8 +605,11 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit { this.onRoomDisconnected.emit(); }, false); } catch (error) { - this.log.e('There was an error disconnecting:', error.code, error.message); - this.actionService.openDialog(this.translateService.translate('ERRORS.DISCONNECT'), error?.error || error?.message || error); + this.log.e('There was an error disconnecting:', (error as any).code, (error as any).message); + this.actionService.openDialog( + this.translateService.translate('ERRORS.DISCONNECT'), + (error as any)?.error || (error as any)?.message || error + ); } } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html index 89f616e5..93f69b0c 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.html @@ -92,6 +92,10 @@ + + + + diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts index 90264f54..e704c932 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/videoconference/videoconference.component.ts @@ -61,7 +61,8 @@ import { LangOption } from '../../models/lang.model'; import { LayoutAdditionalElementsDirective, ParticipantPanelAfterLocalParticipantDirective, - PreJoinDirective + PreJoinDirective, + LeaveButtonDirective } from '../../directives/template/internals.directive'; /** @@ -124,6 +125,24 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { return this._externalToolbarAdditionalButtons; } + private _externalToolbarLeaveButton?: LeaveButtonDirective; + + /** + * @internal + */ + @ContentChild(LeaveButtonDirective) + set externalToolbarLeaveButton(value: LeaveButtonDirective) { + this._externalToolbarLeaveButton = value; + this.setupTemplates(); + } + + /** + * @internal + */ + get externalToolbarLeaveButton(): LeaveButtonDirective | undefined { + return this._externalToolbarLeaveButton; + } + private _externalToolbarAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective; /** @@ -397,6 +416,12 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { * @internal */ openviduAngularToolbarAdditionalButtonsTemplate: TemplateRef; + + /** + * @internal + */ + openviduAngularToolbarLeaveButtonTemplate: TemplateRef | undefined; + /** * @internal */ @@ -744,6 +769,7 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { toolbar: this.externalToolbar, toolbarAdditionalButtons: this.externalToolbarAdditionalButtons, toolbarAdditionalPanelButtons: this.externalToolbarAdditionalPanelButtons, + toolbarLeaveButton: this.externalToolbarLeaveButton, additionalPanels: this.externalAdditionalPanels, panel: this.externalPanel, chatPanel: this.externalChatPanel, @@ -800,6 +826,9 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit { if (this.templateConfig.toolbarAdditionalButtonsTemplate) { assignIfChanged('openviduAngularToolbarAdditionalButtonsTemplate', this.templateConfig.toolbarAdditionalButtonsTemplate); } + if (this.templateConfig.toolbarLeaveButtonTemplate) { + assignIfChanged('openviduAngularToolbarLeaveButtonTemplate', this.templateConfig.toolbarLeaveButtonTemplate); + } if (this.templateConfig.toolbarAdditionalPanelButtonsTemplate) { assignIfChanged( 'openviduAngularToolbarAdditionalPanelButtonsTemplate', diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts index 74fabe38..14be054d 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/internals.directive.ts @@ -222,6 +222,33 @@ export class ParticipantPanelAfterLocalParticipantDirective { ) {} } +/** + * The ***ovLeaveButton** directive allows you to replace the default leave button with a custom template. + * Use this directive to provide your own button, confirm dialogs, or any custom leave logic while keeping + * the internal leave flow intact. + * + * Usage example: + * ```html + * + * + * + * + * + * ``` + */ +@Directive({ + selector: '[ovToolbarLeaveButton]', + standalone: false +}) +export class LeaveButtonDirective { + constructor( + public template: TemplateRef, + public container: ViewContainerRef + ) {} +} + /** * The ***ovLayoutAdditionalElements** directive allows you to inject custom HTML or Angular templates * as additional layout elements within the videoconference UI. diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts index ea096455..89f0aa90 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/directives/template/openvidu-components-angular.directive.module.ts @@ -18,7 +18,8 @@ import { LayoutAdditionalElementsDirective, ParticipantPanelAfterLocalParticipantDirective, ParticipantPanelParticipantBadgeDirective, - PreJoinDirective + PreJoinDirective, + LeaveButtonDirective } from './internals.directive'; @NgModule({ @@ -32,6 +33,7 @@ import { StreamDirective, ToolbarDirective, ToolbarAdditionalButtonsDirective, + LeaveButtonDirective, ToolbarAdditionalPanelButtonsDirective, ParticipantPanelItemElementsDirective, ActivitiesPanelDirective, @@ -51,6 +53,7 @@ import { StreamDirective, ToolbarDirective, ToolbarAdditionalButtonsDirective, + LeaveButtonDirective, ToolbarAdditionalPanelButtonsDirective, ParticipantPanelItemElementsDirective, ActivitiesPanelDirective, diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts index c24278af..15a3c91f 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/template/template-manager.service.ts @@ -18,7 +18,8 @@ import { import { PreJoinDirective, ParticipantPanelAfterLocalParticipantDirective, - LayoutAdditionalElementsDirective + LayoutAdditionalElementsDirective, + LeaveButtonDirective } from '../../directives/template/internals.directive'; /** @@ -29,6 +30,7 @@ export interface TemplateConfiguration { toolbarTemplate: TemplateRef; toolbarAdditionalButtonsTemplate?: TemplateRef; toolbarAdditionalPanelButtonsTemplate?: TemplateRef; + toolbarLeaveButtonTemplate?: TemplateRef; // Panel templates panelTemplate: TemplateRef; @@ -69,6 +71,7 @@ export interface PanelTemplateConfiguration { export interface ToolbarTemplateConfiguration { toolbarAdditionalButtonsTemplate?: TemplateRef; toolbarAdditionalPanelButtonsTemplate?: TemplateRef; + toolbarLeaveButtonTemplate?: TemplateRef; } /** @@ -110,6 +113,7 @@ export interface ExternalDirectives { toolbar?: ToolbarDirective; toolbarAdditionalButtons?: ToolbarAdditionalButtonsDirective; toolbarAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective; + toolbarLeaveButton?: LeaveButtonDirective; additionalPanels?: AdditionalPanelsDirective; panel?: PanelDirective; chatPanel?: ChatPanelDirective; @@ -179,6 +183,11 @@ export class TemplateManagerService { this.log.v('Setting EXTERNAL TOOLBAR ADDITIONAL BUTTONS'); } + if (externalDirectives.toolbarLeaveButton) { + config.toolbarLeaveButtonTemplate = externalDirectives.toolbarLeaveButton.template; + this.log.v('Setting EXTERNAL TOOLBAR LEAVE BUTTON'); + } + if (externalDirectives.toolbarAdditionalPanelButtons) { config.toolbarAdditionalPanelButtonsTemplate = externalDirectives.toolbarAdditionalPanelButtons.template; this.log.v('Setting EXTERNAL TOOLBAR ADDITIONAL PANEL BUTTONS'); @@ -358,13 +367,15 @@ export class TemplateManagerService { */ setupToolbarTemplates( externalAdditionalButtons?: ToolbarAdditionalButtonsDirective, - externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective + externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective, + externalLeaveButton?: LeaveButtonDirective ): ToolbarTemplateConfiguration { this.log.v('Setting up toolbar templates...'); return { toolbarAdditionalButtonsTemplate: externalAdditionalButtons?.template, - toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template + toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template, + toolbarLeaveButtonTemplate: externalLeaveButton?.template }; } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts index 367565e4..d9b85c2f 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/public-api.ts @@ -19,7 +19,6 @@ export * from './lib/components/toolbar/toolbar-media-buttons/toolbar-media-butt export * from './lib/components/videoconference/videoconference.component'; export * from './lib/config/openvidu-components-angular.config'; // Directives -export * from './lib/directives/template/internals.directive'; export * from './lib/directives/api/activities-panel.directive'; export * from './lib/directives/api/admin.directive'; export * from './lib/directives/api/api.directive.module'; @@ -28,10 +27,12 @@ export * from './lib/directives/api/participant-panel-item.directive'; export * from './lib/directives/api/stream.directive'; export * from './lib/directives/api/toolbar.directive'; export * from './lib/directives/api/videoconference.directive'; + +export * from './lib/directives/template/internals.directive'; export * from './lib/directives/template/openvidu-components-angular.directive'; export * from './lib/directives/template/openvidu-components-angular.directive.module'; -export * from './lib/models/broadcasting.model'; // Models +export * from './lib/models/broadcasting.model'; export * from './lib/models/panel.model'; export * from './lib/models/participant.model'; export * from './lib/models/recording.model';