frontend: enhance loading state management in recordings components

This commit is contained in:
juancarmore 2025-08-15 12:34:00 +02:00
parent f8729e4240
commit 5f289d12b8
5 changed files with 83 additions and 50 deletions

View File

@ -101,7 +101,7 @@ export class RecordingListsComponent implements OnInit, OnChanges {
@Output() recordingAction = new EventEmitter<RecordingTableAction>();
@Output() filterChange = new EventEmitter<{ nameFilter: string; statusFilter: string }>();
@Output() loadMore = new EventEmitter<{ nameFilter: string; statusFilter: string }>();
@Output() refresh = new EventEmitter<void>();
@Output() refresh = new EventEmitter<{ nameFilter: string; statusFilter: string }>();
// Filter controls
nameFilterControl = new FormControl('');
@ -284,6 +284,12 @@ export class RecordingListsComponent implements OnInit, OnChanges {
this.loadMore.emit({ nameFilter, statusFilter });
}
refreshRecordings() {
const nameFilter = this.nameFilterControl.value || '';
const statusFilter = this.statusFilterControl.value || '';
this.refresh.emit({ nameFilter, statusFilter });
}
// ===== FILTER METHODS =====
hasActiveFilters(): boolean {

View File

@ -1,24 +1,25 @@
@if (isLoading) {
@if (showLoadingSpinner) {
<!-- Enhanced Loading State with delay -->
<div class="ov-page-loading">
<div class="loading-content">
<div class="loading-header">
<div class="loading-title">
<mat-icon class="ov-recording-icon loading-icon">video_library</mat-icon>
<h1>Loading Recordings</h1>
</div>
<p class="loading-subtitle">Please wait while we fetch your recordings...</p>
<!-- Loading State -->
@if (isInitializing && showInitialLoader) {
<div class="ov-page-loading">
<div class="loading-content">
<div class="loading-header">
<div class="loading-title">
<mat-icon class="ov-recording-icon loading-icon">video_library</mat-icon>
<h1>Loading Recordings</h1>
</div>
<p class="loading-subtitle">Please wait while we fetch your recordings...</p>
</div>
<div class="loading-spinner-container">
<mat-spinner diameter="48"></mat-spinner>
</div>
<div class="loading-spinner-container">
<mat-spinner diameter="48"></mat-spinner>
</div>
</div>
}
} @else {
</div>
}
@if (!isInitializing) {
<div class="ov-page-container ov-mb-xxl">
<!-- Recordings Header -->
<div class="page-header">
<div class="title">
<mat-icon class="ov-recording-icon">video_library</mat-icon>
@ -27,6 +28,7 @@
<p class="subtitle">Manage all your meeting recordings, play, download, and share them with others.</p>
</div>
<!-- Content -->
<div class="page-content">
<ov-recording-lists
[recordings]="recordings()"
@ -38,7 +40,7 @@
[showLoadMore]="hasMoreRecordings"
(recordingAction)="onRecordingAction($event)"
(loadMore)="loadMoreRecordings($event)"
(refresh)="refreshRecordings()"
(refresh)="refreshRecordings($event)"
(filterChange)="refreshRecordings($event)"
>
</ov-recording-lists>

View File

@ -16,8 +16,11 @@ import { ILogger, LoggerService } from 'openvidu-components-angular';
})
export class RecordingsComponent implements OnInit {
recordings = signal<MeetRecordingInfo[]>([]);
// Loading state
isInitializing = true;
showInitialLoader = false;
isLoading = false;
showLoadingSpinner = false;
// Pagination
hasMoreRecordings = false;
@ -36,6 +39,10 @@ export class RecordingsComponent implements OnInit {
async ngOnInit() {
const roomId = this.route.snapshot.queryParamMap.get('room-id');
const delayLoader = setTimeout(() => {
this.showInitialLoader = true;
}, 200);
if (roomId) {
// If a specific room ID is provided, filter recordings by that room
await this.loadRecordings({ nameFilter: roomId, statusFilter: '' });
@ -43,6 +50,10 @@ export class RecordingsComponent implements OnInit {
// Load all recordings if no room ID is specified
await this.loadRecordings();
}
clearTimeout(delayLoader);
this.showInitialLoader = false;
this.isInitializing = false;
}
async onRecordingAction(action: RecordingTableAction) {
@ -68,16 +79,15 @@ export class RecordingsComponent implements OnInit {
}
}
private async loadRecordings(filters?: { nameFilter: string; statusFilter: string }) {
this.isLoading = true;
const delaySpinner = setTimeout(() => {
this.showLoadingSpinner = true;
private async loadRecordings(filters?: { nameFilter: string; statusFilter: string }, refresh = false) {
const delayLoader = setTimeout(() => {
this.isLoading = true;
}, 200);
try {
const recordingFilters: MeetRecordingFilters = {
maxItems: 50,
nextPageToken: this.nextPageToken
nextPageToken: !refresh ? this.nextPageToken : undefined
};
// Apply room ID filter if provided
@ -93,9 +103,14 @@ export class RecordingsComponent implements OnInit {
filteredRecordings = response.recordings.filter((r) => r.status === filters.statusFilter);
}
// Update recordings list
const currentRecordings = this.recordings();
this.recordings.set([...currentRecordings, ...filteredRecordings]);
if (!refresh) {
// Update recordings list
const currentRecordings = this.recordings();
this.recordings.set([...currentRecordings, ...filteredRecordings]);
} else {
// Replace recordings list
this.recordings.set(filteredRecordings);
}
// Update pagination
this.nextPageToken = response.pagination.nextPageToken;
@ -104,9 +119,8 @@ export class RecordingsComponent implements OnInit {
this.notificationService.showAlert('Failed to load recordings');
this.log.e('Error loading recordings:', error);
} finally {
clearTimeout(delayLoader);
this.isLoading = false;
clearTimeout(delaySpinner);
this.showLoadingSpinner = false;
}
}
@ -116,10 +130,7 @@ export class RecordingsComponent implements OnInit {
}
async refreshRecordings(filters?: { nameFilter: string; statusFilter: string }) {
this.recordings.set([]);
this.nextPageToken = undefined;
this.hasMoreRecordings = false;
await this.loadRecordings(filters);
await this.loadRecordings(filters, true);
}
private async playRecording(recording: MeetRecordingInfo) {

View File

@ -21,7 +21,7 @@
</div>
<!-- Loading State -->
@if (isLoading && showLoadingSpinner) {
@if (isInitializing && showInitialLoader) {
<div class="recordings-loading-container fade-in">
<div class="loading-card">
<mat-icon class="ov-recording-icon loading-icon">video_library</mat-icon>
@ -35,7 +35,7 @@
}
<!-- Content -->
@if (!isLoading || recordings().length > 0) {
@if (!isInitializing) {
<div class="recordings-content fade-in-delayed">
<div class="section-content">
<ov-recording-lists
@ -46,7 +46,9 @@
[showFilters]="false"
[showSelection]="true"
[showRoomInfo]="false"
[showLoadMore]="hasMoreRecordings"
(recordingAction)="onRecordingAction($event)"
(loadMore)="loadMoreRecordings()"
(refresh)="refreshRecordings()"
class="recordings-list"
>

View File

@ -22,8 +22,10 @@ export class RoomRecordingsComponent implements OnInit {
roomName = '';
canDeleteRecordings = false;
// Loading state
isInitializing = true;
showInitialLoader = false;
isLoading = false;
showLoadingSpinner = false;
// Pagination
hasMoreRecordings = false;
@ -45,8 +47,18 @@ export class RoomRecordingsComponent implements OnInit {
async ngOnInit() {
this.roomId = this.route.snapshot.paramMap.get('room-id')!;
this.canDeleteRecordings = this.recordingService.canDeleteRecordings();
// Load recordings
const delayLoader = setTimeout(() => {
this.showInitialLoader = true;
}, 200);
await this.loadRecordings();
clearTimeout(delayLoader);
this.showInitialLoader = false;
this.isInitializing = false;
// Set room name based on recordings or roomId
if (this.recordings()) {
this.roomName = this.recordings()[0].roomName;
@ -89,17 +101,16 @@ export class RoomRecordingsComponent implements OnInit {
}
}
private async loadRecordings(statusFilter?: string) {
this.isLoading = true;
const delaySpinner = setTimeout(() => {
this.showLoadingSpinner = true;
private async loadRecordings(statusFilter?: string, refresh = false) {
const delayLoader = setTimeout(() => {
this.isLoading = true;
}, 200);
try {
const recordingFilters: MeetRecordingFilters = {
roomId: this.roomId,
maxItems: 50,
nextPageToken: this.nextPageToken
nextPageToken: !refresh ? this.nextPageToken : undefined
};
const response = await this.recordingService.listRecordings(recordingFilters);
@ -110,9 +121,14 @@ export class RoomRecordingsComponent implements OnInit {
filteredRecordings = response.recordings.filter((r) => r.status === statusFilter);
}
// Update recordings list
const currentRecordings = this.recordings();
this.recordings.set([...currentRecordings, ...filteredRecordings]);
if (!refresh) {
// Update recordings list
const currentRecordings = this.recordings();
this.recordings.set([...currentRecordings, ...filteredRecordings]);
} else {
// Replace recordings list
this.recordings.set(filteredRecordings);
}
// Update pagination
this.nextPageToken = response.pagination.nextPageToken;
@ -121,9 +137,8 @@ export class RoomRecordingsComponent implements OnInit {
this.notificationService.showAlert('Failed to load recordings');
this.log.e('Error loading recordings:', error);
} finally {
clearTimeout(delayLoader);
this.isLoading = false;
clearTimeout(delaySpinner);
this.showLoadingSpinner = false;
}
}
@ -133,10 +148,7 @@ export class RoomRecordingsComponent implements OnInit {
}
async refreshRecordings() {
this.recordings.set([]);
this.nextPageToken = undefined;
this.hasMoreRecordings = false;
await this.loadRecordings();
await this.loadRecordings(undefined, true);
}
private async playRecording(recording: MeetRecordingInfo) {