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 { Component, OnInit } from '@angular/core';
|
||||||
import { HttpService } from '@lib/services';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import {
|
import { MatCardModule } from '@angular/material/card';
|
||||||
OpenViduComponentsModule,
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
ApiDirectiveModule,
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
RecordingDeleteRequestedEvent
|
import { ActivatedRoute } from '@angular/router';
|
||||||
} from 'openvidu-components-angular';
|
import { ContextService, HttpService } from '@lib/services';
|
||||||
import { MeetRecordingInfo } from 'shared-meet-components';
|
import { ActionService, MeetRecordingInfo, OpenViduComponentsUiModule } from 'shared-meet-components';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-room-recordings',
|
selector: 'app-room-recordings',
|
||||||
templateUrl: './room-recordings.component.html',
|
templateUrl: './room-recordings.component.html',
|
||||||
styleUrls: ['./room-recordings.component.scss'],
|
styleUrls: ['./room-recordings.component.scss'],
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [OpenViduComponentsModule, ApiDirectiveModule]
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
DatePipe,
|
||||||
|
DecimalPipe,
|
||||||
|
OpenViduComponentsUiModule
|
||||||
|
]
|
||||||
})
|
})
|
||||||
export class RoomRecordingsComponent implements OnInit {
|
export class RoomRecordingsComponent implements OnInit {
|
||||||
|
roomId = '';
|
||||||
|
canDeleteRecordings = false;
|
||||||
recordings: MeetRecordingInfo[] = [];
|
recordings: MeetRecordingInfo[] = [];
|
||||||
loading = true;
|
moreRecordings = false;
|
||||||
error = '';
|
private nextPageToken: string | undefined;
|
||||||
private continuationToken: string | undefined;
|
|
||||||
|
|
||||||
constructor(private httpService: HttpService) {}
|
constructor(
|
||||||
|
protected contextService: ContextService,
|
||||||
|
protected httpService: HttpService,
|
||||||
|
protected actionService: ActionService,
|
||||||
|
protected route: ActivatedRoute
|
||||||
|
) {}
|
||||||
|
|
||||||
async ngOnInit() {
|
async ngOnInit() {
|
||||||
|
this.route.params.subscribe((params) => {
|
||||||
|
this.roomId = params['room-id'];
|
||||||
|
});
|
||||||
|
this.canDeleteRecordings = this.contextService.canDeleteRecordings();
|
||||||
|
|
||||||
await this.loadRecordings();
|
await this.loadRecordings();
|
||||||
console.log('Recordings loaded:', this.recordings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async onLoadMoreRecordingsRequested() {
|
async loadMoreRecordings() {
|
||||||
if (!this.continuationToken) {
|
if (!this.moreRecordings || !this.nextPageToken) {
|
||||||
console.warn('No more recordings to load');
|
console.warn('No more recordings to load');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -37,27 +56,70 @@ export class RoomRecordingsComponent implements OnInit {
|
|||||||
await this.loadRecordings();
|
await this.loadRecordings();
|
||||||
}
|
}
|
||||||
|
|
||||||
async onRefreshRecordingsClicked() {
|
async refreshRecordings() {
|
||||||
|
this.resetRecordings();
|
||||||
await this.loadRecordings();
|
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 {
|
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) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.loadRecordings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async loadRecordings() {
|
private async loadRecordings() {
|
||||||
try {
|
try {
|
||||||
const { recordings, continuationToken } = await this.httpService.getRecordings(this.continuationToken);
|
const response = await this.httpService.getRecordings(this.nextPageToken);
|
||||||
this.recordings = recordings;
|
this.recordings.push(...response.recordings);
|
||||||
this.continuationToken = continuationToken;
|
this.recordings = this.sortRecordingsByDate(this.recordings);
|
||||||
|
this.nextPageToken = response.pagination.nextPageToken;
|
||||||
|
this.moreRecordings = response.pagination.isTruncated;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(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