RED-3842: Show progress bar when downloading file
This commit is contained in:
parent
9d62607b64
commit
54bf4cbc09
@ -92,7 +92,6 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
private readonly _router: Router,
|
||||
private readonly _ngZone: NgZone,
|
||||
private readonly _logger: NGXLogger,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _annotationManager: REDAnnotationManager,
|
||||
private readonly _errorService: ErrorService,
|
||||
private readonly _filterService: FilterService,
|
||||
@ -347,7 +346,7 @@ export class FilePreviewScreenComponent extends AutoUnsubscribe implements OnIni
|
||||
|
||||
async downloadOriginalFile({ cacheIdentifier, dossierId, fileId, filename }: File) {
|
||||
const fileManagementService = this._injector.get(FileManagementService);
|
||||
const originalFile = fileManagementService.downloadOriginalFile(dossierId, fileId, 'response', cacheIdentifier);
|
||||
const originalFile = fileManagementService.downloadOriginal(dossierId, fileId, 'response', cacheIdentifier);
|
||||
download(await firstValueFrom(originalFile), filename);
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import { Dictionary, Dossier, DOSSIER_ID, File, FILE_ID } from '@red/domain';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { FilesMapService } from '@services/files/files-map.service';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { boolFactory } from '@iqser/common-ui';
|
||||
import { boolFactory, LoadingService } from '@iqser/common-ui';
|
||||
import { filter, map, startWith, tap, withLatestFrom } from 'rxjs/operators';
|
||||
import { FileManagementService } from '@services/files/file-management.service';
|
||||
import { dossiersServiceResolver } from '@services/entity-services/dossiers.service.provider';
|
||||
@ -12,6 +12,28 @@ import { wipeFilesCache } from '@red/cache';
|
||||
import { DossiersService } from '@services/dossiers/dossiers.service';
|
||||
import { FilesService } from '@services/files/files.service';
|
||||
import { DictionaryService } from '@services/entity-services/dictionary.service';
|
||||
import { HttpEvent, HttpEventType, HttpProgressEvent, HttpResponse } from '@angular/common/http';
|
||||
|
||||
const ONE_MEGABYTE = 1024 * 1024;
|
||||
|
||||
function getRemainingTime(event: HttpProgressEvent, startTime: number) {
|
||||
const currTime = new Date().getTime();
|
||||
const remaining = event.total - event.loaded;
|
||||
const speed = event.loaded / ((currTime - startTime) / 1000);
|
||||
return Math.round(remaining / speed);
|
||||
}
|
||||
|
||||
function getRemainingTimeVerbose(event: HttpProgressEvent, startTime: number) {
|
||||
const remainingTime = getRemainingTime(event, startTime);
|
||||
if (remainingTime > 60) {
|
||||
return `${Math.round(remainingTime / 60)} minutes`;
|
||||
}
|
||||
return `${remainingTime} seconds`;
|
||||
}
|
||||
|
||||
function isDownload(event: HttpEvent<Blob>): event is HttpProgressEvent {
|
||||
return event.type === HttpEventType.DownloadProgress && event.total > ONE_MEGABYTE;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FilePreviewStateService {
|
||||
@ -40,6 +62,7 @@ export class FilePreviewStateService {
|
||||
private readonly _dossiersService: DossiersService,
|
||||
private readonly _fileManagementService: FileManagementService,
|
||||
private readonly _dictionaryService: DictionaryService,
|
||||
private readonly _loadingService: LoadingService,
|
||||
) {
|
||||
const dossiersService = dossiersServiceResolver(_injector, router);
|
||||
|
||||
@ -97,8 +120,35 @@ export class FilePreviewStateService {
|
||||
}
|
||||
|
||||
#downloadOriginalFile(cacheIdentifier: string, wipeCaches = true): Observable<Blob> {
|
||||
const downloadFile = this._fileManagementService.downloadOriginalFile(this.dossierId, this.fileId, 'body', cacheIdentifier);
|
||||
const downloadFile$ = this.#getFileToDownload(cacheIdentifier);
|
||||
const obs = wipeCaches ? from(wipeFilesCache()) : of({});
|
||||
return obs.pipe(switchMap(() => downloadFile));
|
||||
return obs.pipe(switchMap(() => downloadFile$));
|
||||
}
|
||||
|
||||
#getFileToDownload(cacheIdentifier: string): Observable<Blob> {
|
||||
const downloadFile$ = this._fileManagementService.downloadOriginal(this.dossierId, this.fileId, 'events', cacheIdentifier);
|
||||
let start;
|
||||
return downloadFile$.pipe(
|
||||
tap(() => (start ? undefined : (start = new Date().getTime()))),
|
||||
tap(event => this.#showLoadingIfIsDownloadEvent(event, start)),
|
||||
filter(event => event.type === HttpEventType.Response),
|
||||
map((event: HttpResponse<Blob>) => event.body),
|
||||
);
|
||||
}
|
||||
|
||||
#showLoadingIfIsDownloadEvent(event: HttpEvent<Blob>, start) {
|
||||
if (isDownload(event)) {
|
||||
this.#updateDownloadProgress(event, start);
|
||||
}
|
||||
}
|
||||
|
||||
#updateDownloadProgress(event: HttpProgressEvent, startTime: number) {
|
||||
const progress = Math.round((event.loaded / event.total) * 100);
|
||||
this._loadingService.update({
|
||||
title: 'Loading ' + this.file.filename,
|
||||
type: 'progress-bar',
|
||||
value: progress,
|
||||
remainingTime: getRemainingTimeVerbose(event, startTime),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,7 +10,7 @@ import {
|
||||
ViewChild,
|
||||
} from '@angular/core';
|
||||
import { PermissionsService } from '@services/permissions.service';
|
||||
import { Action, ActionTypes, Dossier, File } from '@red/domain';
|
||||
import { Action, ActionTypes, Dossier, File, User } from '@red/domain';
|
||||
import { DossiersDialogService } from '../../services/dossiers-dialog.service';
|
||||
import {
|
||||
CircleButtonType,
|
||||
@ -46,7 +46,7 @@ import { ROTATION_ACTION_BUTTONS } from '../../../pdf-viewer/utils/constants';
|
||||
})
|
||||
export class FileActionsComponent implements OnChanges {
|
||||
readonly circleButtonTypes = CircleButtonTypes;
|
||||
readonly currentUser;
|
||||
readonly currentUser: User;
|
||||
|
||||
@Input() file: File;
|
||||
@Input() dossier: Dossier;
|
||||
|
||||
@ -215,8 +215,8 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
private _createSubscription(uploadFile: FileUploadModel) {
|
||||
this.activeUploads++;
|
||||
const obs = this.uploadFileForm(uploadFile.dossierId, uploadFile.keepManualRedactions, uploadFile.file);
|
||||
return obs.subscribe(
|
||||
event => {
|
||||
return obs.subscribe({
|
||||
next: event => {
|
||||
if (event.type === HttpEventType.UploadProgress) {
|
||||
uploadFile.progress = Math.round((event.loaded / (event.total || event.loaded)) * 100);
|
||||
this._applicationRef.tick();
|
||||
@ -234,7 +234,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
this._removeUpload(uploadFile);
|
||||
}
|
||||
},
|
||||
(err: HttpErrorResponse) => {
|
||||
error: (err: HttpErrorResponse) => {
|
||||
uploadFile.completed = true;
|
||||
uploadFile.error = {
|
||||
// Extract error message
|
||||
@ -246,7 +246,7 @@ export class FileUploadService extends GenericService<IFileUploadResult> impleme
|
||||
this.scheduleUpload(uploadFile);
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _removeUpload(fileUploadModel: FileUploadModel) {
|
||||
|
||||
@ -1,21 +1,16 @@
|
||||
import { GenericService, HeadersConfiguration, List, QueryParam, RequiredParam, Validate } from '@iqser/common-ui';
|
||||
import { Injectable, Injector } from '@angular/core';
|
||||
import { HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { HttpEvent, HttpHeaders, HttpResponse } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { FilesService } from './files.service';
|
||||
import { DossierStatsService } from '../dossiers/dossier-stats.service';
|
||||
import { File, IPageRotationRequest } from '@red/domain';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FileManagementService extends GenericService<unknown> {
|
||||
constructor(
|
||||
protected readonly _injector: Injector,
|
||||
private readonly _filesService: FilesService,
|
||||
private readonly _dossierStatsService: DossierStatsService,
|
||||
) {
|
||||
constructor(protected readonly _injector: Injector, private readonly _filesService: FilesService) {
|
||||
super(_injector, '');
|
||||
}
|
||||
|
||||
@ -30,13 +25,13 @@ export class FileManagementService extends GenericService<unknown> {
|
||||
return this._post(body, `rotate/${dossierId}/${fileId}`);
|
||||
}
|
||||
|
||||
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'body', indicator?: string): Observable<Blob>;
|
||||
downloadOriginalFile(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
|
||||
downloadOriginal(dossierId: string, fileId: string, observe?: 'events', indicator?: string): Observable<HttpEvent<Blob>>;
|
||||
downloadOriginal(dossierId: string, fileId: string, observe?: 'response', indicator?: string): Observable<HttpResponse<Blob>>;
|
||||
@Validate()
|
||||
downloadOriginalFile(
|
||||
downloadOriginal(
|
||||
@RequiredParam() dossierId: string,
|
||||
@RequiredParam() fileId: string,
|
||||
observe: 'body' | 'response' = 'body',
|
||||
observe: 'events' | 'response' = 'events',
|
||||
indicator?: string,
|
||||
) {
|
||||
const queryParams: QueryParam[] = [{ key: 'inline', value: true }];
|
||||
@ -56,6 +51,7 @@ export class FileManagementService extends GenericService<unknown> {
|
||||
params: this._queryParams(queryParams),
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: observe === 'events',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit f9e24883381ddbf93df5074ec1c176973db44ed1
|
||||
Subproject commit d5ded3615fdf420ca42c21826654013165279462
|
||||
@ -31,6 +31,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
readonly fileAttributes: FileAttributes;
|
||||
readonly fileId: string;
|
||||
readonly filename: string;
|
||||
readonly fileSize: number;
|
||||
readonly hasAnnotationComments: boolean;
|
||||
readonly hasHints: boolean;
|
||||
readonly hasImages: boolean;
|
||||
@ -94,6 +95,7 @@ export class File extends Entity<IFile> implements IFile {
|
||||
this.excludedFromAutomaticAnalysis = !!file.excludedFromAutomaticAnalysis;
|
||||
this.fileId = file.fileId;
|
||||
this.filename = file.filename;
|
||||
this.fileSize = file.fileSize;
|
||||
this.hasAnnotationComments = !!file.hasAnnotationComments;
|
||||
this.hasHints = !!file.hasHints;
|
||||
this.hasImages = !!file.hasImages;
|
||||
|
||||
@ -62,6 +62,7 @@ export interface IFile {
|
||||
* The file's name.
|
||||
*/
|
||||
readonly filename: string;
|
||||
readonly fileSize: number;
|
||||
/**
|
||||
* Shows if this file has comments on annotations.
|
||||
*/
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user