UNPKG

@uppy/utils

Version:

Shared utility functions for Uppy Core and plugins maintained by the Uppy team.

153 lines (143 loc) 6.06 kB
import getFilesAndDirectoriesFromDirectory from './getFilesAndDirectoriesFromDirectory.js' interface FileSystemFileHandle extends FileSystemHandle { getFile(): Promise<File> } interface FileSystemDirectoryHandle extends FileSystemHandle { values(): AsyncGenerator< FileSystemDirectoryHandle | FileSystemFileHandle, void, undefined > } /** * Polyfill for the new (experimental) getAsFileSystemHandle API (using the popular webkitGetAsEntry behind the scenes) * so that we can switch to the getAsFileSystemHandle API once it (hopefully) becomes standard */ function getAsFileSystemHandleFromEntry( entry: FileSystemEntry | null | undefined, logDropError: Parameters<typeof getFilesAndDirectoriesFromDirectory>[2], ): FileSystemFileHandle | FileSystemDirectoryHandle | null | undefined { if (entry == null) return entry return { kind: // eslint-disable-next-line no-nested-ternary entry.isFile ? 'file' : entry.isDirectory ? 'directory' : (undefined as never), name: entry.name, getFile(): ReturnType<FileSystemFileHandle['getFile']> { return new Promise((resolve, reject) => (entry as FileSystemFileEntry).file(resolve, reject), ) }, async *values(): ReturnType<FileSystemDirectoryHandle['values']> { // If the file is a directory. const directoryReader = (entry as FileSystemDirectoryEntry).createReader() const entries = await new Promise< Array<NonNullable<ReturnType<typeof getAsFileSystemHandleFromEntry>>> >((resolve) => { getFilesAndDirectoriesFromDirectory(directoryReader, [], logDropError, { onSuccess: (dirEntries) => resolve( dirEntries.map( (file) => getAsFileSystemHandleFromEntry(file, logDropError)!, ), ), }) }) yield* entries }, isSameEntry: undefined as any as FileSystemDirectoryHandle['isSameEntry'], } } async function* createPromiseToAddFileOrParseDirectory( entry: FileSystemFileHandle | FileSystemDirectoryHandle, relativePath: string, lastResortFile: File | null | undefined = undefined, ): AsyncGenerator<File> { const getNextRelativePath = (): string => `${relativePath}/${entry.name}` // For each dropped item, - make sure it's a file/directory, and start deepening in! if (entry.kind === 'file') { const file = await (entry as FileSystemFileHandle).getFile() if (file != null) { ;(file as any).relativePath = relativePath ? getNextRelativePath() : null yield file } else if (lastResortFile != null) yield lastResortFile } else if (entry.kind === 'directory') { for await (const handle of (entry as FileSystemDirectoryHandle).values()) { // Recurse on the directory, appending the dir name to the relative path yield* createPromiseToAddFileOrParseDirectory( handle, relativePath ? getNextRelativePath() : entry.name, ) } } else if (lastResortFile != null) yield lastResortFile } /** * Load all files from data transfer, and recursively read any directories. * Note that IE is not supported for drag-drop, because IE doesn't support Data Transfers * * @param {DataTransfer} dataTransfer * @param {*} logDropError on error */ export default async function* getFilesFromDataTransfer( dataTransfer: DataTransfer, logDropError: Parameters<typeof getFilesAndDirectoriesFromDirectory>[2], ): ReturnType<typeof createPromiseToAddFileOrParseDirectory> { // Retrieving the dropped items must happen synchronously // otherwise only the first item gets treated and the other ones are garbage collected. // https://github.com/transloadit/uppy/pull/3998 const fileSystemHandles = await Promise.all( Array.from(dataTransfer.items, async (item) => { let fileSystemHandle: | FileSystemFileHandle | FileSystemDirectoryHandle | null | undefined // TODO enable getAsFileSystemHandle API once we can get it working with subdirectories // IMPORTANT: Need to check isSecureContext *before* calling getAsFileSystemHandle // or else Chrome will crash when running in HTTP: https://github.com/transloadit/uppy/issues/4133 // if (window.isSecureContext && item.getAsFileSystemHandle != null) // fileSystemHandle = await item.getAsFileSystemHandle() // `webkitGetAsEntry` exists in all popular browsers (including non-WebKit browsers), // however it may be renamed to getAsEntry() in the future, so you should code defensively, looking for both. // from https://developer.mozilla.org/en-US/docs/Web/API/DataTransferItem/webkitGetAsEntry const getAsEntry = (): ReturnType< DataTransferItem['webkitGetAsEntry'] > => typeof (item as any).getAsEntry === 'function' ? (item as any).getAsEntry() : item.webkitGetAsEntry() // eslint-disable-next-line prefer-const fileSystemHandle ??= getAsFileSystemHandleFromEntry( getAsEntry(), logDropError, ) return { fileSystemHandle, lastResortFile: item.getAsFile(), // can be used as a fallback in case other methods fail } }), ) for (const { lastResortFile, fileSystemHandle } of fileSystemHandles) { // fileSystemHandle and lastResortFile can be null when we drop an url. if (fileSystemHandle != null) { try { yield* createPromiseToAddFileOrParseDirectory( fileSystemHandle, '', lastResortFile, ) } catch (err) { // Example: If dropping a symbolic link, Chromium will throw: // "DOMException: A requested file or directory could not be found at the time an operation was processed.", // So we will use lastResortFile instead. See https://github.com/transloadit/uppy/issues/3505. if (lastResortFile != null) { yield lastResortFile } else { logDropError(err) } } } else if (lastResortFile != null) yield lastResortFile } }