ov-components: add toolbar panel buttons component with event handling and styling

master
Carlos Santos 2025-09-19 11:56:10 +02:00
parent 41152de276
commit d223acefcf
7 changed files with 190 additions and 123 deletions

View File

@ -0,0 +1,55 @@
<!-- Default activities button -->
<button
mat-icon-button
id="activities-panel-btn"
class="panel-button"
*ngIf="!isMinimal && showActivitiesPanelButton"
matTooltip="{{ 'TOOLBAR.ACTIVITIES' | translate }}"
(click)="onToggleActivities()"
[disabled]="isConnectionLost"
[class.active-btn]="isActivitiesOpened"
>
<mat-icon>category</mat-icon>
</button>
<!-- Default participants button -->
<button
mat-icon-button
class="panel-button"
id="participants-panel-btn"
*ngIf="!isMinimal && showParticipantsPanelButton"
matTooltip="{{ 'TOOLBAR.PARTICIPANTS' | translate }}"
(click)="onToggleParticipants()"
[disabled]="isConnectionLost"
[class.active-btn]="isParticipantsOpened"
>
<mat-icon>people</mat-icon>
</button>
<!-- Default chat button -->
<button
mat-icon-button
class="panel-button"
id="chat-panel-btn"
*ngIf="!isMinimal && showChatPanelButton"
matTooltip="{{ 'TOOLBAR.CHAT' | translate }}"
(click)="onToggleChat()"
[disabled]="isConnectionLost"
[class.active-btn]="isChatOpened"
>
<mat-icon
matBadge="{{ unreadMessages }}"
[matBadgeHidden]="unreadMessages === 0"
matBadgePosition="above before"
matBadgeSize="small"
matBadgeColor="accent"
aria-hidden="false"
>
chat
</mat-icon>
</button>
<!-- External additional panel buttons -->
<ng-container *ngIf="toolbarAdditionalPanelButtonsTemplate">
<ng-container *ngTemplateOutlet="toolbarAdditionalPanelButtonsTemplate"></ng-container>
</ng-container>

View File

@ -0,0 +1,11 @@
:host {
.panel-button {
border-radius: var(--ov-toolbar-buttons-radius);
color: var(--ov-secondary-action-color);
}
.active-btn,
::ng-deep .active-btn {
background-color: var(--ov-accent-action-color) !important;
}
}

View File

@ -0,0 +1,43 @@
import { Component, EventEmitter, Input, Output, TemplateRef } from '@angular/core';
@Component({
selector: 'ov-toolbar-panel-buttons',
templateUrl: './toolbar-panel-buttons.component.html',
styleUrl: './toolbar-panel-buttons.component.scss',
standalone: false
})
export class ToolbarPanelButtonsComponent {
// Inputs from toolbar
@Input() isMinimal: boolean = false;
@Input() isConnectionLost: boolean = false;
@Input() isActivitiesOpened: boolean = false;
@Input() isParticipantsOpened: boolean = false;
@Input() isChatOpened: boolean = false;
@Input() unreadMessages: number = 0;
@Input() showActivitiesPanelButton: boolean = true;
@Input() showParticipantsPanelButton: boolean = true;
@Input() showChatPanelButton: boolean = true;
@Input() recordingStatus: any;
@Input() broadcastingStatus: any;
@Input() _recordingStatus: any;
@Input() _broadcastingStatus: any;
@Input() toolbarAdditionalPanelButtonsTemplate: TemplateRef<any> | undefined;
// Outputs back to toolbar
@Output() toggleActivitiesPanel: EventEmitter<string | undefined> = new EventEmitter();
@Output() toggleParticipantsPanel: EventEmitter<void> = new EventEmitter();
@Output() toggleChatPanel: EventEmitter<void> = new EventEmitter();
// Local methods that emit events
onToggleActivities(expand?: string) {
this.toggleActivitiesPanel.emit(expand);
}
onToggleParticipants() {
this.toggleParticipantsPanel.emit();
}
onToggleChat() {
this.toggleChatPanel.emit();
}
}

View File

@ -85,63 +85,26 @@
></ov-toolbar-media-buttons> ></ov-toolbar-media-buttons>
</div> </div>
<!-- Panel buttons (chat, participants, activities) --> <!-- Panel buttons -->
<div class="menu-buttons-container" id="menu-buttons-container"> <div class="menu-buttons-container" id="menu-buttons-container">
<!-- Default activities button --> <ov-toolbar-panel-buttons
<button [isMinimal]="isMinimal"
mat-icon-button [isConnectionLost]="isConnectionLost"
id="activities-panel-btn" [isActivitiesOpened]="isActivitiesOpened"
*ngIf="!isMinimal && showActivitiesPanelButton" [isParticipantsOpened]="isParticipantsOpened"
matTooltip="{{ 'TOOLBAR.ACTIVITIES' | translate }}" [isChatOpened]="isChatOpened"
(click)="toggleActivitiesPanel()" [unreadMessages]="unreadMessages"
[disabled]="isConnectionLost" [showActivitiesPanelButton]="showActivitiesPanelButton"
[ngClass]="{ [showParticipantsPanelButton]="showParticipantsPanelButton"
'blinking-recording-button': !isActivitiesOpened && recordingStatus === _recordingStatus.STARTED, [showChatPanelButton]="showChatPanelButton"
'blinking-broadcasting-button': !isActivitiesOpened && broadcastingStatus === _broadcastingStatus.STARTED, [recordingStatus]="recordingStatus"
'active-btn': isActivitiesOpened [broadcastingStatus]="broadcastingStatus"
}" [_recordingStatus]="_recordingStatus"
> [_broadcastingStatus]="_broadcastingStatus"
<mat-icon>category</mat-icon> [toolbarAdditionalPanelButtonsTemplate]="toolbarAdditionalPanelButtonsTemplate"
</button> (toggleActivitiesPanel)="toggleActivitiesPanel($event)"
(toggleParticipantsPanel)="toggleParticipantsPanel()"
<!-- Default participants button --> (toggleChatPanel)="toggleChatPanel()"
<button ></ov-toolbar-panel-buttons>
mat-icon-button
id="participants-panel-btn"
*ngIf="!isMinimal && showParticipantsPanelButton"
matTooltip="{{ 'TOOLBAR.PARTICIPANTS' | translate }}"
(click)="toggleParticipantsPanel()"
[disabled]="isConnectionLost"
[class.active-btn]="isParticipantsOpened"
>
<mat-icon>people</mat-icon>
</button>
<!-- Default chat button -->
<button
mat-icon-button
id="chat-panel-btn"
*ngIf="!isMinimal && showChatPanelButton"
matTooltip="{{ 'TOOLBAR.CHAT' | translate }}"
(click)="toggleChatPanel()"
[disabled]="isConnectionLost"
[class.active-btn]="isChatOpened"
>
<mat-icon
matBadge="{{ unreadMessages }}"
[matBadgeHidden]="unreadMessages === 0"
matBadgePosition="above before"
matBadgeSize="small"
matBadgeColor="accent"
aria-hidden="false"
>
chat
</mat-icon>
</button>
<!-- External additional panel buttons -->
<ng-container *ngIf="toolbarAdditionalPanelButtonsTemplate">
<ng-container *ngTemplateOutlet="toolbarAdditionalPanelButtonsTemplate"></ng-container>
</ng-container>
</div> </div>
</mat-toolbar> </mat-toolbar>

View File

@ -54,11 +54,6 @@
max-height: 100% !important; max-height: 100% !important;
} }
#menu-buttons-container button {
border-radius: var(--ov-toolbar-buttons-radius);
color: var(--ov-secondary-action-color);
}
#branding-logo { #branding-logo {
background-color: var(--ov-primary-action-color); background-color: var(--ov-primary-action-color);
border-radius: var(--ov-surface-radius); border-radius: var(--ov-surface-radius);
@ -122,8 +117,6 @@
animation: blinker 1.5s linear infinite; animation: blinker 1.5s linear infinite;
} }
::ng-deep .mat-badge-content { ::ng-deep .mat-badge-content {
background-color: var(--ov-warn-color); background-color: var(--ov-warn-color);
} }

View File

@ -1,4 +1,3 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
@ -12,6 +11,7 @@ import { SessionComponent } from './components/session/session.component';
import { StreamComponent } from './components/stream/stream.component'; import { StreamComponent } from './components/stream/stream.component';
import { ToolbarComponent } from './components/toolbar/toolbar.component'; import { ToolbarComponent } from './components/toolbar/toolbar.component';
import { ToolbarMediaButtonsComponent } from './components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component'; import { ToolbarMediaButtonsComponent } from './components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component';
import { ToolbarPanelButtonsComponent } from './components/toolbar/toolbar-panel-buttons/toolbar-panel-buttons.component';
import { MediaElementComponent } from './components/media-element/media-element.component'; import { MediaElementComponent } from './components/media-element/media-element.component';
import { LinkifyPipe } from './pipes/linkify.pipe'; import { LinkifyPipe } from './pipes/linkify.pipe';
@ -48,68 +48,69 @@ import { OpenViduComponentsDirectiveModule } from './directives/template/openvid
import { AppMaterialModule } from './openvidu-components-angular.material.module'; import { AppMaterialModule } from './openvidu-components-angular.material.module';
const publicComponents = [ const publicComponents = [
AdminDashboardComponent, AdminDashboardComponent,
AdminLoginComponent, AdminLoginComponent,
VideoconferenceComponent, VideoconferenceComponent,
ToolbarComponent, ToolbarComponent,
PanelComponent, PanelComponent,
ActivitiesPanelComponent, ActivitiesPanelComponent,
RecordingActivityComponent, RecordingActivityComponent,
BroadcastingActivityComponent, BroadcastingActivityComponent,
ParticipantsPanelComponent, ParticipantsPanelComponent,
ParticipantPanelItemComponent, ParticipantPanelItemComponent,
ChatPanelComponent, ChatPanelComponent,
StreamComponent, StreamComponent,
LayoutComponent LayoutComponent
]; ];
const privateComponents = [ const privateComponents = [
PreJoinComponent, PreJoinComponent,
SessionComponent, SessionComponent,
BackgroundEffectsPanelComponent, BackgroundEffectsPanelComponent,
SettingsPanelComponent, SettingsPanelComponent,
AudioWaveComponent, AudioWaveComponent,
DialogTemplateComponent, DialogTemplateComponent,
ProFeatureDialogTemplateComponent, ProFeatureDialogTemplateComponent,
RecordingDialogComponent, RecordingDialogComponent,
DeleteDialogComponent, DeleteDialogComponent,
AvatarProfileComponent, AvatarProfileComponent,
MediaElementComponent, MediaElementComponent,
VideoDevicesComponent, VideoDevicesComponent,
AudioDevicesComponent, AudioDevicesComponent,
ParticipantNameInputComponent, ParticipantNameInputComponent,
LangSelectorComponent, LangSelectorComponent,
ToolbarMediaButtonsComponent ToolbarMediaButtonsComponent,
ToolbarPanelButtonsComponent
]; ];
@NgModule({ @NgModule({
declarations: [ declarations: [
...publicComponents, ...publicComponents,
...privateComponents, ...privateComponents,
LinkifyPipe, LinkifyPipe,
RemoteParticipantTracksPipe, RemoteParticipantTracksPipe,
DurationFromSecondsPipe, DurationFromSecondsPipe,
SearchByStringPropertyPipe, SearchByStringPropertyPipe,
ThumbnailFromUrlPipe, ThumbnailFromUrlPipe,
TrackPublishedTypesPipe, TrackPublishedTypesPipe,
TranslatePipe TranslatePipe
], ],
imports: [ imports: [
CommonModule, CommonModule,
FormsModule, FormsModule,
ReactiveFormsModule, ReactiveFormsModule,
AppMaterialModule, AppMaterialModule,
OpenViduComponentsDirectiveModule, OpenViduComponentsDirectiveModule,
ApiDirectiveModule, ApiDirectiveModule,
DragDropModule DragDropModule
], ],
exports: [ exports: [
...publicComponents, ...publicComponents,
RemoteParticipantTracksPipe, RemoteParticipantTracksPipe,
DurationFromSecondsPipe, DurationFromSecondsPipe,
TrackPublishedTypesPipe, TrackPublishedTypesPipe,
TranslatePipe, TranslatePipe,
OpenViduComponentsDirectiveModule, OpenViduComponentsDirectiveModule,
ApiDirectiveModule ApiDirectiveModule
] ]
}) })
export class OpenViduComponentsUiModule {} export class OpenViduComponentsUiModule {}

View File

@ -16,6 +16,7 @@ export * from './lib/components/panel/participants-panel/participants-panel/part
export * from './lib/components/stream/stream.component'; export * from './lib/components/stream/stream.component';
export * from './lib/components/toolbar/toolbar.component'; export * from './lib/components/toolbar/toolbar.component';
export * from './lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component'; export * from './lib/components/toolbar/toolbar-media-buttons/toolbar-media-buttons.component';
export * from './lib/components/toolbar/toolbar-panel-buttons/toolbar-panel-buttons.component';
export * from './lib/components/videoconference/videoconference.component'; export * from './lib/components/videoconference/videoconference.component';
export * from './lib/config/openvidu-components-angular.config'; export * from './lib/config/openvidu-components-angular.config';
// Directives // Directives