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

View File

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

View File

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

View File

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

View File

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