fix: abstract and fix legacy fs (#4032)
parent
75aeaa6c38
commit
54739cd2df
@ -0,0 +1,122 @@
|
||||
import {
|
||||
FileWithHandle,
|
||||
fileOpen as _fileOpen,
|
||||
fileSave as _fileSave,
|
||||
FileSystemHandle,
|
||||
supported as nativeFileSystemSupported,
|
||||
} from "@dwelle/browser-fs-access";
|
||||
import { EVENT, MIME_TYPES } from "../constants";
|
||||
import { AbortError } from "../errors";
|
||||
import { debounce } from "../utils";
|
||||
|
||||
type FILE_EXTENSION =
|
||||
| "jpg"
|
||||
| "png"
|
||||
| "svg"
|
||||
| "json"
|
||||
| "excalidraw"
|
||||
| "excalidrawlib";
|
||||
|
||||
const FILE_TYPE_TO_MIME_TYPE: Record<FILE_EXTENSION, string> = {
|
||||
jpg: "image/jpeg",
|
||||
png: "image/png",
|
||||
svg: "image/svg+xml",
|
||||
json: "application/json",
|
||||
excalidraw: MIME_TYPES.excalidraw,
|
||||
excalidrawlib: MIME_TYPES.excalidrawlib,
|
||||
};
|
||||
|
||||
const INPUT_CHANGE_INTERVAL_MS = 500;
|
||||
|
||||
export const fileOpen = <M extends boolean | undefined = false>(opts: {
|
||||
extensions?: FILE_EXTENSION[];
|
||||
description?: string;
|
||||
multiple?: M;
|
||||
}): Promise<
|
||||
M extends false | undefined ? FileWithHandle : FileWithHandle[]
|
||||
> => {
|
||||
// an unsafe TS hack, alas not much we can do AFAIK
|
||||
type RetType = M extends false | undefined
|
||||
? FileWithHandle
|
||||
: FileWithHandle[];
|
||||
|
||||
const mimeTypes = opts.extensions?.reduce((mimeTypes, type) => {
|
||||
mimeTypes.push(FILE_TYPE_TO_MIME_TYPE[type]);
|
||||
|
||||
return mimeTypes;
|
||||
}, [] as string[]);
|
||||
|
||||
const extensions = opts.extensions?.reduce((acc, ext) => {
|
||||
if (ext === "jpg") {
|
||||
return acc.concat(".jpg", ".jpeg");
|
||||
}
|
||||
return acc.concat(`.${ext}`);
|
||||
}, [] as string[]);
|
||||
|
||||
return _fileOpen({
|
||||
description: opts.description,
|
||||
extensions,
|
||||
mimeTypes,
|
||||
multiple: opts.multiple ?? false,
|
||||
legacySetup: (resolve, reject, input) => {
|
||||
const scheduleRejection = debounce(reject, INPUT_CHANGE_INTERVAL_MS);
|
||||
const focusHandler = () => {
|
||||
checkForFile();
|
||||
document.addEventListener(EVENT.KEYUP, scheduleRejection);
|
||||
document.addEventListener(EVENT.POINTER_UP, scheduleRejection);
|
||||
scheduleRejection();
|
||||
};
|
||||
const checkForFile = () => {
|
||||
// this hack might not work when expecting multiple files
|
||||
if (input.files?.length) {
|
||||
const ret = opts.multiple ? [...input.files] : input.files[0];
|
||||
resolve(ret as RetType);
|
||||
}
|
||||
};
|
||||
requestAnimationFrame(() => {
|
||||
window.addEventListener(EVENT.FOCUS, focusHandler);
|
||||
});
|
||||
const interval = window.setInterval(() => {
|
||||
checkForFile();
|
||||
}, INPUT_CHANGE_INTERVAL_MS);
|
||||
return (rejectPromise) => {
|
||||
clearInterval(interval);
|
||||
scheduleRejection.cancel();
|
||||
window.removeEventListener(EVENT.FOCUS, focusHandler);
|
||||
document.removeEventListener(EVENT.KEYUP, scheduleRejection);
|
||||
document.removeEventListener(EVENT.POINTER_UP, scheduleRejection);
|
||||
if (rejectPromise) {
|
||||
// so that something is shown in console if we need to debug this
|
||||
console.warn("Opening the file was canceled (legacy-fs).");
|
||||
rejectPromise(new AbortError());
|
||||
}
|
||||
};
|
||||
},
|
||||
}) as Promise<RetType>;
|
||||
};
|
||||
|
||||
export const fileSave = (
|
||||
blob: Blob,
|
||||
opts: {
|
||||
/** supply without the extension */
|
||||
name: string;
|
||||
/** file extension */
|
||||
extension: FILE_EXTENSION;
|
||||
description?: string;
|
||||
/** existing FileSystemHandle */
|
||||
fileHandle?: FileSystemHandle | null;
|
||||
},
|
||||
) => {
|
||||
return _fileSave(
|
||||
blob,
|
||||
{
|
||||
fileName: `${opts.name}.${opts.extension}`,
|
||||
description: opts.description,
|
||||
extensions: [`.${opts.extension}`],
|
||||
},
|
||||
opts.fileHandle,
|
||||
);
|
||||
};
|
||||
|
||||
export type { FileSystemHandle };
|
||||
export { nativeFileSystemSupported };
|
Loading…
Reference in New Issue