frontend: enhance RoomRecordingsComponent with improved UI and functionality for managing recordings
This commit is contained in:
parent
aa13385c86
commit
de83220e09
File diff suppressed because one or more lines are too long
@ -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));
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user