nm-file-upload
src/app/shared/widgets/upload/file-upload.component.ts
host | { |
selector | nm-file-upload |
styleUrls | file-upload.component.scss |
templateUrl | ./file-upload.component.html |
Widget inputs |
Widget outputs |
Properties |
|
Methods |
constructor(_halService: HalService, _notificationService: CustomNotificationService, changeDetector: ChangeDetectorRef, localStorageService: LocalStorageService, widgetframeService: WidgetframeService, appContext: AppContext, xsrfTokenExtractor: HttpXsrfTokenExtractor)
|
||||||||||||||||||||||||
Parameters :
|
Protected configureWidget | ||||||
configureWidget(configuration: WidgetConfig
|
||||||
Decorators : WidgetConfigure
|
||||||
Parameters :
Returns :
void
|
dragenter | ||||
dragenter(event: )
|
||||
Parameters :
Returns :
void
|
dragleave | ||||
dragleave(event: )
|
||||
Parameters :
Returns :
void
|
Public fileOverBase | ||||||
fileOverBase(e: any)
|
||||||
Parameters :
Returns :
void
|
ngOnDestroy |
ngOnDestroy()
|
Returns :
void
|
ngOnInit |
ngOnInit()
|
Returns :
void
|
onError |
onError(item: any, response: any)
|
Returns :
void
|
onSubmit |
onSubmit()
|
Returns :
void
|
setContainerHeight |
setContainerHeight()
|
Returns :
void
|
validate | ||||||
validate(file: any)
|
||||||
Parameters :
Returns :
true | "filetype" | "filesize"
|
Public _containerHeight |
_containerHeight:
|
Type : string
|
Public action |
action:
|
Public clear |
clear:
|
Type : Subject<void>
|
Default value : new ReplaySubject<void>(1)
|
Decorators : WidgetInput
|
Public configuration |
configuration:
|
Type : WidgetConfig<FileUploadConfiguration>
|
Decorators : WidgetConfiguration
|
Public done |
done:
|
Type : FileUploadInfo[]
|
Default value : []
|
Public doneLabel |
doneLabel:
|
Type : string
|
Public dropZoneText |
dropZoneText:
|
Type : string
|
Public error |
error:
|
Type : FileUploadInfo[]
|
Default value : []
|
Public errorLabel |
errorLabel:
|
Type : string
|
Private fileSizeLimit |
fileSizeLimit:
|
Private finishUri |
finishUri:
|
Public hasBaseDropZoneOver |
hasBaseDropZoneOver:
|
Type : Boolean
|
Default value : false
|
Public localStorageDone |
localStorageDone:
|
Type : LocalStorageEntry
|
Public localStorageError |
localStorageError:
|
Type : LocalStorageEntry
|
Public queuesize |
queuesize:
|
Type : Subject<any>
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Private removeUri |
removeUri:
|
Private taskUri |
taskUri:
|
Public taskUriSubject |
taskUriSubject:
|
Type : Subject<void>
|
Default value : new ReplaySubject<void>(1)
|
Decorators : WidgetInput
|
Private unsubscribe |
unsubscribe:
|
Default value : NgUnsubscribe.create()
|
Public upload |
upload:
|
Type : Subject<void>
|
Default value : new ReplaySubject<void>(1)
|
Decorators : WidgetInput
|
Public uploader |
uploader:
|
Type : FileUploader
|
Public uploadFinished |
uploadFinished:
|
Default value : new Subject<any>()
|
Decorators : WidgetOutput
|
Public uploadLabel |
uploadLabel:
|
Type : string
|
Private uploadSystem |
uploadSystem:
|
Type : any
|
Default value : ""
|
Private uploadUri |
uploadUri:
|
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>