Merge pull request #5505 from tsiegleauq/multiple-file-download

Download mutliple files as zip
This commit is contained in:
Emanuel Schütze 2020-08-12 17:13:41 +02:00 committed by GitHub
commit c6abbb629e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 60 additions and 27 deletions

View File

@ -59,6 +59,7 @@
"css-element-queries": "^1.2.3",
"exceljs": "1.15.0",
"file-saver": "^2.0.2",
"jszip": "^3.5.0",
"lz4js": "^0.2.0",
"material-icon-font": "git+https://github.com/petergng/materialIconFont.git",
"moment": "^2.24.0",

View File

@ -273,4 +273,26 @@ export class HttpService {
public async delete<T>(path: string, data?: any, queryParams?: QueryParams, header?: HttpHeaders): Promise<T> {
return await this.send<T>(path, HTTPMethod.DELETE, data, queryParams, header);
}
/**
* Retrieves a binary file from the url and returns a base64 value
*
* @param url file url
* @returns a promise with a base64 string
*/
public async downloadAsBase64(url: string): Promise<string> {
return new Promise<string>(async (resolve, reject) => {
const headers = new HttpHeaders();
const file = await this.get<Blob>(url, {}, {}, headers, 'blob');
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const resultStr: string = reader.result as string;
resolve(resultStr.split(',')[1]);
};
reader.onerror = error => {
reject(error);
};
});
}
}

View File

@ -1,4 +1,3 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
@ -100,7 +99,7 @@ export class PdfDocumentService {
);
const promises = fontPathList.map(fontPath => {
return this.convertUrlToBase64(fontPath).then(base64 => {
return this.httpService.downloadAsBase64(fontPath).then(base64 => {
return {
[fontPath.split('/').pop()]: base64
};
@ -117,29 +116,6 @@ export class PdfDocumentService {
return vfs;
}
/**
* Retrieves a binary file from the url and returns a base64 value
*
* @param url file url
* @returns a promise with a base64 string
*/
private async convertUrlToBase64(url: string): Promise<string> {
return new Promise<string>((resolve, reject) => {
const headers = new HttpHeaders();
this.httpService.get<Blob>(url, {}, {}, headers, 'blob').then(file => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const resultStr: string = reader.result as string;
resolve(resultStr.split(',')[1]);
};
reader.onerror = error => {
reject(error);
};
});
});
}
/**
* Returns the name of a font from the value of the given
* config variable.
@ -665,7 +641,7 @@ export class PdfDocumentService {
}
if (!vfs[url]) {
const base64 = await this.convertUrlToBase64(url);
const base64 = await this.httpService.downloadAsBase64(url);
vfs[url] = base64;
}
}

View File

@ -2,6 +2,8 @@ import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { saveAs } from 'file-saver';
import * as JSZip from 'jszip';
import { BehaviorSubject, Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
@ -136,6 +138,18 @@ export class MediafileRepositoryService extends BaseIsListOfSpeakersContentObjec
return this.httpService.post<Identifiable>('/rest/mediafiles/mediafile/', file, {}, emptyHeader);
}
public async downloadArchive(archiveName: string, files: ViewMediafile[]): Promise<void> {
const zip = new JSZip();
for (const file of files) {
if (!file.is_directory) {
const base64Data = await this.httpService.downloadAsBase64(file.url);
zip.file(file.filename, base64Data, { base64: true });
}
}
const archive = await zip.generateAsync({ type: 'blob' });
saveAs(archive, archiveName);
}
public getDirectoryBehaviorSubject(): BehaviorSubject<ViewMediafile[]> {
return this.directoryBehaviorSubject;
}

View File

@ -229,12 +229,20 @@
<!-- Menu for Mediafiles -->
<mat-menu #mediafilesMenu="matMenu">
<div *ngIf="!isMultiSelect">
<button mat-menu-item (click)="downloadMultiple()">
<mat-icon>cloud_download</mat-icon>
<span>{{ 'Download folder' | translate }}</span>
</button>
<button mat-menu-item *osPerms="'mediafiles.can_manage'" (click)="toggleMultiSelect()">
<mat-icon>library_add</mat-icon>
<span>{{ 'Multiselect' | translate }}</span>
</button>
</div>
<div *ngIf="isMultiSelect">
<button mat-menu-item [disabled]="!selectedRows.length" (click)="downloadMultiple(selectedRows)">
<mat-icon>cloud_download</mat-icon>
<span>{{ 'Download selected' | translate }}</span>
</button>
<button mat-menu-item [disabled]="!selectedRows.length" (click)="move(moveDialog, selectedRows)">
<mat-icon>near_me</mat-icon>
<span>{{ 'Move' | translate }}</span>

View File

@ -23,6 +23,7 @@ import { OperatorService, Permission } from 'app/core/core-services/operator.ser
import { StorageService } from 'app/core/core-services/storage.service';
import { MediafileRepositoryService } from 'app/core/repositories/mediafiles/mediafile-repository.service';
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { ConfigService } from 'app/core/ui-services/config.service';
import { MediaManageService } from 'app/core/ui-services/media-manage.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ViewportService } from 'app/core/ui-services/viewport.service';
@ -202,7 +203,8 @@ export class MediafileListComponent extends BaseListViewComponent<ViewMediafile>
private fb: FormBuilder,
private formBuilder: FormBuilder,
private groupRepo: GroupRepositoryService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
private configService: ConfigService
) {
super(titleService, translate, matSnackBar, storage);
this.canMultiSelect = true;
@ -459,6 +461,16 @@ export class MediafileListComponent extends BaseListViewComponent<ViewMediafile>
});
}
public downloadMultiple(mediafiles: ViewMediafile[] = this.dataSource.source): void {
/**
* TODO: naming the files is discussable
*/
const eventName = this.configService.instant<string>('general_event_name');
const dirName = this.directory?.filename ?? this.translate.instant('Files');
const archiveName = `${eventName} - ${dirName}`.trim();
this.repo.downloadArchive(archiveName, mediafiles);
}
public move(templateRef: TemplateRef<string>, mediafiles: ViewMediafile[]): void {
this.moveForm.reset();