Skip to content

Commit

Permalink
refactor!: split file handles into seperate function
Browse files Browse the repository at this point in the history
Signed-off-by: Jon Koops <[email protected]>

BREAKING CHANGE: Arrays of `FileSystemFileHandle` are now handled in a dedicated `fromFileHandles()` function.
  • Loading branch information
jonkoops committed Nov 22, 2024
1 parent a2a80e8 commit 50ec437
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 37 deletions.
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,13 @@ input.addEventListener("change", async (event) => {
Convert [FileSystemFileHandle](https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle) items to File objects:

```js
import { fromEvent } from "file-selector";
import { fromFileHandles } from "file-selector";

const handles = await window.showOpenFilePicker({ multiple: true });
const files = await fromEvent(handles);
const files = await fromFileHandles(handles);
console.log(files);
```

> [!NOTE]
> The above is experimental and subject to change.
## Browser Support

Most browser support basic File selection with drag 'n' drop or file input:
Expand Down
18 changes: 4 additions & 14 deletions src/file-selector.spec.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
import { FileWithPath } from "./file.js";
import { fromEvent } from "./file-selector.js";
import { fromEvent, fromFileHandles } from "./file-selector.js";

it("returns a Promise", async () => {
const evt = new Event("test");
expect(fromEvent(evt)).toBeInstanceOf(Promise);
});

it("should return an empty array if the passed arg is not what we expect", async () => {
const files = await fromEvent({});
expect(files).toHaveLength(0);
});

it("should return an empty array if drag event", async () => {
const files = await fromEvent({});
const files = await fromEvent({} as Event);
expect(files).toHaveLength(0);
});

Expand Down Expand Up @@ -56,7 +51,7 @@ it("should return files if the arg is a list of FileSystemFileHandle", async ()
},
);

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

Expand Down Expand Up @@ -537,19 +532,14 @@ function createFileSystemFileHandle<T>(
getFile() {
return Promise.resolve(file);
},
},
} as FileSystemFileHandle,
];
}

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

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

type FileOrDirEntry = FileEntry | DirEntry;

interface FileEntry extends Entry {
Expand Down
49 changes: 31 additions & 18 deletions src/file-selector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,39 @@ const FILES_TO_IGNORE = [
* 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 | any,
evt: Event,
): Promise<(FileWithPath | DataTransferItem)[]> {
if (isObject<DragEvent>(evt) && isDataTransfer(evt.dataTransfer)) {
return getDataTransferFiles(evt.dataTransfer, evt.type);
} else if (isChangeEvt(evt)) {
return getInputFiles(evt);
} else if (
Array.isArray(evt) &&
evt.every((item) => "getFile" in item && typeof item.getFile === "function")
) {
return getFsHandleFiles(evt);
}

return [];
}

/**
* Convert an array of `FileSystemHandle` objects to a list of `FileWithPath` objects.
*
* @param handles The array of handles to convert.
* @returns A promise that resolves to a list of converted `FileWithPath` objects.
*/
export function fromFileHandles(
handles: FileSystemFileHandle[],
): Promise<FileWithPath[]> {
return Promise.all(handles.map((handle) => fromFileHandle(handle)));
}

async function fromFileHandle(
handle: FileSystemFileHandle,
): Promise<FileWithPath> {
const file = await handle.getFile();
return toFileWithPath(file);
}

function isDataTransfer(value: any): value is DataTransfer {
return isObject(value);
}
Expand All @@ -52,12 +64,6 @@ function getInputFiles(event: Event): FileWithPath[] {
return Array.from(event.target.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));
}

async function getDataTransferFiles(dataTransfer: DataTransfer, type: string) {
const items = Array.from(dataTransfer.items).filter(
(item) => item.kind === "file",
Expand Down Expand Up @@ -116,9 +122,16 @@ async function fromDataTransferItem(
throw new Error("Transferred item is not a file.");
}

const file = await handle.getFile();
(file as any).handle = handle;
return toFileWithPath(file);
const fileWithPath = await fromFileHandle(handle);

Object.defineProperty(fileWithPath, "handle", {
value: handle,
writable: false,
configurable: false,
enumerable: true,
});

return fileWithPath;
}

const file = item.getAsFile();
Expand Down

0 comments on commit 50ec437

Please sign in to comment.