feat: add upload progress indicator in percent for video upload

This commit is contained in:
Chneemann 2025-05-04 18:39:06 +02:00
parent 0caabff4d7
commit 3f5d397dfa
8 changed files with 101 additions and 24 deletions

View file

@ -51,7 +51,7 @@
<!-- Loop through genres -->
@for (genre of genres; track genre) {
<option [value]="genre.name">{{ genre.name }}</option>
<option [value]="genre.code">{{ genre.name }}</option>
} }
</select>
<div class="error-msg">
@ -96,5 +96,6 @@
@if(videoData.send){
<app-loading-dialog
loadingMsg="Video is uploading, please be patient a moment."
[uploadProgress]="uploadProgress"
></app-loading-dialog>
}

View file

@ -7,6 +7,7 @@ import { VideoService } from '../../../services/video.service';
import { LoadingDialogComponent } from '../../../shared/components/loading-dialog/loading-dialog.component';
import { Video } from '../../../interfaces/video.interface';
import { GenreService } from '../../../services/genre.service';
import { HttpEvent, HttpEventType } from '@angular/common/http';
@Component({
selector: 'app-upload-video',
@ -25,6 +26,8 @@ export class UploadVideoComponent {
@Output() uploadedVideo = new EventEmitter<Video>();
errorMsgFileSize: string | null = null;
uploadProgress: number | null = null;
readonly maxFileSizeMB = 20;
videoData = {
@ -102,28 +105,51 @@ export class UploadVideoComponent {
}
/**
* Handles the video upload form submission.
* @param ngForm The video upload form
* Handles form submission by validating the form and uploading the video.
* @param ngForm - The submitted Angular form
*/
async onSubmit(ngForm: NgForm): Promise<void> {
if (ngForm.submitted && ngForm.form.valid) {
try {
this.videoData.send = true;
const uploadedVideo = await this.videoService.uploadVideo(
this.createFormData()
);
this.uploadedVideo.emit(uploadedVideo);
ngForm.resetForm();
this.closeVideoUploadOverview();
this.errorService.clearError();
} catch (error) {
this.errorService.handleError(error);
} finally {
this.videoData.send = false;
}
this.videoData.send = true;
this.uploadProgress = 0;
this.videoService
.uploadVideoWithProgress(this.createFormData())
.subscribe({
next: (event) => this.handleUploadEvent(event, ngForm),
error: (err) => this.handleUploadError(err),
});
}
}
/**
* Handles upload progress and response events.
* @param event - The HTTP event from the upload observable
* @param ngForm - The form instance used for resetting after upload
*/
private handleUploadEvent(event: HttpEvent<any>, ngForm: NgForm): void {
if (event.type === HttpEventType.UploadProgress && event.total) {
this.uploadProgress = Math.round(100 * (event.loaded / event.total));
} else if (event.type === HttpEventType.Response) {
this.uploadedVideo.emit(event.body);
ngForm.resetForm();
this.closeVideoUploadOverview();
this.errorService.clearError();
this.uploadProgress = null;
this.videoData.send = false;
}
}
/**
* Handles upload errors and resets UI state.
* @param err - The error from the upload observable
*/
private handleUploadError(err: any): void {
this.errorService.handleError(err);
this.uploadProgress = null;
this.videoData.send = false;
}
/**
* Creates a FormData object containing the video data.
* @returns The FormData for the video upload

View file

@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { HttpClient, HttpEvent, HttpHeaders } from '@angular/common/http';
import { TokenService } from './token.service';
import { environment } from '../../environments/environment';
import { Observable, throwError } from 'rxjs';
@ -45,6 +45,30 @@ export class ApiService {
.pipe(catchError(this.handleError));
}
/**
* Sends a POST request to the specified endpoint with a request body.
*
* @template T The expected type of the response.
* @param endpoint The API endpoint to send the request to.
* @param body The request payload to send.
* @param auth Whether to include authentication headers. Defaults to false.
* @param options Additional options for the request.
* @returns An Observable of type HttpEvent<T>.
*/
postWithProgress<T>(
endpoint: string,
body: any,
auth: boolean = false,
options: any = {}
): Observable<HttpEvent<T>> {
const baseOptions = auth ? { headers: this.getAuthHeaders() } : {};
const mergedOptions = { ...baseOptions, ...options };
return this.http
.post<T>(`${environment.baseUrl}${endpoint}`, body, mergedOptions)
.pipe(catchError(this.handleError));
}
/**
* Sends a PUT request to the specified endpoint with a request body.
*

View file

@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
import { catchError, firstValueFrom, map, Observable, of } from 'rxjs';
import { ApiService } from './api.service';
import { UserService } from './user.service';
import { Router } from '@angular/router';
import { TokenService } from './token.service';
@Injectable({

View file

@ -1,9 +1,10 @@
import { Injectable } from '@angular/core';
import { catchError, firstValueFrom, map, Observable, of } from 'rxjs';
import { catchError, filter, firstValueFrom, map, Observable, of } from 'rxjs';
import { ApiService } from './api.service';
import { AuthService } from './auth.service';
import { ResolutionService } from './resolution.service';
import { Video } from '../interfaces/video.interface';
import { HttpClient, HttpEvent, HttpEventType } from '@angular/common/http';
@Injectable({
providedIn: 'root',
@ -15,12 +16,13 @@ export class VideoService {
private availableResolutions: string[];
/**
* Initializes the VideoService with ApiService, AuthService, and ResolutionService
* Initializes the VideoService with HttpClient,ApiService, AuthService, and ResolutionService
*
* It fetches the available resolutions from the ResolutionService and stores
* them in the availableResolutions field.
*/
constructor(
private http: HttpClient,
private apiService: ApiService,
private authService: AuthService,
private resolutionService: ResolutionService
@ -54,9 +56,15 @@ export class VideoService {
* @param formData the FormData object representing the video to upload
* @returns a promise resolved when the upload is successful
*/
uploadVideo(formData: FormData): Promise<Video> {
return firstValueFrom(
this.apiService.post('/video/upload/', formData, true)
uploadVideoWithProgress(formData: FormData): Observable<any> {
return this.apiService.postWithProgress<any>(
'/video/upload/',
formData,
true,
{
reportProgress: true,
observe: 'events',
}
);
}

View file

@ -1,6 +1,12 @@
<div class="loading-dialog">
<div class="loading-content">
<div id="loading" class="loader"></div>
<!-- Progress indicator -->
@if (uploadProgress !== null) {
<div class="loader-text">{{ uploadProgress }}%</div>
}
<div id="loadingText" class="loading-text">
<p>{{ loadingMsg }}</p>
</div>

View file

@ -21,6 +21,7 @@
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.loader {
@ -34,6 +35,17 @@
margin-bottom: 20px;
}
.loader-text {
position: absolute;
top: 20%;
left: 50%;
transform: translate(-50%, -50%);
color: $white;
text-shadow: 3px 3px 3px $black;
font-size: 18px;
font-weight: 400;
}
.loading-text {
text-align: center;
max-width: 300px;

View file

@ -9,4 +9,5 @@ import { Component, Input } from '@angular/core';
})
export class LoadingDialogComponent {
@Input() loadingMsg: string = '';
@Input() uploadProgress: number | null = null;
}