UNPKG

ngx-file-drop

Version:

Angular ngx-file-drop - Simple desktop file and folder drag and drop

368 lines (361 loc) 19.8 kB
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