File

src/app/shared/widgets/upload/file-upload.component.ts

Index

Properties

Properties

creationDate
creationDate: any
Type : any
error
error: any
Type : any
Optional
size
size: any
Type : any
title
title: any
Type : any
type
type: any
Type : any
uri
uri: any
Type : any
Optional
import { ReplaySubject, Subject, timer as observableTimer } from "rxjs";

import { takeUntil } from "rxjs/operators";
import {
  ChangeDetectorRef,
  Component,
  HostListener,
  Input,
  OnDestroy,
  OnInit,
} from "@angular/core";
import { FileItem, FileUploader } from "ng2-file-upload";
import { HttpXsrfTokenExtractor } from "@angular/common/http";
import {
  WidgetComponent,
  WidgetConfiguration,
  WidgetConfigure,
  WidgetInput,
  WidgetOutput,
} from "../widget.metadata";

import {
  LocalStorageEntry,
  LocalStorageService,
} from "../../../../app/shared/components/local-storage/local-storage.service";
import {
  DeletionMode,
  Scope,
} from "../../../../app/shared/components/local-storage/local-storage-constants";
import {
  getOrDefault,
  throwErrorIfUndefined,
  WidgetConfig,
} from "../../../../app/shared/widgets/widget.configuration";
import { WidgetframeService } from "../../../../app/shared/widgets/widgetframe/widgetframe.service";
import { NgUnsubscribe } from "../../../../app/shared/ng-unsubscribe";
import { AppContext } from "../../../../app/shared/components/app-context/app.context";
import { CustomNotificationService } from "../../../../app/shared/components/notification/customnotification.service";
import { HalService } from "../../../../app/shared/components/hal/hal.service";
import * as uriTemplates_ from "uri-templates";
import { UtilService } from "../../components/util";

declare var contextPath: string;
const uriTemplates = uriTemplates_;

export interface FileUploadConfiguration {
  /**
   * Prefix for local storage. This is used for the done and error list. If the prefix is empty no local storage recovery will be used
   */
  localStorageKey: string;

  /**
   * Uri of rest endpoint to create a ipim task for the upload process (required).
   */
  taskUri: string;

  /**
   * Uri of rest endpoint to upload bytes of a file (required).
   */
  uploadUri: string;

  /**
   * Uri of rest endpoint to finish one file upload (required).
   */
  finishUri: string;

  /**
   * Base uri of rest endpoint to remove file from task (required).
   * Example: "/api/apps/myapp/upload/task"
   * This base uri will be extended by the file name (base64 encoded) to sth. like this:
   * "/api/apps/myapp/upload/task/eW91cmZpbGUuanBn"
   */
  removeUri: string;

  /**
   * Max. file size in bytes
   * Example: 64000000 (for 64 MB)
   */
  fileSizeLimit?: number | string;

  /**
   * A list of allowed MIME types. If the list is empty or null, all MIME types are allowed.
   * Example: ['image/jpeg']
   */
  allowedTypes?: string[];

  /**
   * A list of forbidden MIME types. If the list is empty or null, no MIME type is forbidden.
   * Example: ['application/pdf', 'application/javascript']
   */
  forbiddenTypes?: string[];

  /**
   * Label for the upload section (queue)
   * Default: 'fileupload.panel.upload'
   */
  uploadLabel?: string;

  /**
   * Label for the done section
   * Default: 'fileupload.panel.done'
   */
  doneLabel?: string;

  /**
   * Label for the error section
   * Default: 'fileupload.panel.error'
   */
  errorLabel?: string;

  /**
   * Text for the dropzone on mouseover
   * Default: 'fileupload.overlay.text'
   */

  dropZoneText?: string;
  /**
   * The height of this component. Will be the size of the full page -180px if unset
   */
  height?: string;

  /**
   * Title of the info-dialog. Should be a translation-key
   */
  infoTitle?: string;

  /**
   * Text of the info-dialog. Should be a translation-key
   */
  infoText?: string;
}

export interface FileUploadInfo {
  title: any;
  size: any;
  uri?: any;
  creationDate: any;
  type: any;
  error?: any;
}

@WidgetComponent("nm-file-upload")
@Component({
  selector: "nm-file-upload",
  templateUrl: "./file-upload.component.html",
  styleUrls: ["./file-upload.component.scss"],
  host: {
    "(window:resize)": "setContainerHeight()",
  },
})
export class FileUploadComponentWidget implements OnInit, OnDestroy {
  @Input("visible") visible: boolean;

  @WidgetInput("clear")
  public clear: Subject<void> = new ReplaySubject<void>(1);

  @WidgetInput("upload")
  public upload: Subject<void> = new ReplaySubject<void>(1);

  @WidgetInput("taskUri")
  public taskUriSubject: Subject<void> = new ReplaySubject<void>(1);

  @WidgetOutput("queuesize")
  public queuesize: Subject<any> = new Subject<any>();

  @WidgetOutput("uploadFinished")
  public uploadFinished = new Subject<any>();

  public localStorageDone: LocalStorageEntry;
  public localStorageError: LocalStorageEntry;
  public uploader: FileUploader;
  public hasBaseDropZoneOver: Boolean = false;
  public action;
  private uploadSystem: any = "";

  @WidgetConfiguration()
  public configuration: WidgetConfig<FileUploadConfiguration>;

  public uploadLabel: string;
  public doneLabel: string;
  public errorLabel: string;
  public dropZoneText: string;

  public done: FileUploadInfo[] = [];
  public error: FileUploadInfo[] = [];
  public _containerHeight: string;

  private uploadUri;
  private taskUri;
  private finishUri;
  private removeUri;
  private fileSizeLimit;

  private unsubscribe = NgUnsubscribe.create();

  public fileOverBase(e: any): void {
    this.hasBaseDropZoneOver = e;
  }

  constructor(
    private _halService: HalService,
    private _notificationService: CustomNotificationService,
    private changeDetector: ChangeDetectorRef,
    private localStorageService: LocalStorageService,
    private widgetframeService: WidgetframeService,
    private appContext: AppContext,
    private xsrfTokenExtractor: HttpXsrfTokenExtractor
  ) {}

  @WidgetConfigure()
  protected configureWidget(
    configuration: WidgetConfig<FileUploadConfiguration>
  ) {
    this.uploadUri = throwErrorIfUndefined(
      "uploadUri",
      this.configuration.configuration
    );
    this.finishUri = throwErrorIfUndefined(
      "finishUri",
      this.configuration.configuration
    );
    this.taskUri = throwErrorIfUndefined(
      "taskUri",
      this.configuration.configuration
    );
    this.removeUri = throwErrorIfUndefined(
      "removeUri",
      this.configuration.configuration
    );

    this.uploadLabel = getOrDefault(
      this.configuration.configuration.uploadLabel,
      "fileupload.panel.upload"
    );
    this.doneLabel = getOrDefault(
      this.configuration.configuration.doneLabel,
      "fileupload.panel.done"
    );
    this.errorLabel = getOrDefault(
      this.configuration.configuration.errorLabel,
      "fileupload.panel.error"
    );
    this.dropZoneText = getOrDefault(
      this.configuration.configuration.dropZoneText,
      "fileupload.overlay.text"
    );

    if (this.configuration.configuration.localStorageKey) {
      this.localStorageDone = this.localStorageService.getLocalStorageEntry(
        this.configuration.configuration.localStorageKey + "-done",
        Scope.GLOBAL,
        DeletionMode.NEVER
      );
      this.localStorageError = this.localStorageService.getLocalStorageEntry(
        this.configuration.configuration.localStorageKey + "-error",
        Scope.GLOBAL,
        DeletionMode.NEVER
      );

      if (this.localStorageDone.exists()) {
        this.done = JSON.parse(this.localStorageDone.value);
      }

      if (this.localStorageError.exists()) {
        this.error = JSON.parse(this.localStorageError.value);
      }
    }

    const fileSizeLimit = getOrDefault(
      this.configuration.configuration.fileSizeLimit,
      null
    );
    this.fileSizeLimit = UtilService.dataSizeToBytes(fileSizeLimit);
  }

  ngOnInit() {
    this.setContainerHeight();

    this.clear
      .asObservable()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe(() => {
        this.done = [];
        this.error = [];

        if (this.localStorageDone) {
          this.localStorageDone.value = JSON.stringify(this.done);
        }
        if (this.localStorageError) {
          this.localStorageError.value = JSON.stringify(this.error);
        }

        this.changeDetector.detectChanges();
      });

    this.upload
      .asObservable()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe(() => {
        document.getElementById("hidden-file-upload").click();
      });

    this.taskUriSubject
      .asObservable()
      .pipe(takeUntil(this.unsubscribe))
      .subscribe((uri) => (this.taskUri = uri));

    this.uploader = new FileUploader({ url: this.uploadUri });

    this.uploader.onAfterAddingAll = (fileItems: any[]) => {
      let filenames = [];

      fileItems.forEach((fileItem) => {
        let result = this.validate(fileItem.file);
        if (result === true) {
          filenames.push(fileItem.file.name);
        } else {
          this.onError(fileItem, {
            title: "fileupload.validation.error",
            message: "fileupload.validation.error." + result,
            creationDate: new Date(),
          });
        }
      });

      if (filenames.length > 0) {
        this.widgetframeService
          .postData(this.taskUri, filenames)
          .subscribe((response) => {
            if (typeof Worker !== "undefined") {
              const worker = new Worker(
                contextPath + "/assets/file-upload.worker.js",
                { type: "module" }
              );

              worker.onmessage = ({ data }) => {
                if (data.type === "progress") {
                  this.uploader.onProgressItem(
                    fileItems[data.file],
                    data.progress
                  );
                } else if (data.level === "ERROR") {
                  this.uploader.onErrorItem(
                    fileItems[data.file],
                    data,
                    data.status,
                    null
                  );
                } else if (data.level === "SUCCESS") {
                  this.uploader.onSuccessItem(
                    fileItems[data.file],
                    data,
                    data.status,
                    null
                  );
                }
              };

              let files = fileItems.map((f) => f._file);

              worker.postMessage({
                xsrf: this.xsrfTokenExtractor.getToken(),
                sessionId: getOrDefault(
                  this.appContext ? this.appContext.sessionId : "",
                  ""
                ),
                files: files,
                contextPath: contextPath,
                uploadUri: this.uploadUri,
                finishUri: this.finishUri,
              });
            } else {
              this.uploader.uploadAll();
              this.queuesize.next(this.uploader.queue.length);
            }
          });
      }
    };

    this.uploader.onAfterAddingFile = (fileItem: any) => {
      this.fileOverBase(false);
      fileItem.creationDate = Date.now();
    };

    this.uploader.onSuccessItem = (
      item: any,
      response: any,
      status: any,
      headers: any
    ) => {
      item.isUploading = false;
      this.uploader.removeFromQueue(item);

      if (response) {
        this.uploadFinished.next({ item, response, headers });
        let responseObject = response;
        let uri = responseObject.uri;

        //If this is an absolute uri (no leading /) than take the full uri else add contextpath
        if (uri !== "hotfolder" && responseObject.uri[0] === "/") {
          uri = contextPath + responseObject.uri;
        }

        this.done.unshift({
          title: responseObject.fileName,
          size: responseObject.fileSize,
          uri: uri,
          creationDate: responseObject.creationDate,
          error: responseObject.message,
          type: item.file.type,
        });
      }

      let timer = observableTimer(1);
      timer.subscribe((t) => {
        this.changeDetector.detectChanges();
      });

      if (this.localStorageDone) {
        this.localStorageDone.value = JSON.stringify(this.done);
      }
      this.queuesize.next(this.uploader.queue.length);
    };

    this.uploader.onErrorItem = (
      item: any,
      response: any,
      status: any,
      headers: any
    ) => {
      item.isError = true;

      if (status === 500 || status === 0) {
        let uri = this.removeUri + "/" + btoa(item.file.name);
        this.widgetframeService.deleteData(uri).subscribe((response) => {
          this.onError(item, response);
        });
      } else {
        this.onError(item, response);
      }
    };

    this.uploader.onProgressItem = (fileItem: FileItem, progress: any) => {
      fileItem.progress = progress;
      fileItem.isUploading = true;
      this.changeDetector.detectChanges();
    };
  }

  onError(item: any, response: any) {
    item.isUploading = false;
    this.uploader.removeFromQueue(item);

    this.error.unshift({
      title: item.file.name,
      size: item.file.size,
      creationDate: item.creationDate,
      error: response.message,
      type: item.file.type,
    });

    let timer = observableTimer(1);
    timer.subscribe((t) => {
      this.changeDetector.detectChanges();
    });

    if (this.localStorageError) {
      this.localStorageError.value = JSON.stringify(this.error);
    }

    this.queuesize.next(this.uploader.queue.length);
  }

  validate(file: any) {
    const type = file.type;
    const size = file.size;

    if (
      this.configuration.configuration.allowedTypes &&
      this.configuration.configuration.allowedTypes.length > 0 &&
      this.configuration.configuration.allowedTypes.indexOf(type) === -1
    ) {
      return "filetype";
    }

    if (
      this.configuration.configuration.forbiddenTypes &&
      this.configuration.configuration.forbiddenTypes.length > 0 &&
      this.configuration.configuration.forbiddenTypes.indexOf(type) !== -1
    ) {
      return "filetype";
    }

    if (this.fileSizeLimit && size > this.fileSizeLimit) {
      return "filesize";
    }

    return true;
  }

  onSubmit() {
    event.preventDefault();
  }

  setContainerHeight() {
    if (this.configuration.configuration.height) {
      this._containerHeight = this.configuration.configuration.height;
      return;
    }
    let height =
      window.innerHeight ||
      document.documentElement.clientHeight ||
      document.body.clientHeight;
    this._containerHeight = String(height - 180) + "px";
  }

  dragenter(event) {
    if (event.target.className === "file-drop-upload") {
      event.stopImmediatePropagation();
      event.stopPropagation();
      this.fileOverBase(true);
    }
  }

  dragleave(event) {
    if (event.target.className === "file-drop-upload nv-file-over") {
      event.stopImmediatePropagation();
      event.stopPropagation();
      this.fileOverBase(false);
    }
  }

  get containerHeight() {
    return this._containerHeight;
  }

  @HostListener("window:beforeunload", ["$event"])
  onUnload($event: any) {
    if (this.uploader.queue.length > 0) {
      $event.returnValue = true;
    }
  }

  ngOnDestroy(): void {
    this.unsubscribe.destroy();
  }
}

results matching ""

    No results matching ""