frontend: refactored video-room component and renamed by meeting component
- Implemented EndMeetingComponent to handle user disconnection scenarios. - Created SCSS styles for the EndMeetingComponent to enhance UI/UX. - Updated MeetingComponent to manage participant interactions and room functionalities. - Added HTML structure for meeting access and participant form. - Integrated routing to replace DisconnectedComponent with EndMeetingComponent. - Added unit tests for MeetingComponent to ensure functionality.
This commit is contained in:
parent
91c9690953
commit
1617e2b9d6
@ -1,22 +0,0 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
||||||
|
|
||||||
import { DisconnectedComponent } from './disconnected.component';
|
|
||||||
|
|
||||||
describe('ParticipantDisconnectedComponent', () => {
|
|
||||||
let component: DisconnectedComponent;
|
|
||||||
let fixture: ComponentFixture<DisconnectedComponent>;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
await TestBed.configureTestingModule({
|
|
||||||
imports: [DisconnectedComponent]
|
|
||||||
}).compileComponents();
|
|
||||||
|
|
||||||
fixture = TestBed.createComponent(DisconnectedComponent);
|
|
||||||
component = fixture.componentInstance;
|
|
||||||
fixture.detectChanges();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create', () => {
|
|
||||||
expect(component).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@ -6,9 +6,9 @@ export * from './console/recordings/recordings.component';
|
|||||||
export * from './console/rooms/rooms.component';
|
export * from './console/rooms/rooms.component';
|
||||||
export * from './console/rooms/room-wizard/room-wizard.component';
|
export * from './console/rooms/room-wizard/room-wizard.component';
|
||||||
export * from './console/users-permissions/users-permissions.component';
|
export * from './console/users-permissions/users-permissions.component';
|
||||||
export * from './disconnected/disconnected.component';
|
export * from './meeting/end-meeting/end-meeting.component';
|
||||||
export * from './error/error.component';
|
export * from './error/error.component';
|
||||||
export * from './login/login.component';
|
export * from './login/login.component';
|
||||||
export * from './room-recordings/room-recordings.component';
|
export * from './room-recordings/room-recordings.component';
|
||||||
export * from './video-room/video-room.component';
|
export * from './meeting/meeting.component';
|
||||||
export * from './view-recording/view-recording.component';
|
export * from './view-recording/view-recording.component';
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import '../../../../../../src/assets/styles/design-tokens';
|
@import '../../../../../../../src/assets/styles/design-tokens';
|
||||||
|
|
||||||
.disconnected-container {
|
.disconnected-container {
|
||||||
@include ov-theme-transition;
|
@include ov-theme-transition;
|
||||||
@ -8,13 +8,13 @@ import { AppDataService, AuthService, NavigationService, WebComponentManagerServ
|
|||||||
import { LeftEventReason } from '@lib/typings/ce';
|
import { LeftEventReason } from '@lib/typings/ce';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'ov-disconnected',
|
selector: 'ov-end-meeting',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, MatCardModule, MatButtonModule, MatIconModule],
|
imports: [CommonModule, MatCardModule, MatButtonModule, MatIconModule],
|
||||||
templateUrl: './disconnected.component.html',
|
templateUrl: './end-meeting.component.html',
|
||||||
styleUrl: './disconnected.component.scss'
|
styleUrl: './end-meeting.component.scss'
|
||||||
})
|
})
|
||||||
export class DisconnectedComponent implements OnInit {
|
export class EndMeetingComponent implements OnInit {
|
||||||
disconnectedTitle = 'You Left the Meeting';
|
disconnectedTitle = 'You Left the Meeting';
|
||||||
disconnectReason = 'You have successfully left the meeting';
|
disconnectReason = 'You have successfully left the meeting';
|
||||||
|
|
||||||
@ -1,111 +1,4 @@
|
|||||||
@if (!showRoom) {
|
@if (showMeeting) {
|
||||||
<div class="ov-page-container">
|
|
||||||
<div class="room-access-container fade-in">
|
|
||||||
<!-- Header Section -->
|
|
||||||
<div class="room-header">
|
|
||||||
<mat-icon class="ov-room-icon room-icon">video_chat</mat-icon>
|
|
||||||
<div class="room-info">
|
|
||||||
<h1 class="room-title">{{ roomId }}</h1>
|
|
||||||
<p class="room-subtitle">Choose how you want to proceed</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Action Cards Grid -->
|
|
||||||
<div class="action-cards-grid">
|
|
||||||
<!-- Join Room Card -->
|
|
||||||
<mat-card class="action-card primary-card fade-in">
|
|
||||||
<mat-card-header class="card-header">
|
|
||||||
<mat-icon class="ov-room-icon card-icon">meeting_room</mat-icon>
|
|
||||||
<div class="card-title-group">
|
|
||||||
<mat-card-title>Join Meeting</mat-card-title>
|
|
||||||
<mat-card-subtitle>Enter the room and start connecting</mat-card-subtitle>
|
|
||||||
</div>
|
|
||||||
</mat-card-header>
|
|
||||||
|
|
||||||
<mat-card-content class="card-content">
|
|
||||||
<form [formGroup]="participantForm" (ngSubmit)="submitAccessRoom()" class="join-form">
|
|
||||||
<mat-form-field appearance="outline" class="name-field">
|
|
||||||
<mat-label>Your display name</mat-label>
|
|
||||||
<input
|
|
||||||
id="participant-name-input"
|
|
||||||
matInput
|
|
||||||
placeholder="Enter your name"
|
|
||||||
formControlName="name"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<mat-icon matSuffix class="ov-action-icon">person</mat-icon>
|
|
||||||
@if (participantForm.get('name')?.hasError('minlength')) {
|
|
||||||
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
|
|
||||||
}
|
|
||||||
@if (participantForm.get('name')?.hasError('required')) {
|
|
||||||
<mat-error> The name is <strong>required</strong> </mat-error>
|
|
||||||
}
|
|
||||||
@if (participantForm.get('name')?.hasError('participantExists')) {
|
|
||||||
<mat-error>
|
|
||||||
The name is already taken. <strong> Please choose another name </strong>
|
|
||||||
</mat-error>
|
|
||||||
}
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<button
|
|
||||||
mat-raised-button
|
|
||||||
color="primary"
|
|
||||||
id="participant-name-submit"
|
|
||||||
type="submit"
|
|
||||||
class="join-button"
|
|
||||||
[disabled]="participantForm.invalid"
|
|
||||||
>
|
|
||||||
<span>Join Meeting</span>
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
|
|
||||||
<!-- View Recordings Card -->
|
|
||||||
@if (showRecordingCard) {
|
|
||||||
<mat-card class="action-card secondary-card fade-in-delayed">
|
|
||||||
<mat-card-header class="card-header">
|
|
||||||
<mat-icon class="ov-recording-icon card-icon">video_library</mat-icon>
|
|
||||||
<div class="card-title-group">
|
|
||||||
<mat-card-title>View Recordings</mat-card-title>
|
|
||||||
<mat-card-subtitle>Browse and manage past recordings</mat-card-subtitle>
|
|
||||||
</div>
|
|
||||||
</mat-card-header>
|
|
||||||
|
|
||||||
<mat-card-content class="card-content">
|
|
||||||
<div class="recordings-info">
|
|
||||||
<p class="recordings-description">
|
|
||||||
Access previously recorded meetings from this room. You can watch, download, or
|
|
||||||
manage existing recordings.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button
|
|
||||||
id="view-recordings-btn"
|
|
||||||
mat-stroked-button
|
|
||||||
color="accent"
|
|
||||||
(click)="goToRecordings()"
|
|
||||||
class="recordings-button"
|
|
||||||
>
|
|
||||||
<span>Browse Recordings</span>
|
|
||||||
</button>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quick Actions -->
|
|
||||||
@if (showBackButton) {
|
|
||||||
<div class="quick-actions fade-in-delayed-more">
|
|
||||||
<button mat-button class="quick-action-button" (click)="goBack()">
|
|
||||||
<mat-icon>arrow_back</mat-icon>
|
|
||||||
<span>{{ backButtonText }}</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<ov-videoconference
|
<ov-videoconference
|
||||||
[token]="participantToken"
|
[token]="participantToken"
|
||||||
[participantName]="participantName"
|
[participantName]="participantName"
|
||||||
@ -208,4 +101,112 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</ov-videoconference>
|
</ov-videoconference>
|
||||||
|
} @else {
|
||||||
|
<!-- Move this logic to prejoin meeting page -->
|
||||||
|
<div class="ov-page-container">
|
||||||
|
<div class="room-access-container fade-in">
|
||||||
|
<!-- Header Section -->
|
||||||
|
<div class="room-header">
|
||||||
|
<mat-icon class="ov-room-icon room-icon">video_chat</mat-icon>
|
||||||
|
<div class="room-info">
|
||||||
|
<h1 class="room-title">{{ roomId }}</h1>
|
||||||
|
<p class="room-subtitle">Choose how you want to proceed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Cards Grid -->
|
||||||
|
<div class="action-cards-grid">
|
||||||
|
<!-- Join Room Card -->
|
||||||
|
<mat-card class="action-card primary-card fade-in">
|
||||||
|
<mat-card-header class="card-header">
|
||||||
|
<mat-icon class="ov-room-icon card-icon">meeting_room</mat-icon>
|
||||||
|
<div class="card-title-group">
|
||||||
|
<mat-card-title>Join Meeting</mat-card-title>
|
||||||
|
<mat-card-subtitle>Enter the room and start connecting</mat-card-subtitle>
|
||||||
|
</div>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content class="card-content">
|
||||||
|
<form [formGroup]="participantForm" (ngSubmit)="submitAccessMeeting()" class="join-form">
|
||||||
|
<mat-form-field appearance="outline" class="name-field">
|
||||||
|
<mat-label>Your display name</mat-label>
|
||||||
|
<input
|
||||||
|
id="participant-name-input"
|
||||||
|
matInput
|
||||||
|
placeholder="Enter your name"
|
||||||
|
formControlName="name"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<mat-icon matSuffix class="ov-action-icon">person</mat-icon>
|
||||||
|
@if (participantForm.get('name')?.hasError('minlength')) {
|
||||||
|
<mat-error> The name must be at least <strong>4 characters</strong> </mat-error>
|
||||||
|
}
|
||||||
|
@if (participantForm.get('name')?.hasError('required')) {
|
||||||
|
<mat-error> The name is <strong>required</strong> </mat-error>
|
||||||
|
}
|
||||||
|
@if (participantForm.get('name')?.hasError('participantExists')) {
|
||||||
|
<mat-error>
|
||||||
|
The name is already taken. <strong> Please choose another name </strong>
|
||||||
|
</mat-error>
|
||||||
|
}
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<button
|
||||||
|
mat-raised-button
|
||||||
|
color="primary"
|
||||||
|
id="participant-name-submit"
|
||||||
|
type="submit"
|
||||||
|
class="join-button"
|
||||||
|
[disabled]="participantForm.invalid"
|
||||||
|
>
|
||||||
|
<span>Join Meeting</span>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<!-- View Recordings Card -->
|
||||||
|
@if (showRecordingCard) {
|
||||||
|
<mat-card class="action-card secondary-card fade-in-delayed">
|
||||||
|
<mat-card-header class="card-header">
|
||||||
|
<mat-icon class="ov-recording-icon card-icon">video_library</mat-icon>
|
||||||
|
<div class="card-title-group">
|
||||||
|
<mat-card-title>View Recordings</mat-card-title>
|
||||||
|
<mat-card-subtitle>Browse and manage past recordings</mat-card-subtitle>
|
||||||
|
</div>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content class="card-content">
|
||||||
|
<div class="recordings-info">
|
||||||
|
<p class="recordings-description">
|
||||||
|
Access previously recorded meetings from this room. You can watch, download, or
|
||||||
|
manage existing recordings.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id="view-recordings-btn"
|
||||||
|
mat-stroked-button
|
||||||
|
color="accent"
|
||||||
|
(click)="goToRecordings()"
|
||||||
|
class="recordings-button"
|
||||||
|
>
|
||||||
|
<span>Browse Recordings</span>
|
||||||
|
</button>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Quick Actions -->
|
||||||
|
@if (showBackButton) {
|
||||||
|
<div class="quick-actions fade-in-delayed-more">
|
||||||
|
<button mat-button class="quick-action-button" (click)="goBack()">
|
||||||
|
<mat-icon>arrow_back</mat-icon>
|
||||||
|
<span>{{ backButtonText }}</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@ -1,18 +1,18 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
import { VideoRoomComponent } from './video-room.component';
|
import { MeetingComponent } from './meeting.component';
|
||||||
|
|
||||||
describe('CallComponent', () => {
|
describe('CallComponent', () => {
|
||||||
let component: VideoRoomComponent;
|
let component: MeetingComponent;
|
||||||
let fixture: ComponentFixture<VideoRoomComponent>;
|
let fixture: ComponentFixture<MeetingComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [VideoRoomComponent]
|
imports: [MeetingComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(VideoRoomComponent);
|
fixture = TestBed.createComponent(MeetingComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
@ -51,9 +51,9 @@ import {
|
|||||||
} from 'openvidu-components-angular';
|
} from 'openvidu-components-angular';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-video-room',
|
selector: 'app-meeting',
|
||||||
templateUrl: './video-room.component.html',
|
templateUrl: './meeting.component.html',
|
||||||
styleUrls: ['./video-room.component.scss'],
|
styleUrls: ['./meeting.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
OpenViduComponentsUiModule,
|
OpenViduComponentsUiModule,
|
||||||
@ -72,7 +72,7 @@ import {
|
|||||||
MatRippleModule
|
MatRippleModule
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class VideoRoomComponent implements OnInit {
|
export class MeetingComponent implements OnInit {
|
||||||
participantForm = new FormGroup({
|
participantForm = new FormGroup({
|
||||||
name: new FormControl('', [Validators.required, Validators.minLength(4)])
|
name: new FormControl('', [Validators.required, Validators.minLength(4)])
|
||||||
});
|
});
|
||||||
@ -88,7 +88,7 @@ export class VideoRoomComponent implements OnInit {
|
|||||||
participantToken = '';
|
participantToken = '';
|
||||||
participantRole: ParticipantRole = ParticipantRole.PUBLISHER;
|
participantRole: ParticipantRole = ParticipantRole.PUBLISHER;
|
||||||
|
|
||||||
showRoom = false;
|
showMeeting = false;
|
||||||
features: Signal<ApplicationFeatures>;
|
features: Signal<ApplicationFeatures>;
|
||||||
meetingEndedByMe = false;
|
meetingEndedByMe = false;
|
||||||
|
|
||||||
@ -216,11 +216,11 @@ export class VideoRoomComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async submitAccessRoom() {
|
async submitAccessMeeting() {
|
||||||
const { valid, value } = this.participantForm;
|
const { valid, value } = this.participantForm;
|
||||||
if (!valid || !value.name?.trim()) {
|
if (!valid || !value.name?.trim()) {
|
||||||
// If the form is invalid, do not proceed
|
// If the form is invalid, do not proceed
|
||||||
console.warn('Participant form is invalid. Cannot access room.');
|
console.warn('Participant form is invalid. Cannot access meeting.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,14 +231,14 @@ export class VideoRoomComponent implements OnInit {
|
|||||||
await this.generateParticipantToken();
|
await this.generateParticipantToken();
|
||||||
await this.addParticipantNameToUrl();
|
await this.addParticipantNameToUrl();
|
||||||
await this.roomService.loadPreferences(this.roomId);
|
await this.roomService.loadPreferences(this.roomId);
|
||||||
this.showRoom = true;
|
this.showMeeting = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error accessing room:', error);
|
console.error('Error accessing meeting:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a participant token for joining a video room.
|
* Generates a participant token for joining a meeting.
|
||||||
*
|
*
|
||||||
* @throws When participant already exists in the room (status 409)
|
* @throws When participant already exists in the room (status 409)
|
||||||
* @returns Promise that resolves when token is generated
|
* @returns Promise that resolves when token is generated
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ConsoleComponent,
|
ConsoleComponent,
|
||||||
DevelopersSettingsComponent,
|
DevelopersSettingsComponent,
|
||||||
DisconnectedComponent,
|
EndMeetingComponent,
|
||||||
ErrorComponent,
|
ErrorComponent,
|
||||||
LoginComponent,
|
LoginComponent,
|
||||||
OverviewComponent,
|
OverviewComponent,
|
||||||
@ -22,7 +22,7 @@ import {
|
|||||||
RoomsComponent,
|
RoomsComponent,
|
||||||
RoomWizardComponent,
|
RoomWizardComponent,
|
||||||
UsersPermissionsComponent,
|
UsersPermissionsComponent,
|
||||||
VideoRoomComponent,
|
MeetingComponent,
|
||||||
ViewRecordingComponent
|
ViewRecordingComponent
|
||||||
} from '@lib/pages';
|
} from '@lib/pages';
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ export const baseRoutes: Routes = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'room/:room-id',
|
path: 'room/:room-id',
|
||||||
component: VideoRoomComponent,
|
component: MeetingComponent,
|
||||||
canActivate: [
|
canActivate: [
|
||||||
runGuardsSerially(extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard, removeRoomSecretGuard)
|
runGuardsSerially(extractRoomQueryParamsGuard, checkParticipantRoleAndAuthGuard, removeRoomSecretGuard)
|
||||||
]
|
]
|
||||||
@ -56,7 +56,7 @@ export const baseRoutes: Routes = [
|
|||||||
component: ViewRecordingComponent,
|
component: ViewRecordingComponent,
|
||||||
canActivate: [checkRecordingAuthGuard]
|
canActivate: [checkRecordingAuthGuard]
|
||||||
},
|
},
|
||||||
{ path: 'disconnected', component: DisconnectedComponent },
|
{ path: 'disconnected', component: EndMeetingComponent },
|
||||||
{ path: 'error', component: ErrorComponent },
|
{ path: 'error', component: ErrorComponent },
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user