frontend: enhance RoomRecordingsComponent with improved UI and functionality for managing recordings

This commit is contained in:
juancarmore 2025-05-23 12:59:38 +02:00
parent aa13385c86
commit de83220e09
3 changed files with 387 additions and 32 deletions

View File

@ -0,0 +1,178 @@
.dashboard-container {
height: 100%;
}
.header {
height: 50px;
background-color: var(--ov-primary-action-color);
color: var(--ov-text-primary-color);
justify-content: space-between;
}
#header-title {
font-weight: bold;
}
#room-id {
font-weight: normal;
font-style: italic;
}
#refresh-btn {
color: var(--ov-text-primary-color);
}
.dashboard-body {
background-color: var(--ov-secondary-action-color);
height: 100%;
}
.recordings-container {
background-color: var(--ov-secondary-action-color);
height: 100%;
overflow-y: auto;
overflow-x: hidden;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
grid-auto-rows: max-content;
gap: 15px;
justify-content: center;
align-items: center;
padding: 20px;
padding-bottom: 80px;
box-sizing: border-box;
.recording-card {
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
background-color: var(--ov-surface-color);
}
}
.video-div-container {
position: relative;
text-align: center;
width: 100%;
height: 42%;
overflow: hidden;
background-color: var(--ov-light-color);
display: flex;
justify-content: center;
align-items: center;
img {
border-radius: var(--ov-video-radius);
display: inline;
position: relative;
max-width: 100%;
max-height: 100%;
}
}
.video-btns {
position: absolute;
transform: translate(-50%, -50%);
margin-right: -50%;
top: 50%;
left: 50%;
background-color: var(--ov-secondary-action-color);
border-radius: var(--ov-surface-radius);
}
.video-btns button #play {
color: var(--ov-text-primary-color);
}
.video-btns button #download {
color: var(--ov-accent-action-color);
}
.video-btns button #delete {
color: var(--ov-error-color);
}
.video-info-container > div {
width: 100%;
height: 100%;
display: table;
table-layout: fixed;
box-sizing: border-box;
margin-top: 20px;
}
.video-div-tag:first-child {
margin-top: 20px;
}
.video-div-tag {
display: table-row;
}
.video-card-tag {
font-size: 13px;
color: var(--ov-text-surface-color);
font-weight: bold;
}
.video-card-value {
float: right;
font-size: 13.5px;
padding: 0px 5px;
border-radius: 5px;
&.COMPLETE {
background-color: #8bffc9;
}
&.ACTIVE {
background-color: #ffcc00;
}
&.STARTING,
&.ENDING {
background-color: #f5d34c;
}
&.FAILED,
&.ABORTED,
&.LIMIT_REACHED {
background-color: #ff6666;
}
}
.status-value {
font-weight: bold;
}
.load-more-container {
position: absolute;
left: 50%;
bottom: 35px;
transform: translateX(-50%);
.load-more-btn {
background-color: var(--ov-surface-color);
color: var(--ov-text-surface-color);
border-radius: var(--ov-surface-radius);
}
}
.no-recordings-warn {
height: calc(100% - 52px);
width: 100%;
display: table;
text-align: center;
color: var(--ov-text-surface-color);
}
@media (max-width: 600px) {
.grid-container {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
}
@media (min-width: 601px) and (max-width: 900px) {
.grid-container {
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
}
}

View File

@ -1,35 +1,54 @@
import { HttpErrorResponse } from '@angular/common/http';
import { CommonModule, DatePipe, DecimalPipe } from '@angular/common';
import { Component, OnInit } from '@angular/core';
import { HttpService } from '@lib/services';
import {
OpenViduComponentsModule,
ApiDirectiveModule,
RecordingDeleteRequestedEvent
} from 'openvidu-components-angular';
import { MeetRecordingInfo } from 'shared-meet-components';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ActivatedRoute } from '@angular/router';
import { ContextService, HttpService } from '@lib/services';
import { ActionService, MeetRecordingInfo, OpenViduComponentsUiModule } from 'shared-meet-components';
@Component({
selector: 'app-room-recordings',
templateUrl: './room-recordings.component.html',
styleUrls: ['./room-recordings.component.scss'],
standalone: true,
imports: [OpenViduComponentsModule, ApiDirectiveModule]
imports: [
CommonModule,
MatToolbarModule,
MatCardModule,
MatButtonModule,
MatIconModule,
DatePipe,
DecimalPipe,
OpenViduComponentsUiModule
]
})
export class RoomRecordingsComponent implements OnInit {
roomId = '';
canDeleteRecordings = false;
recordings: MeetRecordingInfo[] = [];
loading = true;
error = '';
private continuationToken: string | undefined;
moreRecordings = false;
private nextPageToken: string | undefined;
constructor(private httpService: HttpService) {}
constructor(
protected contextService: ContextService,
protected httpService: HttpService,
protected actionService: ActionService,
protected route: ActivatedRoute
) {}
async ngOnInit() {
this.route.params.subscribe((params) => {
this.roomId = params['room-id'];
});
this.canDeleteRecordings = this.contextService.canDeleteRecordings();
await this.loadRecordings();
console.log('Recordings loaded:', this.recordings);
}
async onLoadMoreRecordingsRequested() {
if (!this.continuationToken) {
async loadMoreRecordings() {
if (!this.moreRecordings || !this.nextPageToken) {
console.warn('No more recordings to load');
return;
}
@ -37,27 +56,70 @@ export class RoomRecordingsComponent implements OnInit {
await this.loadRecordings();
}
async onRefreshRecordingsClicked() {
async refreshRecordings() {
this.resetRecordings();
await this.loadRecordings();
}
async onDeleteRecordingClicked(deleteRequest: RecordingDeleteRequestedEvent) {
playRecording(recording: MeetRecordingInfo) {
const queryParamForAvoidCache = `?t=${new Date().getTime()}`;
let recordingUrl = this.httpService.getRecordingMediaUrl(recording.recordingId);
recordingUrl += queryParamForAvoidCache;
this.actionService.openRecordingPlayerDialog(recordingUrl);
}
downloadRecording(recording: MeetRecordingInfo) {
const queryParamForAvoidCache = `?t=${new Date().getTime()}`;
let recordingUrl = this.httpService.getRecordingMediaUrl(recording.recordingId);
recordingUrl += queryParamForAvoidCache;
const link = document.createElement('a');
link.href = recordingUrl;
link.download = recording.filename || 'openvidu-recording.mp4';
link.dispatchEvent(
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window
})
);
// For Firefox it is necessary to delay revoking the ObjectURL
setTimeout(() => link.remove(), 100);
}
async deleteRecording(recording: MeetRecordingInfo) {
try {
await this.httpService.deleteRecording(deleteRequest.recordingId!);
await this.httpService.deleteRecording(recording.recordingId!);
this.recordings = this.recordings.filter((r) => r.recordingId !== recording.recordingId);
} catch (error) {
console.log(error);
}
await this.loadRecordings();
}
private async loadRecordings() {
try {
const { recordings, continuationToken } = await this.httpService.getRecordings(this.continuationToken);
this.recordings = recordings;
this.continuationToken = continuationToken;
const response = await this.httpService.getRecordings(this.nextPageToken);
this.recordings.push(...response.recordings);
this.recordings = this.sortRecordingsByDate(this.recordings);
this.nextPageToken = response.pagination.nextPageToken;
this.moreRecordings = response.pagination.isTruncated;
} catch (error) {
console.log(error);
}
}
private sortRecordingsByDate(recordings: MeetRecordingInfo[]) {
return recordings.sort((a, b) => {
const dateA = new Date(a.startDate || -1);
const dateB = new Date(b.startDate || -1);
return dateA.getTime() - dateB.getTime();
});
}
private resetRecordings() {
this.recordings = [];
this.nextPageToken = undefined;
this.moreRecordings = false;
}
}