frontend: enhance participant role management with original role tracking

This commit is contained in:
Carlos Santos 2025-08-19 16:08:15 +02:00
parent a636ad485f
commit f1fc2e0ba4
3 changed files with 105 additions and 39 deletions

View File

@ -3,22 +3,37 @@ import { ParticipantModel, ParticipantProperties } from 'openvidu-components-ang
// Represents a participant in the application.
export class CustomParticipantModel extends ParticipantModel {
// Indicates the role of the participant.
// Indicates the original role of the participant.
private _meetOriginalRole: ParticipantRole;
// Indicates the current role of the participant.
private _meetRole: ParticipantRole;
constructor(props: ParticipantProperties) {
super(props);
const participant = props.participant;
this._meetRole = extractParticipantRole(participant.metadata);
this._meetOriginalRole = extractParticipantRole(participant.metadata);
this._meetRole = this._meetOriginalRole;
}
set meetRole(role: ParticipantRole) {
this._meetRole = role;
}
/**
* Checks if the current role of the participant is moderator.
* @returns True if the current role is moderator, false otherwise.
*/
isModerator(): boolean {
return this._meetRole === ParticipantRole.MODERATOR;
}
/**
* Checks if the original role of the participant is moderator.
* @returns True if the original role is moderator, false otherwise.
*/
isOriginalModerator(): boolean {
return this._meetOriginalRole === ParticipantRole.MODERATOR;
}
}
const extractParticipantRole = (metadata: any): ParticipantRole => {

View File

@ -1,7 +1,6 @@
@if (showMeeting) {
<ov-videoconference
[token]="participantToken"
[prejoin]="true"
[prejoinDisplayParticipantName]="false"
[videoEnabled]="features().videoEnabled"
@ -39,9 +38,9 @@
(onRecordingStopRequested)="onRecordingStopRequested($event)"
(onViewRecordingsClicked)="onViewRecordingsClicked()"
>
@if (features().canModerateRoom) {
<div *ovToolbarAdditionalButtons>
<!-- Copy Link Button -->
<ng-container *ovToolbarAdditionalButtons>
<!-- Copy Link Button -->
@if (features().canModerateRoom) {
<button
id="copy-speaker-link"
mat-icon-button
@ -74,32 +73,34 @@
<span>End meeting for all</span>
</button>
</mat-menu>
</div>
}
</ng-container>
<div *ovParticipantPanelAfterLocalParticipant>
<ng-container *ovParticipantPanelAfterLocalParticipant>
@if (features().canModerateRoom) {
<div class="share-meeting-link-container">
<ov-share-meeting-link
[meetingUrl]="hostname + '/room/' + roomId"
(copyClicked)="copySpeakerLink()"
></ov-share-meeting-link>
</div>
</div>
}
</ng-container>
<ng-container *ovLayoutAdditionalElements>
@if (remoteParticipants.length === 0) {
<div class="main-share-meeting-link-container fade-in-delayed-more">
<ov-share-meeting-link
[title]="'Start collaborating'"
[subtitle]="'Share this link to bring others into the meeting'"
[titleSize]="'xl'"
[titleWeight]="'bold'"
[meetingUrl]="hostname + '/room/' + roomId"
(copyClicked)="copySpeakerLink()"
></ov-share-meeting-link>
</div>
}
</ng-container>
}
<ng-container *ovLayoutAdditionalElements>
@if (features().canModerateRoom && remoteParticipants.length === 0) {
<div class="main-share-meeting-link-container fade-in-delayed-more">
<ov-share-meeting-link
[title]="'Start collaborating'"
[subtitle]="'Share this link to bring others into the meeting'"
[titleSize]="'xl'"
[titleWeight]="'bold'"
[meetingUrl]="hostname + '/room/' + roomId"
(copyClicked)="copySpeakerLink()"
></ov-share-meeting-link>
</div>
}
</ng-container>
<ng-container *ovParticipantPanelItem="let participant">
<!-- If Meet participant is moderator -->
@ -130,25 +131,52 @@
}
<div *ovParticipantPanelItemElements>
<!-- Button to make moderator if not -->
@if (!participant.isModerator()) {
@if (localParticipant!.isOriginalModerator()) {
@if (participant.isModerator() && !participant.isOriginalModerator()) {
<button
mat-icon-button
(click)="unmakeModerator(participant)"
matTooltip="Remove participant moderator"
class="remove-moderator-btn"
>
<mat-icon class="material-symbols-outlined">remove_moderator</mat-icon>
</button>
} @else {
@if (!participant.isModerator()) {
<button
mat-icon-button
(click)="makeModerator(participant)"
matTooltip="Make participant moderator"
class="make-moderator-btn"
>
<mat-icon class="material-symbols-outlined">add_moderator</mat-icon>
</button>
}
}
} @else {
@if (!participant.isModerator()) {
<button
mat-icon-button
(click)="makeModerator(participant)"
matTooltip="Make participant moderator"
class="make-moderator-btn"
>
<mat-icon class="material-symbols-outlined">add_moderator</mat-icon>
</button>
}
}
<!-- Button to kick participant -->
@if (!participant.isOriginalModerator()) {
<button
mat-icon-button
(click)="makeModerator(participant)"
matTooltip="Make participant moderator"
class="make-moderator-btn"
(click)="kickParticipant(participant)"
matTooltip="Kick participant"
class="force-disconnect-btn"
>
<mat-icon class="material-symbols-outlined">add_moderator</mat-icon>
<mat-icon>call_end</mat-icon>
</button>
}
<!-- Button to kick participant -->
<button
mat-icon-button
(click)="kickParticipant(participant)"
matTooltip="Kick participant"
class="force-disconnect-btn"
>
<mat-icon>call_end</mat-icon>
</button>
</div>
</ov-participant-panel-item>
}
@ -172,7 +200,7 @@
</ng-container>
</ov-videoconference>
} @else {
<!-- Move this logic to prejoin meeting page -->
<!-- Move this logic to lobby meeting page -->
<div class="ov-page-container">
<div class="room-access-container fade-in">
<!-- Header Section -->

View File

@ -492,6 +492,10 @@ export class MeetingComponent implements OnInit {
}
}
/**
* Makes a participant as moderator.
* @param participant The participant to make as moderator.
*/
async makeModerator(participant: CustomParticipantModel) {
if (!this.participantService.isModeratorParticipant()) return;
@ -507,6 +511,25 @@ export class MeetingComponent implements OnInit {
}
}
/**
* Unmakes a participant as moderator.
* @param participant The participant to unmake as moderator.
*/
async unmakeModerator(participant: CustomParticipantModel) {
if (!this.participantService.isModeratorParticipant()) return;
try {
await this.meetingService.changeParticipantRole(
this.roomId,
participant.identity,
ParticipantRole.SPEAKER
);
} catch (error) {
console.error('Error unmaking participant moderator:', error);
this.notificationService.showSnackbar('Failed to unmake participant moderator');
}
}
async copyModeratorLink() {
this.clipboard.copy(this.room!.moderatorRoomUrl);
this.notificationService.showSnackbar('Moderator link copied to clipboard');