File

projects/netgrif-components-core/src/lib/data-fields/file-list-field/file-list-default-field/abstract-file-list-default-field.component.ts

Index

Properties

Properties

completed
completed: boolean
Type : boolean
downloading
downloading: boolean
Type : boolean
error
error: boolean
Type : boolean
progress
progress: number
Type : number
uploading
uploading: boolean
Type : boolean
import {
    AfterViewInit,
    Component,
    ElementRef,
    Inject,
    Input,
    OnDestroy,
    OnInit,
    Optional,
    ViewChild
} from "@angular/core";
import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token";
import {FileListField, FileListFieldValidation} from "../models/file-list-field";
import {Subscription} from "rxjs";
import {TaskResourceService} from "../../../resources/engine-endpoint/task-resource.service";
import {LoggerService} from "../../../logger/services/logger.service";
import {SnackBarService} from "../../../snack-bar/services/snack-bar.service";
import {TranslateService} from "@ngx-translate/core";
import {EventService} from "../../../event/services/event.service";
import {FileFieldIdBody} from "../../models/file-field-id-body";
import {EventOutcomeMessageResource} from "../../../resources/interface/message-resource";
import {ProgressType, ProviderProgress} from "../../../resources/resource-provider.service";
import {ChangedFieldsMap} from "../../../event/services/interfaces/changed-fields-map";
import {HttpParams} from "@angular/common/http";
import {take} from "rxjs/operators";
import {FileFieldValue} from "../../file-field/models/file-field-value";
import {AbstractBaseDataFieldComponent} from "../../base-component/abstract-base-data-field.component";
import {FileFieldRequest} from "../../../resources/interface/file-field-request-body";
import {AbstractFileFieldDefaultComponent} from '../../models/abstract-file-field-default-component';

export interface FilesState {
    progress: number;
    uploading: boolean;
    downloading: boolean;
    completed: boolean;
    error: boolean;
}


@Component({
    selector: 'ncc-abstract-file-list-default-field',
    template: ''
})
export abstract class AbstractFileListDefaultFieldComponent extends AbstractFileFieldDefaultComponent<FileListField> implements OnInit, AfterViewInit, OnDestroy {

    public uploadedFiles: Array<string>;
    public state: FilesState;
    private valueChange$: Subscription;

    /**
     * Values from file list field validation (eg. maxFiles 5)
     * maxFilesNumber - maximum uploadable files
     * maxFilesMessage - error message if number of files is exceeded
     */
    protected maxFilesNumber: number;
    protected maxFilesMessage: string;

    protected constructor(protected _taskResourceService: TaskResourceService,
                          protected _log: LoggerService,
                          protected _snackbar: SnackBarService,
                          protected _translate: TranslateService,
                          protected _eventService: EventService,
                          @Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData<FileListField>) {
        super(_log, _snackbar, _translate, dataFieldPortalData);
        this.state = this.defaultState;
        this.uploadedFiles = new Array<string>();
        this.maxFilesNumber = Number.POSITIVE_INFINITY;
        this.taskId = dataFieldPortalData.additionalFieldProperties.taskId as string;
    }

    ngOnInit(): void {
        this.valueChange$ = this.dataField.valueChanges().subscribe(() => {
            this.parseResponse();
        });
        if (this.dataField.validations && this.dataField.validations.length !== 0) {
            const val = this.dataField.validations.find(validation =>
                validation.validationRule.includes(FileListFieldValidation.MAX_FILES)
            );
            if (val && val.validationRule.split(' ').length === 2 && !isNaN(parseInt(val.validationRule.split(' ')[1], 10))) {
                this.maxFilesNumber = parseInt(val.validationRule.split(' ')[1], 10);
                this.maxFilesMessage = val.validationMessage && val.validationMessage !== '' ? val.validationMessage : null;
            }
        }
    }

    /**
     * Set file picker and image elements to [FileFieldService]{@link FileFieldService}.
     *
     * Initialize file image.
     */
    ngAfterViewInit(): void {
        if (this.fileUploadEl) {
            this.fileUploadEl.nativeElement.onchange = () => {
                this.upload();
            };
        }
    }

    ngOnDestroy(): void {
        super.ngOnDestroy();
        this.valueChange$.unsubscribe();
    }

    public chooseFile() {
        if (this.state.uploading || this.formControlRef.disabled) {
            return;
        }
        this.fileUploadEl.nativeElement.click();
    }

    /**
     * Call after click on file field.
     *
     * If file field has no file uploaded
     * [FilesUploadComponent]{@link AbstractFilesUploadComponent} via [SideMenu]{@link SideMenuService} opens.
     *
     * Otherwise opens a file picker from which the user can select files.
     */
    public upload() {
        if (!this.fileUploadEl.nativeElement.files || this.fileUploadEl.nativeElement.files.length === 0) {
            return;
        }
        if (!this.taskId) {
            this._log.error('File cannot be uploaded. No task is set to the field.');
            return;
        }
        if (this.fileUploadEl.nativeElement.files.length + this.uploadedFiles.length > this.maxFilesNumber) {
            this._snackbar.openErrorSnackBar(this.maxFilesMessage ? this.maxFilesMessage :
                this._translate.instant('dataField.snackBar.maxFilesExceeded') + this.maxFilesNumber
            );
            this.fileUploadEl.nativeElement.value = '';
            return;
        }

        let filesToUpload = Array.from(this.fileUploadEl.nativeElement.files);
        let sum = 0;
        filesToUpload.forEach(item => sum += item.size);
        if (this.dataField.maxUploadSizeInBytes &&
            this.dataField.maxUploadSizeInBytes < sum) {
            this._log.error('Files cannot be uploaded. Maximum size of files exceeded.');
            this.resolveMaxSizeMessage();
            this.fileUploadEl.nativeElement.value = '';
            return;
        }

        if (this.dataField.value?.namesPaths && this.dataField.value?.namesPaths.length !== 0) {
            this.dataField.value.namesPaths.forEach(namePath => {
                filesToUpload = filesToUpload.filter(fileToUpload => fileToUpload.name !== namePath.name);
            });
            if (filesToUpload.length === 0) {
                this._log.error('User chose the same files that are already uploaded. Uploading skipped');
                this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.wontUploadSameFiles'));
                this.fileUploadEl.nativeElement.value = '';
                return;
            }
        }

        if (!this.checkAllowedTypes()) {
            return;
        }

        this.state = this.defaultState;
        this.state.uploading = true;
        const fileFormData = new FormData();

        filesToUpload.forEach(fileToUpload => {
            fileFormData.append('files', fileToUpload);
        });
        const requestBody: FileFieldRequest = {
            parentTaskId: this.resolveParentTaskId(),
            fieldId: this.dataField.stringId,
        }
        fileFormData.append('data', new Blob([JSON.stringify(requestBody)], {type: 'application/json'}));
        this._taskResourceService.uploadFile(this.taskId, fileFormData, true).subscribe((response: EventOutcomeMessageResource) => {
                if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.UPLOAD) {
                    this.state.progress = (response as ProviderProgress).progress;
                } else {
                    this.state.completed = true;
                    this.state.uploading = false;
                    this.state.progress = 0;
                    this._log.debug(
                        `Files [${this.dataField.stringId}] were successfully uploaded`
                    );
                    if (response.error) {
                        this.state.error = true;
                        this._log.error(
                            `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, response.error
                        );
                        if (response.error) {
                            this._snackbar.openErrorSnackBar(this._translate.instant(response.error));
                        } else {
                            this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed'));
                        }
                    } else {
                        const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome);
                        this.dataField.emitChangedFields(changedFieldsMap);
                        this.state.error = false;
                        filesToUpload.forEach(fileToUpload => {
                            this.uploadedFiles.push(fileToUpload.name);
                            this.dataField.value.namesPaths.push({name: fileToUpload.name});
                            this.formControlRef.setValue(this.dataField.value.namesPaths.map(namePath => {
                                return namePath['name'];
                            }).join('/'));
                        });
                    }
                    this.dataField.touch = true;
                    this.dataField.update();
                    this.fileUploadEl.nativeElement.value = '';
                }
            }, error => {
                this.state.completed = true;
                this.state.error = true;
                this.state.uploading = false;
                this.state.progress = 0;
                if (error?.error?.message) {
                    this._snackbar.openErrorSnackBar(this._translate.instant(error.error.message));
                } else {
                    this._snackbar.openErrorSnackBar(this._translate.instant('dataField.snackBar.fileUploadFailed'));
                }
                this._log.error(
                    `File [${this.dataField.stringId}] ${this.fileUploadEl.nativeElement.files.item(0)} uploading has failed!`, error
                );
                this.dataField.touch = true;
                this.dataField.update();
                this.fileUploadEl.nativeElement.value = '';
            });
    }

    public download(fileName: string) {
        if (!this.dataField.value?.namesPaths?.find(namePath => namePath.name === fileName)) {
            return;
        }
        if (!this.taskId) {
            this._log.error('File cannot be downloaded. No task is set to the field.');
            return;
        }

        this.state = this.defaultState;
        this.state.downloading = true;
        let params = new HttpParams();
        params = params.set("fieldId", this.dataField.stringId);
        params = params.set("fileName", fileName);
        this._taskResourceService.downloadFile(this.resolveParentTaskId(), params).subscribe(response => {
            if ((response as ProviderProgress).type && (response as ProviderProgress).type === ProgressType.DOWNLOAD) {
                this.state.progress = (response as ProviderProgress).progress;
            } else {
                this._log.debug(`File [${this.dataField.stringId}] ${fileName} was successfully downloaded`);
                this.downloadViaAnchor(response as Blob, fileName);
                this.state.downloading = false;
                this.state.progress = 0;
                this.dataField.downloaded.push(fileName);
            }
        }, error => {
            this._log.error(`Downloading file [${this.dataField.stringId}] ${fileName} has failed!`, error);
            this._snackbar.openErrorSnackBar(fileName + ' ' + this._translate.instant('dataField.snackBar.downloadFail'));
            this.state.downloading = false;
            this.state.progress = 0;
        });
    }

    protected downloadViaAnchor(blob: Blob, fileName: string): void {
        const a = document.createElement('a');
        document.body.appendChild(a);
        a.setAttribute('style', 'display: none');
        blob = new Blob([blob], {type: 'application/octet-stream'});
        const url = window.URL.createObjectURL(blob);
        a.href = url;
        a.download = fileName;
        a.click();
        window.URL.revokeObjectURL(url);
        document.body.removeChild(a);
    }

    public deleteFile(fileName: string) {
        if (!this.dataField.value?.namesPaths?.find(namePath => namePath.name === fileName)) {
            return;
        }
        if (!this.taskId) {
            this._log.error('File cannot be deleted. No task is set to the field.');
            return;
        }
        const requestBody: FileFieldRequest = {
            parentTaskId: this.resolveParentTaskId(),
            fieldId: this.dataField.stringId,
            fileName
        }
        this._taskResourceService.deleteFile(this.taskId, requestBody).pipe(take(1)).subscribe((response: EventOutcomeMessageResource) => {
            if (response.success) {
                const changedFieldsMap: ChangedFieldsMap = this._eventService.parseChangedFieldsFromOutcomeTree(response.outcome);
                this.dataField.emitChangedFields(changedFieldsMap);
                this.fileUploadEl.nativeElement.value = '';
                this.uploadedFiles = this.uploadedFiles.filter(uploadedFile => uploadedFile !== fileName);
                if (this.dataField.value.namesPaths) {
                    this.dataField.value.namesPaths = this.dataField.value.namesPaths.filter(namePath => namePath.name !== fileName);
                    this.formControlRef.setValue(this.dataField.value.namesPaths.map(namePath => {
                        return namePath['name'];
                    }).join('/'));
                    this.dataField.update();
                }
                this.dataField.downloaded = this.dataField.downloaded.filter(one => one !== fileName);
                this._log.debug(`File [${this.dataField.stringId}] ${fileName} was successfully deleted`);
                this.formControlRef.markAsTouched();
            } else {
                this._log.error(`Deleting file [${this.dataField.stringId}] ${fileName} has failed!`, response.error);
                this._snackbar.openErrorSnackBar(
                    fileName + ' ' + this._translate.instant('dataField.snackBar.fileDeleteFailed')
                );
            }
        });
    }

    protected get defaultState(): FilesState {
        return {
            progress: 0,
            completed: false,
            error: false,
            uploading: false,
            downloading: false
        };
    }

    /**
     * Construct display name.
     */
    public constructDisplayName(): string {
        if (!!this.dataField && !!this.dataField.placeholder) {
            return this.dataField.placeholder;
        }
        return this._translate.instant('dataField.file.noFile');
    }

    protected parseResponse(): void {
        if (this.dataField.value) {
            if (!!this.dataField.value.namesPaths && this.dataField.value.namesPaths.length !== 0) {
                this.uploadedFiles = new Array<string>();
                this.dataField.value.namesPaths.forEach(namePath => {
                    this.uploadedFiles.push(namePath.name);
                });
            } else {
                this.dataField.value.namesPaths = new Array<FileFieldValue>();
            }
            this.uploadedFiles = this.dataField.value.namesPaths.map(namePath => namePath.name);
        }
    }
}

result-matching ""

    No results matching ""