Skip to content

Commit

Permalink
feat: add support for reading files from FileSystemFileHandle
Browse files Browse the repository at this point in the history
  • Loading branch information
Roland Groza committed Feb 1, 2022
1 parent d828791 commit 7d3ff11
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 19 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ document.addEventListener('drop', async evt => {
---------

#### ES6
Convert a `DragEvent` to File objects:
Convert a [DragEvent](https://developer.mozilla.org/en-US/docs/Web/API/DragEvent) to File objects:
```ts
import {fromEvent} from 'file-selector';
document.addEventListener('drop', async evt => {
Expand All @@ -55,7 +55,7 @@ document.addEventListener('drop', async evt => {
});
```

Convert a file input to File objects:
Convert a [change event](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/change_event) for an input type file to File objects:
```ts
import {fromEvent} from 'file-selector';
const input = document.getElementById('myInput');
Expand All @@ -65,6 +65,18 @@ input.addEventListener('change', async evt => {
});
```

Convert [FileSystemFileHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle) items to File objects:
```ts
import {fromEvent} from 'file-selector';

// Open file picker
const handles = await window.showOpenFilePicker({multiple: true});
// Get the files
const files = await fromEvent(handles);
console.log(files);
```
**NOTE** The above is experimental and subject to change.

#### CommonJS
Convert a `DragEvent` to File objects:
```ts
Expand Down
67 changes: 55 additions & 12 deletions src/file-selector.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ it('returns a Promise', async () => {
expect(fromEvent(evt)).toBeInstanceOf(Promise);
});

it('should return an empty array if the passed event is not a DragEvent', async () => {
const evt = new Event('test');
const files = await fromEvent(evt);
it('should return an empty array if the passed arg is not what we expect', async () => {
const files = await fromEvent({});
expect(files).toHaveLength(0);
});

Expand All @@ -33,14 +32,19 @@ it('should return the evt {target} {files} if the passed event is an input evt',
expect(file.path).toBe(name);
});

it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11)', async () => {
it('should return an empty array if the evt {target} has no {files} prop', async () => {
const evt = inputEvtFromFiles();
const files = await fromEvent(evt);
expect(files).toHaveLength(0);
});

it('should return files if the arg is a list of FileSystemFileHandle', async () => {
const name = 'test.json';
const mockFile = createFile(name, {ping: true}, {
const [mockFile, mockHandle] = createFileSystemFileHandle(name, {ping: true}, {
type: 'application/json'
});
const evt = dragEvt([mockFile]);

const files = await fromEvent(evt);
const files = await fromEvent([mockHandle]);
expect(files).toHaveLength(1);
expect(files.every(file => file instanceof File)).toBe(true);

Expand All @@ -53,12 +57,32 @@ it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11
expect(file.path).toBe(name);
});

it('should return an empty array if the evt {target} has no {files} prop', async () => {
const evt = inputEvtFromFiles();
it('should return an empty array if the passed event is not a DragEvent', async () => {
const evt = new Event('test');
const files = await fromEvent(evt);
expect(files).toHaveLength(0);
});

it('should return {files} from DataTransfer if {items} is not defined (e.g. IE11)', async () => {
const name = 'test.json';
const mockFile = createFile(name, {ping: true}, {
type: 'application/json'
});
const evt = dragEvt([mockFile]);

const files = await fromEvent(evt);
expect(files).toHaveLength(1);
expect(files.every(file => file instanceof File)).toBe(true);

const [file] = files as FileWithPath[];

expect(file.name).toBe(mockFile.name);
expect(file.type).toBe(mockFile.type);
expect(file.size).toBe(mockFile.size);
expect(file.lastModified).toBe(mockFile.lastModified);
expect(file.path).toBe(name);
});

it('should return files from DataTransfer {items} if the passed event is a DragEvent', async () => {
const name = 'test.json';
const mockFile = createFile(name, {ping: true}, {
Expand Down Expand Up @@ -373,9 +397,14 @@ function inputEvtFromFiles(...files: File[]): Event {
value: files
});
}
return {
target: input
} as any;
return new Proxy(new CustomEvent('input'), {
get(t, p, rcvr) {
if (p === 'target') {
return input;
}
return t[p];
}
});
}

function createFile<T>(name: string, data: T, options?: FilePropertyBag) {
Expand All @@ -384,12 +413,26 @@ function createFile<T>(name: string, data: T, options?: FilePropertyBag) {
return file;
}

function createFileSystemFileHandle<T>(name: string, data: T, options?: FilePropertyBag): [File, FileSystemFileHandle] {
const json = JSON.stringify(data);
const file = new File([json], name, options);
return [file, {
getFile() {
return Promise.resolve(file);
}
}];
}

function sortFiles<T extends File>(files: T[]) {
return files.slice(0)
.sort((a, b) => a.name.localeCompare(b.name));
}


interface FileSystemFileHandle {
getFile(): Promise<File>;
}

type FileOrDirEntry = FileEntry | DirEntry

interface FileEntry extends Entry {
Expand Down
23 changes: 19 additions & 4 deletions src/file-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,21 @@ const FILES_TO_IGNORE = [
* Convert a DragEvent's DataTrasfer object to a list of File objects
* NOTE: If some of the items are folders,
* everything will be flattened and placed in the same list but the paths will be kept as a {path} property.
*
* EXPERIMENTAL: A list of https://developer.mozilla.org/en-US/docs/Web/API/FileSystemHandle objects can also be passed as an arg
* and a list of File objects will be returned.
*
* @param evt
*/
export async function fromEvent(evt: Event): Promise<(FileWithPath | DataTransferItem)[]> {
return isDragEvt(evt) && evt.dataTransfer
? getDataTransferFiles(evt.dataTransfer, evt.type)
: getInputFiles(evt);
export async function fromEvent(evt: Event | any): Promise<(FileWithPath | DataTransferItem)[]> {
if (isDragEvt(evt) && evt.dataTransfer) {
return getDataTransferFiles(evt.dataTransfer, evt.type);
} else if (evt instanceof Event) {
return getInputFiles(evt);
} else if (Array.isArray(evt) && evt.every(item => 'getFile' in item && typeof item.getFile === 'function')) {
return getFsHandleFiles(evt)
}
return [];
}

function isDragEvt(value: any): value is DragEvent {
Expand All @@ -33,6 +42,12 @@ function getInputFiles(evt: Event) {
return files.map(file => toFileWithPath(file));
}

// Ee expect each handle to be https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle
async function getFsHandleFiles(handles: any[]) {
const files = await Promise.all(handles.map(h => h.getFile()));
return files.map(file => toFileWithPath(file));
}

function isInput(value: EventTarget | null): value is HTMLInputElement {
return value !== null;
}
Expand Down
3 changes: 2 additions & 1 deletion tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "commonjs",
"target": "es5"
"target": "es5",
"suppressImplicitAnyIndexErrors": true
},
"include": [
"src/**/*.ts"
Expand Down

0 comments on commit 7d3ff11

Please sign in to comment.