feat: add upload progress indicator in percent for video upload
This commit is contained in:
parent
0caabff4d7
commit
3f5d397dfa
8 changed files with 101 additions and 24 deletions
|
|
@ -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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ import { Component, Input } from '@angular/core';
|
|||
})
|
||||
export class LoadingDialogComponent {
|
||||
@Input() loadingMsg: string = '';
|
||||
@Input() uploadProgress: number | null = null;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue