ngx-file-drop
Version:
Angular ngx-file-drop - Simple desktop file and folder drag and drop
368 lines (361 loc) • 19.8 kB
JavaScript
import * as i0 from '@angular/core';
import { Directive, EventEmitter, TemplateRef, Component, Input, Output, ContentChild, ViewChild, NgModule } from '@angular/core';
import { timer } from 'rxjs';
import * as i1 from '@angular/common';
import { CommonModule } from '@angular/common';
/**
* fileEntry is an instance of {@link FileSystemFileEntry} or {@link FileSystemDirectoryEntry}.
* Which one is it can be checked using {@link FileSystemEntry.isFile} or {@link FileSystemEntry.isDirectory}
* properties of the given {@link FileSystemEntry}.
*/
class NgxFileDropEntry {
constructor(relativePath, fileEntry) {
this.relativePath = relativePath;
this.fileEntry = fileEntry;
}
}
class NgxFileDropContentTemplateDirective {
constructor(template) {
this.template = template;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropContentTemplateDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive }); }
static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "16.1.1", type: NgxFileDropContentTemplateDirective, selector: "[ngx-file-drop-content-tmp]", ngImport: i0 }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropContentTemplateDirective, decorators: [{
type: Directive,
args: [{ selector: '[ngx-file-drop-content-tmp]' }]
}], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } });
class NgxFileDropComponent {
get disabled() { return this._disabled; }
set disabled(value) {
this._disabled = (value != null && `${value}` !== 'false');
}
constructor(zone, renderer) {
this.zone = zone;
this.renderer = renderer;
this.accept = '*';
this.directory = false;
this.multiple = true;
this.dropZoneLabel = '';
this.dropZoneClassName = 'ngx-file-drop__drop-zone';
this.useDragEnter = false;
this.contentClassName = 'ngx-file-drop__content';
this.showBrowseBtn = false;
this.browseBtnClassName = 'btn btn-primary btn-xs ngx-file-drop__browse-btn';
this.browseBtnLabel = 'Browse files';
this.onFileDrop = new EventEmitter();
this.onFileOver = new EventEmitter();
this.onFileLeave = new EventEmitter();
this.isDraggingOverDropZone = false;
this.globalDraggingInProgress = false;
this.files = [];
this.numOfActiveReadEntries = 0;
this.helperFormEl = null;
this.fileInputPlaceholderEl = null;
this.dropEventTimerSubscription = null;
this._disabled = false;
this.openFileSelector = (event) => {
if (this.fileSelector && this.fileSelector.nativeElement) {
this.fileSelector.nativeElement.click();
}
};
this.globalDragStartListener = this.renderer.listen('document', 'dragstart', (evt) => {
this.globalDraggingInProgress = true;
});
this.globalDragEndListener = this.renderer.listen('document', 'dragend', (evt) => {
this.globalDraggingInProgress = false;
});
}
ngOnDestroy() {
if (this.dropEventTimerSubscription) {
this.dropEventTimerSubscription.unsubscribe();
this.dropEventTimerSubscription = null;
}
this.globalDragStartListener();
this.globalDragEndListener();
this.files = [];
this.helperFormEl = null;
this.fileInputPlaceholderEl = null;
}
onDragOver(event) {
if (this.useDragEnter) {
this.preventAndStop(event);
if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy';
}
}
else if (!this.isDropzoneDisabled() && !this.useDragEnter && event.dataTransfer) {
if (!this.isDraggingOverDropZone) {
this.isDraggingOverDropZone = true;
this.onFileOver.emit(event);
}
this.preventAndStop(event);
event.dataTransfer.dropEffect = 'copy';
}
}
onDragEnter(event) {
if (!this.isDropzoneDisabled() && this.useDragEnter) {
if (!this.isDraggingOverDropZone) {
this.isDraggingOverDropZone = true;
this.onFileOver.emit(event);
}
this.preventAndStop(event);
}
}
onDragLeave(event) {
if (!this.isDropzoneDisabled()) {
if (this.isDraggingOverDropZone) {
this.isDraggingOverDropZone = false;
this.onFileLeave.emit(event);
}
this.preventAndStop(event);
}
}
dropFiles(event) {
if (this.isDropzoneDisabled()) {
return;
}
this.isDraggingOverDropZone = false;
if (event.dataTransfer) {
let items;
if (event.dataTransfer.items) {
items = event.dataTransfer.items;
}
else {
items = event.dataTransfer.files;
}
this.preventAndStop(event);
this.checkFiles(items);
}
}
/**
* Processes the change event of the file input and adds the given files.
* @param Event event
*/
uploadFiles(event) {
if (this.isDropzoneDisabled()) {
return;
}
if (event.target) {
const items = event.target.files || [];
this.checkFiles(items);
this.resetFileInput();
}
}
getFakeDropEntry(file) {
const fakeFileEntry = {
name: file.name,
isDirectory: false,
isFile: true,
file: (callback) => callback(file),
};
return new NgxFileDropEntry(fakeFileEntry.name, fakeFileEntry);
}
checkFile(item) {
if (!item) {
return;
}
// if ("getAsFile" in item) {
// const file = item.getAsFile();
// if (file) {
// this.addToQueue(
// this.getFakeDropEntry(file)
// );
// return;
// }
// }
if ("webkitGetAsEntry" in item) {
let entry = item.webkitGetAsEntry();
if (entry) {
if (entry.isFile) {
const toUpload = new NgxFileDropEntry(entry.name, entry);
this.addToQueue(toUpload);
}
else if (entry.isDirectory) {
this.traverseFileTree(entry, entry.name);
}
return;
}
}
this.addToQueue(this.getFakeDropEntry(item));
}
checkFiles(items) {
for (let i = 0; i < items.length; i++) {
this.checkFile(items[i]);
}
if (this.dropEventTimerSubscription) {
this.dropEventTimerSubscription.unsubscribe();
}
this.dropEventTimerSubscription = timer(200, 200)
.subscribe(() => {
if (this.files.length > 0 && this.numOfActiveReadEntries === 0) {
const files = this.files;
this.files = [];
this.onFileDrop.emit(files);
}
});
}
traverseFileTree(item, path) {
if (item.isFile) {
const toUpload = new NgxFileDropEntry(path, item);
this.files.push(toUpload);
}
else {
path = path + '/';
const dirReader = item.createReader();
let entries = [];
const readEntries = () => {
this.numOfActiveReadEntries++;
dirReader.readEntries((result) => {
if (!result.length) {
// add empty folders
if (entries.length === 0) {
const toUpload = new NgxFileDropEntry(path, item);
this.zone.run(() => {
this.addToQueue(toUpload);
});
}
else {
for (let i = 0; i < entries.length; i++) {
this.zone.run(() => {
this.traverseFileTree(entries[i], path + entries[i].name);
});
}
}
}
else {
// continue with the reading
entries = entries.concat(result);
readEntries();
}
this.numOfActiveReadEntries--;
});
};
readEntries();
}
}
/**
* Clears any added files from the file input element so the same file can subsequently be added multiple times.
*/
resetFileInput() {
if (this.fileSelector && this.fileSelector.nativeElement) {
const fileInputEl = this.fileSelector.nativeElement;
const fileInputContainerEl = fileInputEl.parentElement;
const helperFormEl = this.getHelperFormElement();
const fileInputPlaceholderEl = this.getFileInputPlaceholderElement();
// Just a quick check so we do not mess up the DOM (will never happen though).
if (fileInputContainerEl !== helperFormEl) {
// Insert the form input placeholder in the DOM before the form input element.
this.renderer.insertBefore(fileInputContainerEl, fileInputPlaceholderEl, fileInputEl);
// Add the form input as child of the temporary form element, removing the form input from the DOM.
this.renderer.appendChild(helperFormEl, fileInputEl);
// Reset the form, thus clearing the input element of any files.
helperFormEl.reset();
// Add the file input back to the DOM in place of the file input placeholder element.
this.renderer.insertBefore(fileInputContainerEl, fileInputEl, fileInputPlaceholderEl);
// Remove the input placeholder from the DOM
this.renderer.removeChild(fileInputContainerEl, fileInputPlaceholderEl);
}
}
}
/**
* Get a cached HTML form element as a helper element to clear the file input element.
*/
getHelperFormElement() {
if (!this.helperFormEl) {
this.helperFormEl = this.renderer.createElement('form');
}
return this.helperFormEl;
}
/**
* Get a cached HTML div element to be used as placeholder for the file input element when clearing said element.
*/
getFileInputPlaceholderElement() {
if (!this.fileInputPlaceholderEl) {
this.fileInputPlaceholderEl = this.renderer.createElement('div');
}
return this.fileInputPlaceholderEl;
}
isDropzoneDisabled() {
return (this.globalDraggingInProgress || this.disabled);
}
addToQueue(item) {
this.files.push(item);
}
preventAndStop(event) {
event.stopPropagation();
event.preventDefault();
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropComponent, deps: [{ token: i0.NgZone }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: NgxFileDropComponent, selector: "ngx-file-drop", inputs: { accept: "accept", directory: "directory", multiple: "multiple", dropZoneLabel: "dropZoneLabel", dropZoneClassName: "dropZoneClassName", useDragEnter: "useDragEnter", contentClassName: "contentClassName", showBrowseBtn: "showBrowseBtn", browseBtnClassName: "browseBtnClassName", browseBtnLabel: "browseBtnLabel", disabled: "disabled" }, outputs: { onFileDrop: "onFileDrop", onFileOver: "onFileOver", onFileLeave: "onFileLeave" }, queries: [{ propertyName: "contentTemplate", first: true, predicate: NgxFileDropContentTemplateDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "fileSelector", first: true, predicate: ["fileSelector"], descendants: true, static: true }], ngImport: i0, template: "<div [className]=\"dropZoneClassName\"\r\n [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n (drop)=\"dropFiles($event)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\">\r\n <div [className]=\"contentClassName\">\r\n <input \r\n type=\"file\" \r\n #fileSelector \r\n [accept]=\"accept\" \r\n [attr.directory]=\"directory || undefined\" \r\n [attr.webkitdirectory]=\"directory || undefined\"\r\n [attr.mozdirectory]=\"directory || undefined\"\r\n [attr.msdirectory]=\"directory || undefined\"\r\n [attr.odirectory]=\"directory || undefined\"\r\n [multiple]=\"multiple\"\r\n (change)=\"uploadFiles($event)\" \r\n class=\"ngx-file-drop__file-input\" \r\n />\r\n\r\n <ng-template #defaultContentTemplate>\r\n <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n <div *ngIf=\"showBrowseBtn\">\r\n <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template\r\n [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n </ng-template>\r\n </div>\r\n</div>\r\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropComponent, decorators: [{
type: Component,
args: [{ selector: 'ngx-file-drop', template: "<div [className]=\"dropZoneClassName\"\r\n [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n (drop)=\"dropFiles($event)\"\r\n (dragover)=\"onDragOver($event)\"\r\n (dragenter)=\"onDragEnter($event)\"\r\n (dragleave)=\"onDragLeave($event)\">\r\n <div [className]=\"contentClassName\">\r\n <input \r\n type=\"file\" \r\n #fileSelector \r\n [accept]=\"accept\" \r\n [attr.directory]=\"directory || undefined\" \r\n [attr.webkitdirectory]=\"directory || undefined\"\r\n [attr.mozdirectory]=\"directory || undefined\"\r\n [attr.msdirectory]=\"directory || undefined\"\r\n [attr.odirectory]=\"directory || undefined\"\r\n [multiple]=\"multiple\"\r\n (change)=\"uploadFiles($event)\" \r\n class=\"ngx-file-drop__file-input\" \r\n />\r\n\r\n <ng-template #defaultContentTemplate>\r\n <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n <div *ngIf=\"showBrowseBtn\">\r\n <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template\r\n [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n </ng-template>\r\n </div>\r\n</div>\r\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"] }]
}], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.Renderer2 }]; }, propDecorators: { accept: [{
type: Input
}], directory: [{
type: Input
}], multiple: [{
type: Input
}], dropZoneLabel: [{
type: Input
}], dropZoneClassName: [{
type: Input
}], useDragEnter: [{
type: Input
}], contentClassName: [{
type: Input
}], showBrowseBtn: [{
type: Input
}], browseBtnClassName: [{
type: Input
}], browseBtnLabel: [{
type: Input
}], onFileDrop: [{
type: Output
}], onFileOver: [{
type: Output
}], onFileLeave: [{
type: Output
}], contentTemplate: [{
type: ContentChild,
args: [NgxFileDropContentTemplateDirective, { read: TemplateRef }]
}], fileSelector: [{
type: ViewChild,
args: ['fileSelector', { static: true }]
}], disabled: [{
type: Input
}] } });
class NgxFileDropModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropModule, bootstrap: [NgxFileDropComponent], declarations: [NgxFileDropComponent,
NgxFileDropContentTemplateDirective], imports: [CommonModule], exports: [NgxFileDropComponent,
NgxFileDropContentTemplateDirective] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropModule, imports: [CommonModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropModule, decorators: [{
type: NgModule,
args: [{
declarations: [
NgxFileDropComponent,
NgxFileDropContentTemplateDirective,
],
imports: [
CommonModule
],
exports: [
NgxFileDropComponent,
NgxFileDropContentTemplateDirective,
],
providers: [],
bootstrap: [
NgxFileDropComponent
],
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { NgxFileDropComponent, NgxFileDropContentTemplateDirective, NgxFileDropEntry, NgxFileDropModule };
//# sourceMappingURL=ngx-file-drop.mjs.map