@WidgetComponent

nm-file-upload

File

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

Implements

OnInit OnDestroy

Metadata

host {
}
selector nm-file-upload
styleUrls file-upload.component.scss
templateUrl ./file-upload.component.html

Index

Widget inputs
Widget outputs
Properties
Methods

Constructor

constructor(_halService: HalService, _notificationService: CustomNotificationService, changeDetector: ChangeDetectorRef, localStorageService: LocalStorageService, widgetframeService: WidgetframeService, appContext: AppContext, xsrfTokenExtractor: HttpXsrfTokenExtractor)
Parameters :
Name Type Optional
_halService HalService no
_notificationService CustomNotificationService no
changeDetector ChangeDetectorRef no
localStorageService LocalStorageService no
widgetframeService WidgetframeService no
appContext AppContext no
xsrfTokenExtractor HttpXsrfTokenExtractor no

Methods

Protected configureWidget
configureWidget(configuration: WidgetConfig)
Decorators : WidgetConfigure
Parameters :
Name Type Optional
configuration WidgetConfig<FileUploadConfiguration> no
Returns : void
dragenter
dragenter(event: )
Parameters :
Name Optional
event no
Returns : void
dragleave
dragleave(event: )
Parameters :
Name Optional
event no
Returns : void
Public fileOverBase
fileOverBase(e: any)
Parameters :
Name Type Optional
e any no
Returns : void
ngOnDestroy
ngOnDestroy()
Returns : void
ngOnInit
ngOnInit()
Returns : void
onError
onError(item: any, response: any)
Parameters :
Name Type Optional
item any no
response any no
Returns : void
onSubmit
onSubmit()
Returns : void
setContainerHeight
setContainerHeight()
Returns : void
validate
validate(file: any)
Parameters :
Name Type Optional
file any no
Returns : true | "filetype" | "filesize"

Properties

Public _containerHeight
_containerHeight: string
Type : string
Public action
action:
Public clear
clear: Subject<void>
Type : Subject<void>
Default value : new ReplaySubject<void>(1)
Decorators : WidgetInput
Public configuration
configuration: WidgetConfig<FileUploadConfiguration>
Type : WidgetConfig<FileUploadConfiguration>
Decorators : WidgetConfiguration
Public done
done: FileUploadInfo[]
Type : FileUploadInfo[]
Default value : []
Public doneLabel
doneLabel: string
Type : string
Public dropZoneText
dropZoneText: string
Type : string
Public error
error: FileUploadInfo[]
Type : FileUploadInfo[]
Default value : []
Public errorLabel
errorLabel: string
Type : string
Private fileSizeLimit
fileSizeLimit:
Private finishUri
finishUri:
Public hasBaseDropZoneOver
hasBaseDropZoneOver: Boolean
Type : Boolean
Default value : false
Public localStorageDone
localStorageDone: LocalStorageEntry
Type : LocalStorageEntry
Public localStorageError
localStorageError: LocalStorageEntry
Type : LocalStorageEntry
Public queuesize
queuesize: Subject<any>
Type : Subject<any>
Default value : new Subject<any>()
Decorators : WidgetOutput
Private removeUri
removeUri:
Private taskUri
taskUri:
Public taskUriSubject
taskUriSubject: Subject<void>
Type : Subject<void>
Default value : new ReplaySubject<void>(1)
Decorators : WidgetInput
Private unsubscribe
unsubscribe:
Default value : NgUnsubscribe.create()
Public upload
upload: Subject<void>
Type : Subject<void>
Default value : new ReplaySubject<void>(1)
Decorators : WidgetInput
Public uploader
uploader: FileUploader
Type : FileUploader
Public uploadFinished
uploadFinished:
Default value : new Subject<any>()
Decorators : WidgetOutput
Public uploadLabel
uploadLabel: string
Type : string
Private uploadSystem
uploadSystem: any
Type : any
Default value : ""
Private uploadUri
uploadUri:

Accessors

containerHeight
getcontainerHeight()
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();
  }
}
<div class="nm-fileUpload" [style.height]="containerHeight">
  <nm-help-icon
    class="nm-help-icon"
    *ngIf="configuration.configuration.infoText"
    [info-title]="configuration.configuration.infoTitle"
    [info-placement]="'bottom'"
    [info-text]="configuration.configuration.infoText"
  >
  </nm-help-icon>

  <div
    id="drop"
    class="file-drop-upload"
    ng2FileDrop
    [ngClass]="{ 'nv-file-over': hasBaseDropZoneOver }"
    (dragenter)="dragenter($event)"
    (dragleave)="dragleave($event)"
    [uploader]="uploader"
  >
    <mat-icon svgIcon="upload"></mat-icon>
    <div id="drop-info-text">{{ dropZoneText | translate }}</div>
  </div>

  <input
    id="hidden-file-upload"
    type="file"
    ng2FileSelect
    [uploader]="uploader"
    multiple
    style="display: none"
  />

  <div class="file-history">
    <mat-accordion multiple="true">
      <mat-expansion-panel [disabled]="uploader?.queue?.length === 0">
        <mat-expansion-panel-header>
          <mat-panel-title>
            <mat-icon
              class="file-upload-running"
              svgIcon="progress-upload"
            ></mat-icon>
          </mat-panel-title>
          <mat-panel-description>
            {{ uploadLabel | translate }} ({{ uploader?.queue?.length }})
          </mat-panel-description>
        </mat-expansion-panel-header>

        <div class="file-upload-list">
          <div
            class="file-upload-list-item"
            *ngFor="let item of uploader.queue"
          >
            <div class="file-upload-image">
              <img src="" preview [image]="item?._file" />
            </div>
            <div class="file-upload-info">
              <span class="file-upload-title">
                <nm-ellipsis
                  width="185px"
                  [content]="item?.file?.name"
                ></nm-ellipsis
              ></span>
              <mat-card-subtitle matLine class="file-upload-size"
                >{{ item?.file?.size / 1024 / 1024 | number: ".2" }}MB
              </mat-card-subtitle>
              <div class="file-upload-state">
                <div class="file-upload-progress" *ngIf="item.isUploading">
                  <mat-card-subtitle matLine class="nm-progressbar-container">
                    <mat-progress-bar
                      [value]="item.progress"
                      [mode]="'determinate'"
                    ></mat-progress-bar>
                    <span class="nm-progressbar-label">
                      {{ item.progress }} %
                    </span>
                  </mat-card-subtitle>
                </div>
              </div>
            </div>
          </div>
        </div>
      </mat-expansion-panel>

      <mat-expansion-panel [disabled]="done.length === 0">
        <mat-expansion-panel-header>
          <mat-panel-title>
            <mat-icon class="file-upload-done">done</mat-icon>
          </mat-panel-title>
          <mat-panel-description>
            {{ doneLabel | translate }} ({{ done.length }})
          </mat-panel-description>
        </mat-expansion-panel-header>

        <div class="file-upload-list">
          <div class="file-upload-list-item" *ngFor="let item of done">
            <div class="file-upload-image" *ngIf="item.uri !== 'hotfolder'">
              <nm-file-preview [item]="item"></nm-file-preview>
            </div>
            <div class="file-upload-hotfolder" *ngIf="item.uri === 'hotfolder'">
              <mat-icon
                [matTooltip]="item.error | translate"
                matTooltipPosition="left"
                >create_new_folder
              </mat-icon>
            </div>
            <div class="file-upload-info">
              <span class="file-upload-title"
                ><nm-ellipsis
                  width="185px"
                  [content]="item?.title"
                ></nm-ellipsis
              ></span>
              <mat-card-subtitle matLine class="file-upload-creationdate">{{
                item.creationDate
              }}</mat-card-subtitle>
              <mat-card-subtitle matLine class="file-upload-size"
                >{{ item.size / 1024 / 1024 | number: ".2" }}MB
              </mat-card-subtitle>
            </div>
          </div>
        </div>
      </mat-expansion-panel>

      <mat-expansion-panel [disabled]="error.length === 0">
        <mat-expansion-panel-header>
          <mat-panel-title>
            <mat-icon
              class="file-upload-error"
              svgIcon="close-circle"
            ></mat-icon>
          </mat-panel-title>
          <mat-panel-description>
            {{ errorLabel | translate }} ({{ error.length }})
          </mat-panel-description>
        </mat-expansion-panel-header>

        <div class="file-upload-list">
          <div class="file-upload-list-item" *ngFor="let item of error">
            <div class="file-upload-error">
              <mat-icon
                [matTooltip]="item.error | translate"
                matTooltipPosition="left"
                matTooltipClass="upload-error"
                >error
              </mat-icon>
            </div>
            <div class="file-upload-info">
              <span class="file-upload-title"
                ><nm-ellipsis
                  width="185px"
                  [content]="item?.title"
                ></nm-ellipsis
              ></span>
              <mat-card-subtitle matLine class="file-upload-creationdate">{{
                item.creationDate | date: "dd.MM.yyyy HH:mm:ss"
              }}</mat-card-subtitle>
              <mat-card-subtitle matLine class="file-upload-size"
                >{{ item.size / 1024 / 1024 | number: ".2" }}MB
              </mat-card-subtitle>
            </div>
          </div>
        </div>
      </mat-expansion-panel>
    </mat-accordion>
  </div>
</div>
Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""