From 50ec4379dbc7717eeb8348dbe41b9d9e5e68dd69 Mon Sep 17 00:00:00 2001 From: Jon Koops Date: Fri, 22 Nov 2024 17:42:42 +0100 Subject: [PATCH] refactor!: split file handles into seperate function Signed-off-by: Jon Koops BREAKING CHANGE: Arrays of `FileSystemFileHandle` are now handled in a dedicated `fromFileHandles()` function. --- README.md | 7 ++---- src/file-selector.spec.ts | 18 ++++---------- src/file-selector.ts | 49 +++++++++++++++++++++++++-------------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 5758af5..0c3cf17 100644 --- a/README.md +++ b/README.md @@ -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: diff --git a/src/file-selector.spec.ts b/src/file-selector.spec.ts index ff272ac..1402e21 100644 --- a/src/file-selector.spec.ts +++ b/src/file-selector.spec.ts @@ -1,5 +1,5 @@ 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"); @@ -7,12 +7,7 @@ it("returns a Promise", async () => { }); 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); }); @@ -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); @@ -537,7 +532,7 @@ function createFileSystemFileHandle( getFile() { return Promise.resolve(file); }, - }, + } as FileSystemFileHandle, ]; } @@ -545,11 +540,6 @@ function sortFiles(files: T[]) { return files.slice(0).sort((a, b) => a.name.localeCompare(b.name)); } -interface FileSystemFileHandle { - kind: "file"; - getFile(): Promise; -} - type FileOrDirEntry = FileEntry | DirEntry; interface FileEntry extends Entry { diff --git a/src/file-selector.ts b/src/file-selector.ts index deeb8a6..8e131fa 100644 --- a/src/file-selector.ts +++ b/src/file-selector.ts @@ -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(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 { + return Promise.all(handles.map((handle) => fromFileHandle(handle))); +} + +async function fromFileHandle( + handle: FileSystemFileHandle, +): Promise { + const file = await handle.getFile(); + return toFileWithPath(file); +} + function isDataTransfer(value: any): value is DataTransfer { return isObject(value); } @@ -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", @@ -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();