mirror of https://github.com/OpenVidu/openvidu.git
ov-components: implement custom leave button directive and enhance toolbar for additional button templates
parent
2a9f3a62fa
commit
41152de276
|
@ -63,9 +63,10 @@
|
|||
</mat-menu>
|
||||
}
|
||||
|
||||
<ng-container *ngIf="toolbarAdditionalButtonsTemplate && additionalButtonsPosition && additionalButtonsPosition === 'beforeMenu'">
|
||||
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate"></ng-container>
|
||||
</ng-container>
|
||||
<!-- Additional buttons injection from parent component (desktop/tablet only) -->
|
||||
@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'beforeMenu') {
|
||||
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate; context: { $implicit: additionalButtonsPosition }"></ng-container>
|
||||
}
|
||||
|
||||
<!-- More options button -->
|
||||
@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 }}"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
<mat-menu #settingsMenu="matMenu" id="more-options-menu">
|
||||
|
||||
<!-- Dynamic mobile buttons -->
|
||||
@for (button of buttonsInMoreOptions(); track button.key) {
|
||||
@if (button.show) {
|
||||
<button
|
||||
mat-menu-item
|
||||
[id]="button.key + '-btn'"
|
||||
[disabled]="button.disabled"
|
||||
(click)="button.action()"
|
||||
>
|
||||
<mat-icon [color]="button.color || ''">{{ button.icon }}</mat-icon>
|
||||
<span>{{ button.label | translate }}</span>
|
||||
</button>
|
||||
}
|
||||
<!-- Additional buttons injection inside menu (mobile only) -->
|
||||
@if (showAdditionalButtonsInsideMenu() && additionalButtonsPosition === 'beforeMenu') {
|
||||
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate; context: { $implicit: additionalButtonsPosition }">
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<!-- Divider if there are mobile buttons -->
|
||||
@if (buttonsInMoreOptions().length > 0) {
|
||||
<mat-divider class="divider"></mat-divider>
|
||||
}
|
||||
<!-- Fullscreen button -->
|
||||
@if (showFullscreenButton) {
|
||||
<button mat-menu-item id="fullscreen-btn" (click)="onFullscreenToggle()">
|
||||
|
@ -177,16 +160,22 @@
|
|||
|
||||
<!-- Captions button -->
|
||||
<!-- <button
|
||||
*ngIf="!isMinimal && showCaptionsButton"
|
||||
[disabled]="isConnectionLost"
|
||||
mat-menu-item
|
||||
id="captions-btn"
|
||||
(click)="onCaptionsToggle()"
|
||||
>
|
||||
<mat-icon>closed_caption</mat-icon>
|
||||
<span *ngIf="captionsEnabled">{{ 'TOOLBAR.DISABLE_CAPTIONS' | translate }}</span>
|
||||
<span *ngIf="!captionsEnabled">{{ 'TOOLBAR.ENABLE_CAPTIONS' | translate }}</span>
|
||||
</button> -->
|
||||
*ngIf="!isMinimal && showCaptionsButton"
|
||||
[disabled]="isConnectionLost"
|
||||
mat-menu-item
|
||||
id="captions-btn"
|
||||
(click)="onCaptionsToggle()"
|
||||
>
|
||||
<mat-icon>closed_caption</mat-icon>
|
||||
<span *ngIf="captionsEnabled">{{ 'TOOLBAR.DISABLE_CAPTIONS' | translate }}</span>
|
||||
<span *ngIf="!captionsEnabled">{{ 'TOOLBAR.ENABLE_CAPTIONS' | translate }}</span>
|
||||
</button> -->
|
||||
|
||||
<!-- Additional buttons injection inside menu (mobile only) -->
|
||||
@if (showAdditionalButtonsInsideMenu() && additionalButtonsPosition === 'afterMenu') {
|
||||
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate; context: { $implicit: additionalButtonsPosition }">
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<!-- Divider before settings -->
|
||||
@if (showSettingsButton) {
|
||||
|
@ -203,20 +192,24 @@
|
|||
</mat-menu>
|
||||
}
|
||||
|
||||
<!-- External additional buttons -->
|
||||
<ng-container *ngIf="toolbarAdditionalButtonsTemplate && additionalButtonsPosition && additionalButtonsPosition === 'afterMenu'">
|
||||
<!-- Additional buttons injection from parent component (desktop/tablet only) -->
|
||||
@if (showAdditionalButtonsOutside() && additionalButtonsPosition === 'afterMenu') {
|
||||
<ng-container *ngTemplateOutlet="toolbarAdditionalButtonsTemplate"></ng-container>
|
||||
</ng-container>
|
||||
}
|
||||
|
||||
<!-- Leave session button -->
|
||||
@if (showLeaveButtonDirect()) {
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="onLeaveClick()"
|
||||
id="leave-btn"
|
||||
[class.mobile-btn]="isMobileView()"
|
||||
matTooltip="{{ 'TOOLBAR.LEAVE' | translate }}"
|
||||
>
|
||||
<mat-icon>call_end</mat-icon>
|
||||
</button>
|
||||
@if (toolbarLeaveButtonTemplate) {
|
||||
<ng-container *ngTemplateOutlet="toolbarLeaveButtonTemplate"></ng-container>
|
||||
} @else {
|
||||
<button
|
||||
mat-icon-button
|
||||
(click)="onLeaveClick()"
|
||||
id="leave-btn"
|
||||
[class.mobile-btn]="isMobileView()"
|
||||
matTooltip="{{ 'TOOLBAR.LEAVE' | translate }}"
|
||||
>
|
||||
<mat-icon>call_end</mat-icon>
|
||||
</button>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<any> | null = null;
|
||||
@Input() additionalButtonsPosition: ToolbarAdditionalButtonsPosition | undefined;
|
||||
|
||||
// Additional buttons template
|
||||
@Input() toolbarAdditionalButtonsTemplate: TemplateRef<any> | null = null;
|
||||
@Input() additionalButtonsPosition: ToolbarAdditionalButtonsPosition | undefined;
|
||||
// Leave button template
|
||||
@Input() toolbarLeaveButtonTemplate: TemplateRef<any> | 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<void>();
|
||||
@Output() microphoneToggled = new EventEmitter<void>();
|
||||
@Output() screenShareToggled = new EventEmitter<void>();
|
||||
@Output() screenTrackReplaced = new EventEmitter<void>();
|
||||
|
||||
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<void>();
|
||||
@Output() recordingToggled = new EventEmitter<void>();
|
||||
@Output() viewRecordingsClicked = new EventEmitter<void>();
|
||||
@Output() broadcastingToggled = new EventEmitter<void>();
|
||||
@Output() backgroundEffectsToggled = new EventEmitter<void>();
|
||||
@Output() captionsToggled = new EventEmitter<void>();
|
||||
@Output() settingsToggled = new EventEmitter<void>();
|
||||
|
||||
return buttons;
|
||||
});
|
||||
// Leave button output
|
||||
@Output() leaveClicked = new EventEmitter<void>();
|
||||
|
||||
// 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<void>();
|
||||
@Output() microphoneToggled = new EventEmitter<void>();
|
||||
@Output() screenShareToggled = new EventEmitter<void>();
|
||||
@Output() screenTrackReplaced = new EventEmitter<void>();
|
||||
onMicrophoneToggle(): void {
|
||||
this.microphoneToggled.emit();
|
||||
}
|
||||
|
||||
// More options menu outputs
|
||||
@Output() fullscreenToggled = new EventEmitter<void>();
|
||||
@Output() recordingToggled = new EventEmitter<void>();
|
||||
@Output() viewRecordingsClicked = new EventEmitter<void>();
|
||||
@Output() broadcastingToggled = new EventEmitter<void>();
|
||||
@Output() backgroundEffectsToggled = new EventEmitter<void>();
|
||||
@Output() captionsToggled = new EventEmitter<void>();
|
||||
@Output() settingsToggled = new EventEmitter<void>();
|
||||
onScreenShareToggle(): void {
|
||||
this.screenShareToggled.emit();
|
||||
}
|
||||
|
||||
// Leave button output
|
||||
@Output() leaveClicked = new EventEmitter<void>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
[showLeaveButton]="showLeaveButton"
|
||||
[toolbarAdditionalButtonsTemplate]="toolbarAdditionalButtonsTemplate"
|
||||
[additionalButtonsPosition]="additionalButtonsPosition"
|
||||
[toolbarLeaveButtonTemplate]="toolbarLeaveButtonTemplate"
|
||||
(cameraToggled)="toggleCamera()"
|
||||
(microphoneToggled)="toggleMicrophone()"
|
||||
(screenShareToggled)="toggleScreenShare()"
|
||||
|
|
|
@ -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<any>;
|
||||
@ContentChild('toolbarAdditionalButtons', { read: TemplateRef }) toolbarAdditionalButtonsTemplate: TemplateRef<any> | undefined;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild('toolbarAdditionalPanelButtons', { read: TemplateRef }) toolbarAdditionalPanelButtonsTemplate: TemplateRef<any>;
|
||||
@ContentChild('toolbarLeaveButton', { read: TemplateRef }) toolbarLeaveButtonTemplate: TemplateRef<any> | undefined;
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
@ContentChild('toolbarAdditionalPanelButtons', { read: TemplateRef }) toolbarAdditionalPanelButtonsTemplate:
|
||||
| TemplateRef<any>
|
||||
| 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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -92,6 +92,10 @@
|
|||
<ng-template #toolbarAdditionalPanelButtons>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularToolbarAdditionalPanelButtonsTemplate"></ng-container>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #toolbarLeaveButton>
|
||||
<ng-container *ngTemplateOutlet="openviduAngularToolbarLeaveButtonTemplate"></ng-container>
|
||||
</ng-template>
|
||||
</ov-toolbar>
|
||||
</ng-template>
|
||||
|
||||
|
|
|
@ -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<any>;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
openviduAngularToolbarLeaveButtonTemplate: TemplateRef<any> | 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',
|
||||
|
|
|
@ -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
|
||||
* <ov-videoconference>
|
||||
* <ng-container *ovLeaveButton>
|
||||
* <button class="my-leave-button" (click)="customLeave()">
|
||||
* Leave meeting
|
||||
* </button>
|
||||
* </ng-container>
|
||||
* </ov-videoconference>
|
||||
* ```
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[ovToolbarLeaveButton]',
|
||||
standalone: false
|
||||
})
|
||||
export class LeaveButtonDirective {
|
||||
constructor(
|
||||
public template: TemplateRef<any>,
|
||||
public container: ViewContainerRef
|
||||
) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* The ***ovLayoutAdditionalElements** directive allows you to inject custom HTML or Angular templates
|
||||
* as additional layout elements within the videoconference UI.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<any>;
|
||||
toolbarAdditionalButtonsTemplate?: TemplateRef<any>;
|
||||
toolbarAdditionalPanelButtonsTemplate?: TemplateRef<any>;
|
||||
toolbarLeaveButtonTemplate?: TemplateRef<any>;
|
||||
|
||||
// Panel templates
|
||||
panelTemplate: TemplateRef<any>;
|
||||
|
@ -69,6 +71,7 @@ export interface PanelTemplateConfiguration {
|
|||
export interface ToolbarTemplateConfiguration {
|
||||
toolbarAdditionalButtonsTemplate?: TemplateRef<any>;
|
||||
toolbarAdditionalPanelButtonsTemplate?: TemplateRef<any>;
|
||||
toolbarLeaveButtonTemplate?: TemplateRef<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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';
|
||||
|
|
Loading…
Reference in New Issue