Ontica/Empiria.Land.Intranet

View on GitHub
src/app/shared/form-controls/file-control/file-control.component.ts

Summary

Maintainability
B
4 hrs
Test Coverage
/**
 * @license
 * Copyright (c) La Vía Óntica SC, Ontica LLC and contributors. All rights reserved.
 *
 * See LICENSE.txt in the project root for complete license information.
 */

import { Component, Output, EventEmitter, Input, OnChanges, SimpleChanges } from '@angular/core';

import { FileDownloadService } from '@app/data-services/file-services/file-download.service';

import { FormatLibrary } from '@app/shared/utils';

import { DefaultFileControlConfig, FileData, FileControlActions, FileControlConfig, FileControlMenuOptions,
         FileTypeAccepted, FileType, CsvFileTypeException, FileControlEventData } from './file-control-data';

@Component({
  selector: 'emp-ng-file-control',
  templateUrl: './file-control.component.html',
  styleUrls: ['./file-control.component.scss'],
})
export class FileControlComponent implements OnChanges {

  @Input() fileControl: FileData[] | FileData | null = null;

  @Output() fileControlChange = new EventEmitter<FileData[] | FileData | null>();

  @Output() fileControlEvent = new EventEmitter<FileControlEventData>();

  @Input() readonly = false;

  @Input() disabled = false;

  @Input() showError = false;

  @Input()
  get config() {
    return this.fileControlConfig;
  }
  set config(value: FileControlConfig) {
    this.fileControlConfig = Object.assign({}, DefaultFileControlConfig, value);
  }

  fileControlConfig = DefaultFileControlConfig;

  filesToUpload: FileData[] = [];

  acceptedFilesTypes: FileTypeAccepted[];

  acceptedFileString = '*';

  idFileControl: string = 'idFile' + Math.random().toString(16).slice(2);

  tagFileSelected = null;

  constructor(private fileDownload: FileDownloadService) {}

  ngOnChanges(changes: SimpleChanges) {
    if (changes.config) {
      this.setAcceptedFilesTypes();

      if (changes.config.previousValue?.tagDefault !== changes.config.currentValue?.tagDefault) {
        this.setTagFile();
      }
    }

    if (changes.fileControl) {
      this.setFilesSaved();
    }
  }

  handleFileInput(fileInput) {
    const fileList: FileList = fileInput.files;

    if (!fileList || fileList.length === 0 || this.readonly ||
        this.filesToUpload.length >= this.fileControlConfig.maxFiles) {
      return;
    }

    const oldFiles = this.filesToUpload ?? [];

    const filesArray = Array.from(fileList);

    const filesByAcceptedType: File[] = filesArray.filter(f => this.validateFileType(f));

    const newFiles: FileData[] = this.mapFileDataArrayFromFileArray(filesByAcceptedType);

    const allFiles = [...oldFiles, ...newFiles];

    this.filesToUpload = allFiles.length > this.fileControlConfig.maxFiles ?
      allFiles.slice(0, this.fileControlConfig.maxFiles) : allFiles;

    this.emitFiles();

    fileInput.value = '';
  }

  onClickFileOptions(file: FileData, option: FileControlActions) {
    if ('CANCEL' === option) {
      this.removeFile(file);
    }
    if ('SHOW' === option && !this.validateShowOption(file.type)) {
      return;
    }
    if ('DOWNLOAD' === option) {
      this.downloadFile(file.url);
    }
    this.fileControlEvent.emit({ option, file });
  }

  onDragOver(event: DragEvent): void {
    event.preventDefault();
    event.dataTransfer.dropEffect = 'copy';
    event.stopPropagation();
  }

  onDropFile(event: DragEvent): void {
    event.preventDefault();
    this.handleFileInput(event.dataTransfer);
    event.stopPropagation();
  }

  onDownloadTemplateFile() {
    if (!this.tagFileSelected?.templateUrl) {
      return;
    }
    this.downloadFile(this.tagFileSelected.templateUrl);
  }

  formatBytes(sizeBytes) {
    return FormatLibrary.formatBytes(sizeBytes);
  }

  resetFileControl() {
    this.clearFileControl();
  }

  private setAcceptedFilesTypes() {
    this.acceptedFilesTypes = [];

    if (this.isFileTypeInConfig('all')) {
      this.validateAndSetAcceptedFileType('all', FileTypeAccepted.all);
    } else {
      this.validateAndSetAcceptedFileType('pdf', FileTypeAccepted.pdf);
      this.validateAndSetAcceptedFileType('excel', FileTypeAccepted.excel);
      this.validateAndSetAcceptedFileType('csv', FileTypeAccepted.csv);
      this.validateAndSetAcceptedFileType('image', FileTypeAccepted.image);
      this.validateAndSetAcceptedFileType('txt', FileTypeAccepted.txt);
    }

    this.acceptedFileString = this.acceptedFilesTypes.toString();
  }

  private isFileTypeInConfig(type: FileType): boolean {
    if (this.fileControlConfig.filesTypes.filter(x => x === type).length > 0) {
      return true;
    }
    return false;
  }

  private validateAndSetAcceptedFileType(type: FileType, accepted: FileTypeAccepted) {
    if (this.isFileTypeInConfig(type)) {
      this.acceptedFilesTypes = [...this.acceptedFilesTypes, ...[accepted]];
    }
  }

  private setFilesSaved() {
    if (this.fileControl) {
      this.filesToUpload = this.fileControl instanceof Array ? this.fileControl : [this.fileControl];
      this.filesToUpload.forEach(file => {
        file.menuOptions = this.getMenuOptions(file);
        file.sizeString = this.formatBytes(file.size);
        file.fileIcon = this.getFileIcon(file.type);
      });
      return;
    }
    this.filesToUpload = [];
  }

  private setTagFile() {
    if (this.fileControlConfig.tagRequired) {
      this.tagFileSelected =
        this.fileControlConfig.tagsList.find(x => x.type === this.fileControlConfig.tagDefault);
    } else {
      this.tagFileSelected = null;
    }
  }

  private getMenuOptions(file: FileData): FileControlMenuOptions[] {
    if (file.uid) {
      const options: FileControlMenuOptions[] = [];

      if (this.validateShowOption(file.type)) {
        options.push({ name: 'Ver', action: 'SHOW', icon: 'visibility' });
      }

      if (!!file.url) {
        options.push({ name: 'Descargar', action: 'DOWNLOAD', icon: 'file_download' });
      }

      if (!this.readonly) {
        options.push({ name: 'Eliminar', action: 'REMOVE', icon: 'delete' });
      }

      return options;
    }
    return [
      { name: 'Guardar', action: 'SAVE', icon: 'save' },
      { name: 'Cancelar', action: 'CANCEL', icon: 'clear' },
    ];
  }

  private validateShowOption(type: string): boolean {
    return type === FileTypeAccepted.pdf || type.startsWith('image/');
  }

  private getFileIcon(type: string) {
    if (type === FileTypeAccepted.pdf) {
      return 'emp-pdf-file';
    }

    if (FileTypeAccepted.excel.includes(type) || FileTypeAccepted.csv.includes(type) ||
        CsvFileTypeException === type) {
      return 'emp-xls-file';
    }

    if (type.startsWith('image/')) {
      return 'emp-image-file';
    }

    return 'emp-file';
  }

  private validateFileType(file: File) {
    if (this.isFileTypeInConfig('all')) {
      return true;
    }

    if (file.type === FileTypeAccepted.pdf) {
      return this.isValidFileType('pdf', file);
    }

    if (FileTypeAccepted.excel.includes(file.type)) {
      return this.isValidFileType('excel', file);
    }

    if (FileTypeAccepted.csv.includes(file.type) || CsvFileTypeException === file.type) {
      return this.isValidFileType('csv', file);
    }

    if (file.type.startsWith('image/')) {
      return this.isValidFileType('image', file);
    }

    if (file.type === FileTypeAccepted.txt) {
      return this.isValidFileType('txt', file);
    }

    return false;
  }

  private isValidFileType(type: FileType, file: File) {
    if (this.isFileTypeInConfig(type)) {
      return true;
    }

    console.log(`Invalid format: ${file.type}. File: ${file.name}. Is required: ${this.acceptedFileString}.`);
    return false;
  }

  private mapFileDataArrayFromFileArray(fileList: File[]): FileData[] {
    const files: FileData[] = [];
    fileList.forEach(item => files.push(this.mapFileDataFromFile(item)));
    return files;
  }

  private mapFileDataFromFile(file: File): FileData {
    return {
      file,
      name: this.getFileName(file.name),
      type: file.type,
      size: file.size,
      sizeString: FormatLibrary.formatBytes(file.size),
      tag: this.tagFileSelected?.type ?? '',
      fileIcon: this.getFileIcon(file.type),
      menuOptions: this.getMenuOptions(file)
    };
  }

  private getFileName(name: string) {
    return this.fileControlConfig.fileName ?
      this.fileControlConfig.fileName + this.getFileNameExtension(name) :
      name;
  }

  private getFileNameExtension(name: string) {
    const strings = name.split('.');
    return strings.length > 1 ? '.' + strings[strings.length - 1] : '';
  }

  private emitFiles() {
    let files: FileData | FileData[] = this.filesToUpload;

    if (this.fileControlConfig.maxFiles === 1) {
      files = this.filesToUpload.length >= 1 ? this.filesToUpload[0] : null;
    }

    this.fileControlChange.emit(files);
  }

  private downloadFile(url: string) {
    this.fileDownload.download(url);
  }

  private removeFile(file) {
    this.filesToUpload = this.filesToUpload.filter(f => f !== file);
    this.emitFiles();
  }

  private clearFileControl() {
    this.filesToUpload = [];
    this.emitFiles();
  }

}