diff --git a/src/modules/fileattachments/file-drop.component.spec.ts b/src/modules/fileattachments/file-drop.component.spec.ts index ebd818ac7..2472b774e 100644 --- a/src/modules/fileattachments/file-drop.component.spec.ts +++ b/src/modules/fileattachments/file-drop.component.spec.ts @@ -199,10 +199,10 @@ describe('File drop component', () => { if (fileReaderSpy.loadCallbacks[0]) { fileReaderSpy.loadCallbacks[0]({ - target: { - result: 'url' - } - }); + target: { + result: 'url' + } + }); } if (fileReaderSpy.loadCallbacks[1]) { @@ -363,7 +363,7 @@ describe('File drop component', () => { componentInstance.validateFn = function(file: any) { if (file.file.name.indexOf('w') === 0) { - return errorMessage; + return errorMessage; } }; @@ -407,7 +407,7 @@ describe('File drop component', () => { expect(filesChangedActual.files[0].file.size).toBe(1000); }); - it('should reject a file with no type when accepted types are defined', () => { + it('should reject a file with no type when accepted types are defined', () => { let filesChangedActual: SkyFileDropChange; componentInstance.filesChanged.subscribe( @@ -418,16 +418,16 @@ describe('File drop component', () => { fixture.detectChanges(); let files = [ - { - name: 'foo.txt', - size: 1000 - }, - { - name: 'woo.txt', - size: 2000, - type: 'image/jpeg' - } - ]; + { + name: 'foo.txt', + size: 1000 + }, + { + name: 'woo.txt', + size: 2000, + type: 'image/jpeg' + } + ]; setupStandardFileChangeEvent(files); @@ -493,6 +493,7 @@ describe('File drop component', () => { let dragOverEvent = { dataTransfer: { + files: {} as any, items: files }, stopPropagation: function () { @@ -516,14 +517,13 @@ describe('File drop component', () => { let dropEvent = { dataTransfer: { - files: - { - length: fileLength, - item: function (index: number) { - return files[index]; - } - }, - items: files + files: { + length: fileLength, + item: function (index: number) { + return files[index]; + } + }, + items: files }, stopPropagation: function () { dropPropStopped = true; @@ -564,20 +564,20 @@ describe('File drop component', () => { let dropDebugEl = getDropDebugEl(); let files = [ - { - name: 'foo.txt', - size: 1000, - type: 'image/png' - } - ]; + { + name: 'foo.txt', + size: 1000, + type: 'image/png' + } + ]; let invalidFiles = [ - { - name: 'foo.txt', - size: 1000, - type: 'image/jpeg' - } - ]; + { + name: 'foo.txt', + size: 1000, + type: 'image/jpeg' + } + ]; triggerDragEnter('sky-drop', dropDebugEl); triggerDragOver(files, dropDebugEl); @@ -588,6 +588,7 @@ describe('File drop component', () => { triggerDrop(files, dropDebugEl); validateDropClasses(false, false, dropElWrapper); + fileReaderSpy.loadCallbacks[0]({ target: { result: 'url' @@ -597,7 +598,6 @@ describe('File drop component', () => { fixture.detectChanges(); expect(filesChangedActual.rejectedFiles.length).toBe(0); - expect(filesChangedActual.files.length).toBe(1); expect(filesChangedActual.files[0].url).toBe('url'); expect(filesChangedActual.files[0].file.name).toBe('foo.txt'); @@ -618,7 +618,7 @@ describe('File drop component', () => { validateDropClasses(false, false, dropElWrapper); let emptyEvent = { - stopPropagation: function (){}, + stopPropagation: function () {}, preventDefault: function () {} }; @@ -635,66 +635,99 @@ describe('File drop component', () => { }); + it([ + 'should accept a file of rejected type on drag (but not on drop)', + 'if the browser does not support dataTransfer.items' + ].join(' '), () => { + let filesChangedActual: SkyFileDropChange; + + componentInstance.filesChanged.subscribe( + (filesChanged: SkyFileDropChange) => filesChangedActual = filesChanged ); + + componentInstance.acceptedTypes = 'image/png, image/tiff'; + + fixture.detectChanges(); + + let dropDebugEl = getDropDebugEl(); + + let invalidFiles = [ + { + name: 'foo.txt', + size: 1000, + type: 'image/jpeg' + } + ]; + + let dropElWrapper = getDropElWrapper(); + + triggerDragEnter('sky-drop', dropDebugEl); + triggerDragOver(undefined, dropDebugEl); + validateDropClasses(true, false, dropElWrapper); + + triggerDrop(invalidFiles, dropDebugEl); + validateDropClasses(false, false, dropElWrapper); + }); + it('should prevent loading multiple files on drag and drop when multiple is false', () => { let files = [ - { - name: 'foo.txt', - size: 1000, - type: 'image/png' - }, - { - name: 'goo.txt', - size: 1000, - type: 'image/png' - } - ]; + { + name: 'foo.txt', + size: 1000, + type: 'image/png' + }, + { + name: 'goo.txt', + size: 1000, + type: 'image/png' + } + ]; - let filesChangedActual: SkyFileDropChange; + let filesChangedActual: SkyFileDropChange; - componentInstance.filesChanged.subscribe( - (filesChanged: SkyFileDropChange) => filesChangedActual = filesChanged ); + componentInstance.filesChanged.subscribe( + (filesChanged: SkyFileDropChange) => filesChangedActual = filesChanged ); - let fileReaderSpy = setupFileReaderSpy(); + let fileReaderSpy = setupFileReaderSpy(); - componentInstance.multiple = false; - fixture.detectChanges(); + componentInstance.multiple = false; + fixture.detectChanges(); - let dropDebugEl = getDropDebugEl(); + let dropDebugEl = getDropDebugEl(); - triggerDragEnter('sky-drop', dropDebugEl); - triggerDragOver(files, dropDebugEl); - triggerDrop(files, dropDebugEl); - expect(fileReaderSpy.loadCallbacks.length).toBe(0); + triggerDragEnter('sky-drop', dropDebugEl); + triggerDragOver(files, dropDebugEl); + triggerDrop(files, dropDebugEl); + expect(fileReaderSpy.loadCallbacks.length).toBe(0); }); it('should prevent loading directories on drag and drop', () => { let files = [ - { - name: 'foo.txt', - size: 1000, - type: 'image/png', - webkitGetAsEntry: function () { - return { - isDirectory: true - }; - } + { + name: 'foo.txt', + size: 1000, + type: 'image/png', + webkitGetAsEntry: function () { + return { + isDirectory: true + }; } - ]; + } + ]; - let filesChangedActual: SkyFileDropChange; + let filesChangedActual: SkyFileDropChange; - componentInstance.filesChanged.subscribe( - (filesChanged: SkyFileDropChange) => filesChangedActual = filesChanged ); + componentInstance.filesChanged.subscribe( + (filesChanged: SkyFileDropChange) => filesChangedActual = filesChanged ); - let fileReaderSpy = setupFileReaderSpy(); - fixture.detectChanges(); + let fileReaderSpy = setupFileReaderSpy(); + fixture.detectChanges(); - let dropDebugEl = getDropDebugEl(); + let dropDebugEl = getDropDebugEl(); - triggerDragEnter('sky-drop', dropDebugEl); - triggerDragOver(files, dropDebugEl); - triggerDrop(files, dropDebugEl); - expect(fileReaderSpy.loadCallbacks.length).toBe(0); + triggerDragEnter('sky-drop', dropDebugEl); + triggerDragOver(files, dropDebugEl); + triggerDrop(files, dropDebugEl); + expect(fileReaderSpy.loadCallbacks.length).toBe(0); }); it('should show link section when allowLinks is true', () => { diff --git a/src/modules/fileattachments/file-drop.component.ts b/src/modules/fileattachments/file-drop.component.ts index 95f0602dd..ea9f1c2c8 100644 --- a/src/modules/fileattachments/file-drop.component.ts +++ b/src/modules/fileattachments/file-drop.component.ts @@ -25,7 +25,6 @@ import { styleUrls: ['./file-drop.component.scss'] }) export class SkyFileDropComponent { - @Output() public filesChanged = new EventEmitter(); @@ -57,9 +56,7 @@ export class SkyFileDropComponent { public inputEl: ElementRef; public rejectedOver: boolean = false; - - public acceptedOver: boolean = false; - + public acceptedOver: boolean = false; public linkUrl: string; private enterEventTarget: any; @@ -74,51 +71,64 @@ export class SkyFileDropComponent { this.handleFiles(fileChangeEvent.target.files); } - public fileDragEnter(this: SkyFileDropComponent, dragEnterEvent: any) { + public fileDragEnter(dragEnterEvent: any) { // Save this target to know when the drag event leaves this.enterEventTarget = dragEnterEvent.target; dragEnterEvent.stopPropagation(); dragEnterEvent.preventDefault(); - } - public fileDragOver(this: SkyFileDropComponent, dragOverEvent: any) { + public fileDragOver(dragOverEvent: any) { + const transfer = dragOverEvent.dataTransfer; + dragOverEvent.stopPropagation(); dragOverEvent.preventDefault(); - if (dragOverEvent.dataTransfer && dragOverEvent.dataTransfer.items) { + if (transfer) { + if (transfer.items) { + const files = transfer.items; - let files = dragOverEvent.dataTransfer.items; + for (let index = 0; index < files.length; index++) { + const file: any = files[index]; - for (let index = 0; index < files.length; index++) { - let file: any = files[index]; - if (file.type && this.fileTypeRejected(file.type)) { - this.rejectedOver = true; - this.acceptedOver = false; - return; + if (file.type && this.fileTypeRejected(file.type)) { + this.rejectedOver = true; + this.acceptedOver = false; + return; + } } - } - if (files.length > 0 && !this.acceptedOver) { + + if (files.length > 0 && !this.acceptedOver) { + this.rejectedOver = false; + this.acceptedOver = true; + } + + } else if (transfer.files) { + // If the browser does not support DataTransfer.items, + // defer file-type checking to drop handler. + // https://developer.mozilla.org/en-US/docs/Web/API/DataTransfer/items#Browser_compatibility this.rejectedOver = false; this.acceptedOver = true; } } } - public fileDrop(this: SkyFileDropComponent, dropEvent: any) { - this.enterEventTarget = undefined; + public fileDrop(dropEvent: any) { dropEvent.stopPropagation(); dropEvent.preventDefault(); + + this.enterEventTarget = undefined; this.rejectedOver = false; this.acceptedOver = false; + if (dropEvent.dataTransfer && dropEvent.dataTransfer.files) { - if (this.verifyDropFiles(dropEvent.dataTransfer.items)) { + if (this.verifyDropFiles(dropEvent.dataTransfer.files)) { this.handleFiles(dropEvent.dataTransfer.files); } } } - public fileDragLeave(this: SkyFileDropComponent, dragLeaveEvent: any) { + public fileDragLeave(dragLeaveEvent: any) { if (this.enterEventTarget === dragLeaveEvent.target) { this.rejectedOver = false; this.acceptedOver = false; @@ -138,22 +148,21 @@ export class SkyFileDropComponent { } private emitFileChangeEvent( - this: SkyFileDropComponent, totalFiles: number, rejectedFileArray: Array, - validFileArray: Array) { - + validFileArray: Array + ) { if (totalFiles === rejectedFileArray.length + validFileArray.length) { this.filesChanged.emit({ files: validFileArray, rejectedFiles: rejectedFileArray } as SkyFileDropChange); + this.inputEl.nativeElement.value = ''; } } private filesRejected( - this: SkyFileDropComponent, file: SkyFileItem, validFileArray: Array, rejectedFileArray: Array, @@ -168,29 +177,23 @@ export class SkyFileDropComponent { file: SkyFileItem, validFileArray: Array, rejectedFileArray: Array, - totalFiles: number) { - - let reader = new FileReader(); + totalFiles: number + ) { + const reader = new FileReader(); - reader.addEventListener('load', - function (this: FileReader, event: any) { - file.url = event.target.result; - validFileArray.push(file); - fileDrop.emitFileChangeEvent(totalFiles, rejectedFileArray, validFileArray); - } - ); + reader.addEventListener('load', (event: any) => { + file.url = event.target.result; + validFileArray.push(file); + fileDrop.emitFileChangeEvent(totalFiles, rejectedFileArray, validFileArray); + }); - reader.addEventListener('error', - function (this: FileReader, event: any) { - fileDrop.filesRejected(file, validFileArray, rejectedFileArray, totalFiles); - } - ); + reader.addEventListener('error', (event: any) => { + fileDrop.filesRejected(file, validFileArray, rejectedFileArray, totalFiles); + }); - reader.addEventListener('abort', - function (this: FileReader, event: any) { - fileDrop.filesRejected(file, validFileArray, rejectedFileArray, totalFiles); - } - ); + reader.addEventListener('abort', (event: any) => { + fileDrop.filesRejected(file, validFileArray, rejectedFileArray, totalFiles); + }); reader.readAsDataURL(file.file); } @@ -207,15 +210,18 @@ export class SkyFileDropComponent { if (typeArray.indexOf(fileType) !== -1) { return true; } + for (let index = 0; index < typeArray.length; index++) { - let type = typeArray[index]; - let validSubtype = this.getMimeSubtype(type); + const type = typeArray[index]; + const validSubtype = this.getMimeSubtype(type); + if (validSubtype === '*') { if (this.getMimeMainType(type) === this.getMimeMainType(fileType)) { return true; } } } + return false; } @@ -230,6 +236,7 @@ export class SkyFileDropComponent { let acceptedTypesUpper = this.acceptedTypes.toUpperCase(); let typeArray = acceptedTypesUpper.split(','); + return !this.fileTypeInArray(typeArray, fileType.toUpperCase()); } @@ -237,30 +244,36 @@ export class SkyFileDropComponent { let validFileArray: Array = []; let rejectedFileArray: Array = []; let totalFiles = files.length; - let fileDrop = this; for (let index = 0; index < files.length; index++) { - let fileItem = { file: files.item(index) }; + let fileItem = { + file: files.item(index) + } as SkyFileItem; if (fileItem.file.size < this.minFileSize) { fileItem.errorType = 'minFileSize'; fileItem.errorParam = this.minFileSize.toString(); this.filesRejected(fileItem, validFileArray, rejectedFileArray, totalFiles); + } else if (fileItem.file.size > this.maxFileSize) { fileItem.errorType = 'maxFileSize'; fileItem.errorParam = this.maxFileSize.toString(); this.filesRejected(fileItem, validFileArray, rejectedFileArray, totalFiles); + } else if (this.fileTypeRejected(fileItem.file.type)) { fileItem.errorType = 'fileType'; fileItem.errorParam = this.acceptedTypes; this.filesRejected(fileItem, validFileArray, rejectedFileArray, totalFiles); + } else if (this.validateFn) { let errorParam = this.validateFn(fileItem); + if (!!errorParam) { fileItem.errorType = 'validate'; fileItem.errorParam = errorParam; this.filesRejected(fileItem, validFileArray, rejectedFileArray, totalFiles); + } else { this.loadFile(fileDrop, fileItem, validFileArray, rejectedFileArray, totalFiles); } @@ -271,13 +284,14 @@ export class SkyFileDropComponent { } } - private verifyDropFiles(this: SkyFileDropComponent, items: any) { - if (!this.multiple && items.length > 1) { + private verifyDropFiles(files: any) { + if (!this.multiple && files.length > 1) { return false; } - for (let index = 0; index < items.length; index++) { - let file = items[index]; + for (let index = 0; index < files.length; index++) { + const file = files.item(index); + if (file.webkitGetAsEntry && file.webkitGetAsEntry() && file.webkitGetAsEntry().isDirectory) { return false; } @@ -285,5 +299,4 @@ export class SkyFileDropComponent { return true; } - }