@knide/fs-prober
Version:
fs-prober is a browser-friendly NPM package for extracting file and folder structures from user-selected files or directories.
139 lines (121 loc) • 5.1 kB
text/typescript
import type { DataTransferDropEvent } from "./types"
const isDtMethodAvailable = (method: keyof DataTransferItem | (string & {})) => {
if (typeof DataTransferItem === "undefined") return false
return method in DataTransferItem.prototype
}
const shouldUseFsApi = isDtMethodAvailable("getAsFileSystemHandle")
const shouldUseWebkitApi = isDtMethodAvailable("webkitGetAsEntry")
const isFsApiHandle = (handle: FileSystemHandle | FileSystemEntry): handle is FileSystemHandle => {
return "kind" in handle
}
export const isDirEntry = (
handle: FileSystemEntry | FileSystemHandle,
): handle is FileSystemDirectoryHandle | FileSystemDirectoryEntry => {
if ("kind" in handle) return handle.kind === "directory"
if (handle.isDirectory) return true
return false
}
export const isFileEntry = (
handle: FileSystemEntry | FileSystemHandle,
): handle is FileSystemFileHandle | FileSystemFileEntry => {
if ("kind" in handle) return handle.kind === "file"
if (handle.isFile) return true
return false
}
const errorOut = () => {
const errMsg = "Your browser is not supported. Please use a chromium based browser."
console.error(errMsg)
return new Error(errMsg)
}
/** When we use FS API it will return promisified rootHandle else we'll get rootHandle directly */
export const getRootHandle = async (
dtEvent: DataTransferDropEvent,
dataTransferItemIdx: number,
): Promise<FileSystemHandle | FileSystemEntry | null> => {
if (!dtEvent.dataTransfer) throw new Error("Unable to get rootHandle for drop event")
if (shouldUseWebkitApi) {
const item = dtEvent.dataTransfer.items[dataTransferItemIdx]?.webkitGetAsEntry() // "DirectoryEntry" or "FileEntry"
return item || null
}
if (shouldUseFsApi) {
const handle = dtEvent.dataTransfer.items[dataTransferItemIdx]?.getAsFileSystemHandle()
return handle || Promise.resolve(null)
/**
* We are returning a promise here. When the promise is resolved, we get a handle
* The handle can be "FileSystemFileHandle" or "FileSystemDirectoryHandle"
*
* IMPORTANT: FileSystemDirectoryHandle or FileSystemFileHandle are experimental.
* ref 1 : https://developer.mozilla.org/en-US/docs/Web/API/FileSystemFileHandle
* ref 2: https://developer.mozilla.org/en-US/docs/Web/API/FileSystemDirectoryHandle
* The app needs to be served with secure contexts (HTTPS) using ngrok: https://frontendguruji.com/blog/run-next-js-app-locally-in-https/
*/
}
throw errorOut()
}
export const getHandleKind = (handle: FileSystemHandle | FileSystemEntry): FileSystemHandleKind => {
if (shouldUseWebkitApi && !isFsApiHandle(handle)) {
if ("isFile" in handle && handle.isFile) return "file"
if ("isDirectory" in handle && handle.isDirectory) return "directory"
throw new Error("Unknown handle kind")
} else if (shouldUseFsApi) {
if (isFsApiHandle(handle)) return handle.kind
throw new Error("Invalid FileSystemHandle")
}
throw errorOut()
}
export const getDirHandleEntries = async (
dirHandle: FileSystemDirectoryHandle | FileSystemDirectoryEntry,
): Promise<
[string, FileSystemEntry][] | [string, FileSystemDirectoryHandle | FileSystemFileHandle][]
> => {
const errMsg = "Passed handle is not a directory"
if (shouldUseWebkitApi && !isFsApiHandle(dirHandle)) {
if (!dirHandle.isDirectory) throw Error(errMsg)
const directoryReader = dirHandle.createReader()
const readEntries = async () => {
const allEntries: [FileSystemEntry["name"], FileSystemEntry][] = []
const read = async (): Promise<[FileSystemEntry["name"], FileSystemEntry][]> => {
return new Promise((resolve, reject) => {
directoryReader.readEntries(
(entriesArr) => {
if (entriesArr.length > 0) {
entriesArr.forEach((entry) => {
allEntries.push([entry.name, entry])
})
// Continue reading recursively
read().then(resolve).catch(reject)
} else {
// Resolve with all entries when no more entries are available
resolve(allEntries)
}
},
(error) => reject(error),
)
})
}
return read()
}
return await readEntries()
} else if (shouldUseFsApi && isFsApiHandle(dirHandle)) {
if (dirHandle.kind !== "directory") throw Error(errMsg)
const entries = []
for await (const entry of dirHandle.entries()) {
entries.push(entry) // entry = [name, handle]
}
return entries
}
throw errorOut()
}
export const getFile = async (
fileHandle: FileSystemFileHandle | FileSystemFileEntry,
): Promise<File> => {
if (shouldUseWebkitApi && !isFsApiHandle(fileHandle)) {
const getFileFromFileEntry = async (fileEntry: FileSystemFileEntry): Promise<File> => {
return new Promise((resolve, reject) => fileEntry.file(resolve, reject))
}
return await getFileFromFileEntry(fileHandle)
} else if (shouldUseFsApi && isFsApiHandle(fileHandle)) {
return await fileHandle.getFile()
}
throw errorOut()
}