file-selector
Version:
Convert DataTransfer object to a list of File objects
183 lines (182 loc) • 5.85 kB
JavaScript
import { DEFAULT_MIME_TYPES } from "./mime-default.js";
//#region src/error.ts
var UnexpectedObjectError = class extends Error {
constructor(item) {
super("DataTransferItem is not a file");
this.item = item;
this.name = "UnexpectedObjectError";
}
};
//#endregion
//#region src/file.ts
function toFileWithPath(file, path, h) {
const f = file;
const { webkitRelativePath } = file;
const p = typeof path === "string" ? path : typeof webkitRelativePath === "string" && webkitRelativePath.length > 0 ? webkitRelativePath : `./${file.name}`;
if (typeof f.path !== "string") setObjProp(f, "path", p);
if (h !== void 0) Object.defineProperty(f, "handle", {
value: h,
writable: false,
configurable: false,
enumerable: true
});
setObjProp(f, "relativePath", p);
return f;
}
/**
* Sets a File's `type` from its extension using the provided `mimeTypes` lookup,
* but only when the browser didn't already set a type. Returns the same File.
*/
function withMimeType(file, mimeTypes = DEFAULT_MIME_TYPES) {
const { name } = file;
if (name && name.lastIndexOf(".") !== -1 && !file.type) {
const ext = name.split(".").pop().toLowerCase();
const type = mimeTypes.get(ext);
if (type) Object.defineProperty(file, "type", {
value: type,
writable: false,
configurable: false,
enumerable: true
});
}
return file;
}
function setObjProp(f, key, value) {
Object.defineProperty(f, key, {
value,
writable: false,
configurable: false,
enumerable: true
});
}
//#endregion
//#region src/file-selector.ts
const FILES_TO_IGNORE = [".DS_Store", "Thumbs.db"];
/**
* 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
* @param options
*/
async function fromEvent(evt, { mimeTypes = DEFAULT_MIME_TYPES } = {}) {
return (await getFilesOrItems(evt)).map((item) => item instanceof File ? withMimeType(item, mimeTypes) : item);
}
async function getFilesOrItems(evt) {
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 [];
}
function isDataTransfer(value) {
return isObject(value);
}
function isChangeEvt(value) {
return isObject(value) && isObject(value.target);
}
function isObject(v) {
return typeof v === "object" && v !== null;
}
function getInputFiles(evt) {
return fromList(evt.target.files).map((file) => toFileWithPath(file));
}
async function getFsHandleFiles(handles) {
return (await Promise.all(handles.map((h) => h.getFile()))).map((file) => toFileWithPath(file));
}
async function getDataTransferFiles(dt, type) {
const items = fromList(dt.items).filter((item) => item.kind === "file");
if (type !== "drop") return items;
return noIgnoredFiles(flatten(await Promise.all(items.map(toFilePromises))));
}
function noIgnoredFiles(files) {
return files.filter((file) => FILES_TO_IGNORE.indexOf(file.name) === -1);
}
function fromList(items) {
if (items === null) return [];
return Array.from(items);
}
async function toFilePromises(item) {
if (typeof item.webkitGetAsEntry !== "function") return fromDataTransferItem(item);
const entry = item.webkitGetAsEntry();
if (entry?.isDirectory) {
const handle = await getFsHandle(item);
if (handle?.kind === "directory") return fromDirHandle(handle, `/${handle.name}`);
return fromDirEntry(entry);
}
return fromDataTransferItem(item, entry);
}
function flatten(items) {
const result = [];
for (const item of items) if (Array.isArray(item)) result.push(...flatten(item));
else result.push(item);
return result;
}
async function fromDataTransferItem(item, entry) {
const h = await getFsHandle(item);
if (h === null) throw new UnexpectedObjectError(item);
if (h !== void 0) {
const file = await h.getFile();
file.handle = h;
return toFileWithPath(file);
}
const file = item.getAsFile();
if (!file) throw new UnexpectedObjectError(item);
return toFileWithPath(file, entry?.fullPath ?? void 0);
}
async function getFsHandle(item) {
if (globalThis.isSecureContext && typeof item.getAsFileSystemHandle === "function") return item.getAsFileSystemHandle();
}
async function fromDirHandle(handle, path) {
const files = [];
for await (const child of handle.values()) {
const childPath = `${path}/${child.name}`;
if (child.kind === "directory") files.push(...await fromDirHandle(child, childPath));
else {
const file = await child.getFile();
files.push(toFileWithPath(file, childPath, child));
}
}
return files;
}
async function fromEntry(entry) {
return entry.isDirectory ? fromDirEntry(entry) : fromFileEntry(entry);
}
function fromDirEntry(entry) {
const reader = entry.createReader();
return new Promise((resolve, reject) => {
const entries = [];
function readEntries() {
reader.readEntries(async (batch) => {
if (!batch.length) try {
resolve(await Promise.all(entries));
} catch (err) {
reject(err);
}
else {
const items = Promise.all(batch.map(fromEntry));
entries.push(items);
readEntries();
}
}, (err) => {
reject(err);
});
}
readEntries();
});
}
async function fromFileEntry(entry) {
return new Promise((resolve, reject) => {
entry.file((file) => {
resolve(toFileWithPath(file, entry.fullPath));
}, (err) => {
reject(err);
});
});
}
//#endregion
export { DEFAULT_MIME_TYPES, fromEvent };
//# sourceMappingURL=index.js.map