Browse Source

UI: Added multiple-file-input; Add support load multiple resource

pull/4391/head
Vladyslav_Prykhodko 6 years ago
parent
commit
5672d2c451
  1. 23
      ui-ngx/src/app/core/http/resource.service.ts
  2. 19
      ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts
  3. 4
      ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html
  4. 23
      ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts
  5. 4
      ui-ngx/src/app/shared/components/file-input.component.html
  6. 107
      ui-ngx/src/app/shared/components/file-input.component.ts
  7. 5
      ui-ngx/src/app/shared/models/resource.models.ts

23
ui-ngx/src/app/core/http/resource.service.ts

@ -18,10 +18,10 @@ import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils'; import { defaultHttpOptionsFromConfig, RequestConfig } from '@core/http/http-utils';
import { Observable } from 'rxjs'; import { forkJoin, Observable, of } from 'rxjs';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { Resource, ResourceInfo } from '@shared/models/resource.models'; import { Resource, ResourceInfo } from '@shared/models/resource.models';
import { map } from 'rxjs/operators'; import { catchError, map, mergeMap } from 'rxjs/operators';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -70,6 +70,25 @@ export class ResourceService {
); );
} }
public saveResources(resources: Resource[], config?: RequestConfig) {
let partSize = 100;
partSize = resources.length > partSize ? partSize : resources.length;
const resourceObservables = [];
for (let i = 0; i < partSize; i++) {
resourceObservables.push(this.saveResource(resources[i], config).pipe(catchError(error => of(error))));
}
return forkJoin(resourceObservables).pipe(
mergeMap((resource) => {
resources.splice(0, partSize);
if (resources.length) {
return this.saveResources(resources, config);
} else {
return of(resource);
}
})
);
}
public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> { public saveResource(resource: Resource, config?: RequestConfig): Observable<Resource> {
return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config)); return this.http.post<Resource>('/api/resource', resource, defaultHttpOptionsFromConfig(config));
} }

19
ui-ngx/src/app/modules/home/pages/resource/resources-library-table-config.resolve.ts

@ -85,10 +85,27 @@ export class ResourcesLibraryTableConfigResolver implements Resolve<EntityTableC
this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink) as Observable<PageData<Resource>>; this.config.entitiesFetchFunction = pageLink => this.resourceService.getResources(pageLink) as Observable<PageData<Resource>>;
this.config.loadEntity = id => this.resourceService.getResource(id.id); this.config.loadEntity = id => this.resourceService.getResource(id.id);
this.config.saveEntity = resource => this.resourceService.saveResource(resource); this.config.saveEntity = resource => this.saveResource(resource);
this.config.deleteEntity = id => this.resourceService.deleteResource(id.id); this.config.deleteEntity = id => this.resourceService.deleteResource(id.id);
} }
saveResource(resource) {
if (Array.isArray(resource.data)) {
const resources = [];
resource.data.forEach((data, index) => {
resources.push({
resourceType: resource.resourceType,
data,
fileName: resource.fileName[index],
title: resource.title
});
});
return this.resourceService.saveResources(resources, {resendRequest: true});
} else {
return this.resourceService.saveResource(resource);
}
}
resolve(): EntityTableConfig<Resource> { resolve(): EntityTableConfig<Resource> {
this.config.tableTitle = this.translate.instant('resource.resources-library'); this.config.tableTitle = this.translate.instant('resource.resources-library');
const authUser = getCurrentAuthUser(this.store); const authUser = getCurrentAuthUser(this.store);

4
ui-ngx/src/app/modules/home/pages/resource/resources-library.component.html

@ -44,9 +44,11 @@
<tb-file-input <tb-file-input
formControlName="data" formControlName="data"
required required
[convertToBase64]="true" [readAsBinary]="true"
[allowedExtensions]="getAllowedExtensions()" [allowedExtensions]="getAllowedExtensions()"
[contentConvertFunction]="convertToBase64File"
[accept]="getAcceptType()" [accept]="getAcceptType()"
[multipleFile]="entityForm.get('resourceType').value === resourceType.LWM2M_MODEL"
dropLabel="{{'resource.drop-file' | translate}}" dropLabel="{{'resource.drop-file' | translate}}"
[existingFileName]="entityForm.get('fileName')?.value" [existingFileName]="entityForm.get('fileName')?.value"
(fileNameChanged)="entityForm?.get('fileName').patchValue($event)"> (fileNameChanged)="entityForm?.get('fileName').patchValue($event)">

23
ui-ngx/src/app/modules/home/pages/resource/resources-library.component.ts

@ -29,7 +29,7 @@ import {
ResourceTypeMIMETypes, ResourceTypeMIMETypes,
ResourceTypeTranslationMap ResourceTypeTranslationMap
} from '@shared/models/resource.models'; } from '@shared/models/resource.models';
import { distinctUntilChanged, takeUntil } from 'rxjs/operators'; import { pairwise, startWith, takeUntil } from 'rxjs/operators';
@Component({ @Component({
selector: 'tb-resources-library', selector: 'tb-resources-library',
@ -54,15 +54,22 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
ngOnInit() { ngOnInit() {
super.ngOnInit(); super.ngOnInit();
this.entityForm.get('resourceType').valueChanges.pipe( this.entityForm.get('resourceType').valueChanges.pipe(
distinctUntilChanged((oldValue, newValue) => [oldValue, newValue].includes(this.resourceType.LWM2M_MODEL)), startWith(ResourceType.LWM2M_MODEL),
pairwise(),
takeUntil(this.destroy$) takeUntil(this.destroy$)
).subscribe((type) => { ).subscribe(([previousType, type]) => {
if (previousType === this.resourceType.LWM2M_MODEL) {
this.entityForm.get('title').setValidators(Validators.required);
this.entityForm.get('title').updateValueAndValidity({emitEvent: false});
}
if (type === this.resourceType.LWM2M_MODEL) { if (type === this.resourceType.LWM2M_MODEL) {
this.entityForm.get('title').clearValidators(); this.entityForm.get('title').clearValidators();
} else { this.entityForm.get('title').updateValueAndValidity({emitEvent: false});
this.entityForm.get('title').setValidators(Validators.required);
} }
this.entityForm.get('title').updateValueAndValidity({emitEvent: false}); this.entityForm.patchValue({
data: null,
fileName: null
}, {emitEvent: false});
}); });
} }
@ -119,4 +126,8 @@ export class ResourcesLibraryComponent extends EntityComponent<Resource> impleme
return '*/*'; return '*/*';
} }
} }
convertToBase64File(data: string): string {
return window.btoa(data);
}
} }

4
ui-ngx/src/app/shared/components/file-input.component.html

@ -18,7 +18,7 @@
<div class="tb-container"> <div class="tb-container">
<label class="tb-title">{{ label }}</label> <label class="tb-title">{{ label }}</label>
<ng-container #flow="flow" <ng-container #flow="flow"
[flowConfig]="{singleFile: true, allowDuplicateUploads: true}"> [flowConfig]="{allowDuplicateUploads: true}">
<div class="tb-file-select-container"> <div class="tb-file-select-container">
<div class="tb-file-clear-container"> <div class="tb-file-clear-container">
<button mat-button mat-icon-button color="primary" <button mat-button mat-icon-button color="primary"
@ -34,7 +34,7 @@
flowDrop flowDrop
[flow]="flow.flowJs"> [flow]="flow.flowJs">
<label for="{{inputId}}">{{ dropLabel }}</label> <label for="{{inputId}}">{{ dropLabel }}</label>
<input class="file-input" flowButton type="file" [flow]="flow.flowJs" [flowAttributes]="{accept: accept}" id="{{inputId}}"> <input class="file-input" flowButton #flowInput type="file" [flow]="flow.flowJs" [flowAttributes]="{accept: accept}" id="{{inputId}}">
</div> </div>
</div> </div>
</ng-container> </ng-container>

107
ui-ngx/src/app/shared/components/file-input.component.ts

@ -17,6 +17,7 @@
import { import {
AfterViewInit, AfterViewInit,
Component, Component,
ElementRef,
EventEmitter, EventEmitter,
forwardRef, forwardRef,
Input, Input,
@ -102,17 +103,34 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
existingFileName: string; existingFileName: string;
@Input() @Input()
convertToBase64 = false; readAsBinary = false;
private multipleFileValue = false;
@Input()
set multipleFile(value: boolean) {
this.multipleFileValue = value;
if (this.flow?.flowJs) {
this.updateMultipleFileMode(this.multipleFile);
}
}
get multipleFile(): boolean {
return this.multipleFileValue;
}
@Output() @Output()
fileNameChanged = new EventEmitter<string>(); fileNameChanged = new EventEmitter<string|string[]>();
fileName: string; fileName: string | string[];
fileContent: any; fileContent: any;
@ViewChild('flow', {static: true}) @ViewChild('flow', {static: true})
flow: FlowDirective; flow: FlowDirective;
@ViewChild('flowInput', {static: true})
flowInput: ElementRef;
autoUploadSubscription: Subscription; autoUploadSubscription: Subscription;
private propagateChange = null; private propagateChange = null;
@ -125,34 +143,60 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
ngAfterViewInit() { ngAfterViewInit() {
this.autoUploadSubscription = this.flow.events$.subscribe(event => { this.autoUploadSubscription = this.flow.events$.subscribe(event => {
if (event.type === 'fileAdded') { if (event.type === 'filesAdded') {
const file = event.event[0] as flowjs.FlowFile; const readers = [];
if (this.filterFile(file)) { (event.event[0] as flowjs.FlowFile[]).forEach(file => {
const reader = new FileReader(); if (this.filterFile(file)) {
reader.onload = (loadEvent) => { readers.push(this.readerAsFile(file));
if (typeof reader.result === 'string') { }
const fileContent = this.convertToBase64 ? window.btoa(reader.result) : reader.result; });
if (fileContent && fileContent.length > 0) { if (readers.length) {
if (this.contentConvertFunction) { Promise.all(readers).then((filesContent) => {
this.fileContent = this.contentConvertFunction(fileContent); filesContent = filesContent.filter(content => content.fileContent != null);
} else { if (filesContent.length === 1) {
this.fileContent = fileContent; this.fileContent = filesContent[0].fileContent;
} this.fileName = filesContent[0].fileName;
if (this.fileContent) { this.updateModel();
this.fileName = file.name; } else if (filesContent.length > 1) {
} else { this.fileContent = filesContent.map(content => content.fileContent);
this.fileName = null; this.fileName = filesContent.map(content => content.fileName);
} this.updateModel();
this.updateModel(); }
} });
}
}
});
if (!this.multipleFile) {
this.updateMultipleFileMode(this.multipleFile);
}
}
private readerAsFile(file: flowjs.FlowFile): Promise<any> {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onload = () => {
let fileName = null;
let fileContent = null;
if (typeof reader.result === 'string') {
fileContent = reader.result;
if (fileContent && fileContent.length > 0) {
if (this.contentConvertFunction) {
fileContent = this.contentConvertFunction(fileContent);
}
if (fileContent) {
fileName = file.name;
} }
};
if (this.convertToBase64) {
reader.readAsBinaryString(file.file);
} else {
reader.readAsText(file.file);
} }
} }
resolve({fileContent, fileName});
};
reader.onerror = () => {
resolve({fileContent: null, fileName: null});
};
if (this.readAsBinary) {
reader.readAsBinaryString(file.file);
} else {
reader.readAsText(file.file);
} }
}); });
} }
@ -207,4 +251,11 @@ export class FileInputComponent extends PageComponent implements AfterViewInit,
this.fileContent = null; this.fileContent = null;
this.updateModel(); this.updateModel();
} }
private updateMultipleFileMode(multiple: boolean) {
this.flow.flowJs.opts.singleFile = !multiple;
if (!multiple) {
this.flowInput.nativeElement.removeAttribute('multiple');
}
}
} }

5
ui-ngx/src/app/shared/models/resource.models.ts

@ -59,3 +59,8 @@ export interface Resource extends ResourceInfo {
data: string; data: string;
fileName: string; fileName: string;
} }
export interface Resources extends ResourceInfo {
data: string|string[];
fileName: string|string[];
}

Loading…
Cancel
Save