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. // Represents a participant in the application.
export class CustomParticipantModel extends ParticipantModel { 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; private _meetRole: ParticipantRole;
constructor(props: ParticipantProperties) { constructor(props: ParticipantProperties) {
super(props); super(props);
const participant = props.participant; const participant = props.participant;
this._meetRole = extractParticipantRole(participant.metadata); this._meetOriginalRole = extractParticipantRole(participant.metadata);
this._meetRole = this._meetOriginalRole;
} }
set meetRole(role: ParticipantRole) { set meetRole(role: ParticipantRole) {
this._meetRole = role; 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 { isModerator(): boolean {
return this._meetRole === ParticipantRole.MODERATOR; 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 => { const extractParticipantRole = (metadata: any): ParticipantRole => {

View File

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