ov-components: Add directive for injecting custom menu items into toolbar more options

master
Carlos Santos 2025-11-24 13:38:29 +01:00
parent 171a5104ae
commit 9918b07f51
9 changed files with 145 additions and 12 deletions

View File

@ -177,6 +177,11 @@
</ng-container> </ng-container>
} }
<!-- Additional menu items injected via directive -->
@if (moreOptionsAdditionalMenuItemsTemplate) {
<ng-container *ngTemplateOutlet="moreOptionsAdditionalMenuItemsTemplate"></ng-container>
}
<!-- Divider before settings --> <!-- Divider before settings -->
@if (showSettingsButton) { @if (showSettingsButton) {
<mat-divider class="divider"></mat-divider> <mat-divider class="divider"></mat-divider>

View File

@ -1,8 +1,9 @@
import { Component, EventEmitter, Input, Output, TemplateRef, computed, inject } from '@angular/core'; import { Component, ContentChild, EventEmitter, Input, Output, TemplateRef, computed, inject } from '@angular/core';
import { RecordingStatus } from '../../../models/recording.model'; import { RecordingStatus } from '../../../models/recording.model';
import { BroadcastingStatus } from '../../../models/broadcasting.model'; import { BroadcastingStatus } from '../../../models/broadcasting.model';
import { ToolbarAdditionalButtonsPosition } from '../../../models/toolbar.model'; import { ToolbarAdditionalButtonsPosition } from '../../../models/toolbar.model';
import { ViewportService } from '../../../services/viewport/viewport.service'; import { ViewportService } from '../../../services/viewport/viewport.service';
import { ToolbarMoreOptionsAdditionalMenuItemsDirective } from '../../../directives/template/internals.directive';
/** /**
* @internal * @internal
@ -71,6 +72,21 @@ export class ToolbarMediaButtonsComponent {
// Leave button template // Leave button template
@Input() toolbarLeaveButtonTemplate: TemplateRef<any> | null = null; @Input() toolbarLeaveButtonTemplate: TemplateRef<any> | null = null;
/**
* @internal
* ContentChild for custom menu items in more options menu
*/
@ContentChild(ToolbarMoreOptionsAdditionalMenuItemsDirective)
externalMoreOptionsAdditionalMenuItems!: ToolbarMoreOptionsAdditionalMenuItemsDirective;
/**
* @internal
* Gets the template for additional menu items in more options
*/
get moreOptionsAdditionalMenuItemsTemplate(): TemplateRef<any> | undefined {
return this.externalMoreOptionsAdditionalMenuItems?.template;
}
// Status enums for template usage // Status enums for template usage
_recordingStatus = RecordingStatus; _recordingStatus = RecordingStatus;
_broadcastingStatus = BroadcastingStatus; _broadcastingStatus = BroadcastingStatus;

View File

@ -82,7 +82,12 @@
(captionsToggled)="onCaptionsToggle()" (captionsToggled)="onCaptionsToggle()"
(settingsToggled)="toggleSettings()" (settingsToggled)="toggleSettings()"
(leaveClicked)="disconnect()" (leaveClicked)="disconnect()"
></ov-toolbar-media-buttons> >
<!-- Inject additional menu items via content projection -->
<ng-container *ovToolbarMoreOptionsAdditionalMenuItems>
<ng-container *ngTemplateOutlet="externalMoreOptionsAdditionalMenuItems?.template"></ng-container>
</ng-container>
</ov-toolbar-media-buttons>
</div> </div>
<!-- Panel buttons --> <!-- Panel buttons -->

View File

@ -50,7 +50,7 @@ import { CdkOverlayService } from '../../services/cdk-overlay/cdk-overlay.servic
import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model'; import { ParticipantLeftEvent, ParticipantLeftReason, ParticipantModel } from '../../models/participant.model';
import { Room, RoomEvent } from 'livekit-client'; import { Room, RoomEvent } from 'livekit-client';
import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model'; import { ToolbarAdditionalButtonsPosition } from '../../models/toolbar.model';
import { LeaveButtonDirective } from '../../directives/template/internals.directive'; import { LeaveButtonDirective, ToolbarMoreOptionsAdditionalMenuItemsDirective } from '../../directives/template/internals.directive';
/** /**
* The **ToolbarComponent** is hosted inside of the {@link VideoconferenceComponent}. * The **ToolbarComponent** is hosted inside of the {@link VideoconferenceComponent}.
@ -80,6 +80,28 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
| TemplateRef<any> | TemplateRef<any>
| undefined; | undefined;
/**
* @internal
* Template for additional menu items in the more options menu
*/
moreOptionsAdditionalMenuItemsTemplate: TemplateRef<any> | undefined;
private _externalMoreOptionsAdditionalMenuItems?: ToolbarMoreOptionsAdditionalMenuItemsDirective;
/**
* @internal
*/
@ContentChild(ToolbarMoreOptionsAdditionalMenuItemsDirective)
set externalMoreOptionsAdditionalMenuItems(value: ToolbarMoreOptionsAdditionalMenuItemsDirective) {
this._externalMoreOptionsAdditionalMenuItems = value;
this.setupTemplates();
}
/**
* @internal
*/
get externalMoreOptionsAdditionalMenuItems(): ToolbarMoreOptionsAdditionalMenuItemsDirective | undefined {
return this._externalMoreOptionsAdditionalMenuItems;
}
/** /**
* @ignore * @ignore
*/ */
@ -494,7 +516,8 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
this.templateConfig = this.templateManagerService.setupToolbarTemplates( this.templateConfig = this.templateManagerService.setupToolbarTemplates(
this._externalAdditionalButtons, this._externalAdditionalButtons,
this._externalAdditionalPanelButtons, this._externalAdditionalPanelButtons,
this._externalLeaveButton this._externalLeaveButton,
this._externalMoreOptionsAdditionalMenuItems
); );
// Apply templates to component properties for backward compatibility // Apply templates to component properties for backward compatibility
@ -515,6 +538,9 @@ export class ToolbarComponent implements OnInit, OnDestroy, AfterViewInit {
if (this.templateConfig.toolbarLeaveButtonTemplate) { if (this.templateConfig.toolbarLeaveButtonTemplate) {
this.toolbarLeaveButtonTemplate = this.templateConfig.toolbarLeaveButtonTemplate; this.toolbarLeaveButtonTemplate = this.templateConfig.toolbarLeaveButtonTemplate;
} }
if (this.templateConfig.toolbarMoreOptionsAdditionalMenuItemsTemplate) {
this.moreOptionsAdditionalMenuItemsTemplate = this.templateConfig.toolbarMoreOptionsAdditionalMenuItemsTemplate;
}
} }
/** /**

View File

@ -96,6 +96,11 @@
<ng-template #toolbarLeaveButton> <ng-template #toolbarLeaveButton>
<ng-container *ngTemplateOutlet="openviduAngularToolbarLeaveButtonTemplate"></ng-container> <ng-container *ngTemplateOutlet="openviduAngularToolbarLeaveButtonTemplate"></ng-container>
</ng-template> </ng-template>
<!-- Inject additional menu items in toolbar more options -->
<ng-container *ovToolbarMoreOptionsAdditionalMenuItems>
<ng-container *ngTemplateOutlet="ovToolbarMoreOptionsAdditionalMenuItemsTemplate"></ng-container>
</ng-container>
</ov-toolbar> </ov-toolbar>
</ng-template> </ng-template>

View File

@ -63,7 +63,8 @@ import {
ParticipantPanelAfterLocalParticipantDirective, ParticipantPanelAfterLocalParticipantDirective,
PreJoinDirective, PreJoinDirective,
LeaveButtonDirective, LeaveButtonDirective,
SettingsPanelGeneralAdditionalElementsDirective SettingsPanelGeneralAdditionalElementsDirective,
ToolbarMoreOptionsAdditionalMenuItemsDirective
} from '../../directives/template/internals.directive'; } from '../../directives/template/internals.directive';
import { OpenViduThemeService } from '../../services/theme/theme.service'; import { OpenViduThemeService } from '../../services/theme/theme.service';
import { E2eeService } from '../../services/e2ee/e2ee.service'; import { E2eeService } from '../../services/e2ee/e2ee.service';
@ -392,6 +393,22 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
return this._externalSettingsPanelGeneralAdditionalElements; return this._externalSettingsPanelGeneralAdditionalElements;
} }
private _externalToolbarMoreOptionsAdditionalMenuItems?: ToolbarMoreOptionsAdditionalMenuItemsDirective;
/**
* @internal
*/
@ContentChild(ToolbarMoreOptionsAdditionalMenuItemsDirective)
set externalToolbarMoreOptionsAdditionalMenuItems(value: ToolbarMoreOptionsAdditionalMenuItemsDirective) {
this._externalToolbarMoreOptionsAdditionalMenuItems = value;
this.setupTemplates();
}
/**
* @internal
*/
get externalToolbarMoreOptionsAdditionalMenuItems(): ToolbarMoreOptionsAdditionalMenuItemsDirective | undefined {
return this._externalToolbarMoreOptionsAdditionalMenuItems;
}
/** /**
* @internal * @internal
*/ */
@ -498,6 +515,10 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
* @internal * @internal
*/ */
ovSettingsPanelGeneralAdditionalElementsTemplate: TemplateRef<any>; ovSettingsPanelGeneralAdditionalElementsTemplate: TemplateRef<any>;
/**
* @internal
*/
ovToolbarMoreOptionsAdditionalMenuItemsTemplate: TemplateRef<any>;
/** /**
* @internal * @internal
@ -808,7 +829,8 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
stream: this.externalStream, stream: this.externalStream,
preJoin: this.externalPreJoin, preJoin: this.externalPreJoin,
layoutAdditionalElements: this.externalLayoutAdditionalElements, layoutAdditionalElements: this.externalLayoutAdditionalElements,
settingsPanelGeneralAdditionalElements: this.externalSettingsPanelGeneralAdditionalElements settingsPanelGeneralAdditionalElements: this.externalSettingsPanelGeneralAdditionalElements,
toolbarMoreOptionsAdditionalMenuItems: this.externalToolbarMoreOptionsAdditionalMenuItems
}; };
const defaultTemplates: DefaultTemplates = { const defaultTemplates: DefaultTemplates = {
@ -886,6 +908,9 @@ export class VideoconferenceComponent implements OnDestroy, AfterViewInit {
if (this.templateConfig.settingsPanelGeneralAdditionalElementsTemplate) { if (this.templateConfig.settingsPanelGeneralAdditionalElementsTemplate) {
assignIfChanged('ovSettingsPanelGeneralAdditionalElementsTemplate', this.templateConfig.settingsPanelGeneralAdditionalElementsTemplate); assignIfChanged('ovSettingsPanelGeneralAdditionalElementsTemplate', this.templateConfig.settingsPanelGeneralAdditionalElementsTemplate);
} }
if (this.templateConfig.toolbarMoreOptionsAdditionalMenuItemsTemplate) {
assignIfChanged('ovToolbarMoreOptionsAdditionalMenuItemsTemplate', this.templateConfig.toolbarMoreOptionsAdditionalMenuItemsTemplate);
}
} }
/** /**

View File

@ -343,3 +343,38 @@ export class SettingsPanelGeneralAdditionalElementsDirective {
public container: ViewContainerRef public container: ViewContainerRef
) {} ) {}
} }
/**
* The ***ovToolbarMoreOptionsAdditionalMenuItems** directive allows you to inject custom HTML or Angular templates
* into the "more options" menu (three dots button) of the toolbar.
* This enables you to add custom menu items to extend the toolbar functionality.
*
* Usage example:
* ```html
* <ov-videoconference>
* <ng-container *ovToolbarMoreOptionsAdditionalMenuItems>
* <button mat-menu-item (click)="onCustomAction()">
* <mat-icon>star</mat-icon>
* <span>Custom Action</span>
* </button>
* <mat-divider></mat-divider>
* <button mat-menu-item (click)="onAnotherAction()">
* <mat-icon>info</mat-icon>
* <span>Another Action</span>
* </button>
* </ng-container>
* </ov-videoconference>
* ```
*
* @internal
*/
@Directive({
selector: '[ovToolbarMoreOptionsAdditionalMenuItems]',
standalone: false
})
export class ToolbarMoreOptionsAdditionalMenuItemsDirective {
constructor(
public template: TemplateRef<any>,
public container: ViewContainerRef
) {}
}

View File

@ -20,7 +20,8 @@ import {
ParticipantPanelParticipantBadgeDirective, ParticipantPanelParticipantBadgeDirective,
PreJoinDirective, PreJoinDirective,
LeaveButtonDirective, LeaveButtonDirective,
SettingsPanelGeneralAdditionalElementsDirective SettingsPanelGeneralAdditionalElementsDirective,
ToolbarMoreOptionsAdditionalMenuItemsDirective
} from './internals.directive'; } from './internals.directive';
@NgModule({ @NgModule({
@ -42,7 +43,8 @@ import {
ParticipantPanelAfterLocalParticipantDirective, ParticipantPanelAfterLocalParticipantDirective,
LayoutAdditionalElementsDirective, LayoutAdditionalElementsDirective,
ParticipantPanelParticipantBadgeDirective, ParticipantPanelParticipantBadgeDirective,
SettingsPanelGeneralAdditionalElementsDirective SettingsPanelGeneralAdditionalElementsDirective,
ToolbarMoreOptionsAdditionalMenuItemsDirective
// BackgroundEffectsPanelDirective // BackgroundEffectsPanelDirective
], ],
exports: [ exports: [
@ -63,7 +65,8 @@ import {
ParticipantPanelAfterLocalParticipantDirective, ParticipantPanelAfterLocalParticipantDirective,
LayoutAdditionalElementsDirective, LayoutAdditionalElementsDirective,
ParticipantPanelParticipantBadgeDirective, ParticipantPanelParticipantBadgeDirective,
SettingsPanelGeneralAdditionalElementsDirective SettingsPanelGeneralAdditionalElementsDirective,
ToolbarMoreOptionsAdditionalMenuItemsDirective
// BackgroundEffectsPanelDirective // BackgroundEffectsPanelDirective
] ]
}) })

View File

@ -20,7 +20,8 @@ import {
ParticipantPanelAfterLocalParticipantDirective, ParticipantPanelAfterLocalParticipantDirective,
LayoutAdditionalElementsDirective, LayoutAdditionalElementsDirective,
LeaveButtonDirective, LeaveButtonDirective,
SettingsPanelGeneralAdditionalElementsDirective SettingsPanelGeneralAdditionalElementsDirective,
ToolbarMoreOptionsAdditionalMenuItemsDirective
} from '../../directives/template/internals.directive'; } from '../../directives/template/internals.directive';
/** /**
@ -53,6 +54,9 @@ export interface TemplateConfiguration {
// Settings panel templates // Settings panel templates
settingsPanelGeneralAdditionalElementsTemplate?: TemplateRef<any>; settingsPanelGeneralAdditionalElementsTemplate?: TemplateRef<any>;
// Toolbar templates
toolbarMoreOptionsAdditionalMenuItemsTemplate?: TemplateRef<any>;
// PreJoin template // PreJoin template
preJoinTemplate?: TemplateRef<any>; preJoinTemplate?: TemplateRef<any>;
} }
@ -76,6 +80,7 @@ export interface ToolbarTemplateConfiguration {
toolbarAdditionalButtonsTemplate?: TemplateRef<any>; toolbarAdditionalButtonsTemplate?: TemplateRef<any>;
toolbarAdditionalPanelButtonsTemplate?: TemplateRef<any>; toolbarAdditionalPanelButtonsTemplate?: TemplateRef<any>;
toolbarLeaveButtonTemplate?: TemplateRef<any>; toolbarLeaveButtonTemplate?: TemplateRef<any>;
toolbarMoreOptionsAdditionalMenuItemsTemplate?: TemplateRef<any>;
} }
/** /**
@ -131,6 +136,7 @@ export interface ExternalDirectives {
preJoin?: PreJoinDirective; preJoin?: PreJoinDirective;
layoutAdditionalElements?: LayoutAdditionalElementsDirective; layoutAdditionalElements?: LayoutAdditionalElementsDirective;
settingsPanelGeneralAdditionalElements?: SettingsPanelGeneralAdditionalElementsDirective; settingsPanelGeneralAdditionalElements?: SettingsPanelGeneralAdditionalElementsDirective;
toolbarMoreOptionsAdditionalMenuItems?: ToolbarMoreOptionsAdditionalMenuItemsDirective;
} }
/** /**
@ -218,6 +224,11 @@ export class TemplateManagerService {
config.settingsPanelGeneralAdditionalElementsTemplate = externalDirectives.settingsPanelGeneralAdditionalElements.template; config.settingsPanelGeneralAdditionalElementsTemplate = externalDirectives.settingsPanelGeneralAdditionalElements.template;
} }
if (externalDirectives.toolbarMoreOptionsAdditionalMenuItems) {
this.log.v('Setting EXTERNAL TOOLBAR MORE OPTIONS ADDITIONAL MENU ITEMS');
config.toolbarMoreOptionsAdditionalMenuItemsTemplate = externalDirectives.toolbarMoreOptionsAdditionalMenuItems.template;
}
this.log.v('Template setup completed', config); this.log.v('Template setup completed', config);
return config; return config;
} }
@ -378,14 +389,16 @@ export class TemplateManagerService {
setupToolbarTemplates( setupToolbarTemplates(
externalAdditionalButtons?: ToolbarAdditionalButtonsDirective, externalAdditionalButtons?: ToolbarAdditionalButtonsDirective,
externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective, externalAdditionalPanelButtons?: ToolbarAdditionalPanelButtonsDirective,
externalLeaveButton?: LeaveButtonDirective externalLeaveButton?: LeaveButtonDirective,
externalMoreOptionsAdditionalMenuItems?: ToolbarMoreOptionsAdditionalMenuItemsDirective
): ToolbarTemplateConfiguration { ): ToolbarTemplateConfiguration {
this.log.v('Setting up toolbar templates...'); this.log.v('Setting up toolbar templates...');
return { return {
toolbarAdditionalButtonsTemplate: externalAdditionalButtons?.template, toolbarAdditionalButtonsTemplate: externalAdditionalButtons?.template,
toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template, toolbarAdditionalPanelButtonsTemplate: externalAdditionalPanelButtons?.template,
toolbarLeaveButtonTemplate: externalLeaveButton?.template toolbarLeaveButtonTemplate: externalLeaveButton?.template,
toolbarMoreOptionsAdditionalMenuItemsTemplate: externalMoreOptionsAdditionalMenuItems?.template
}; };
} }