123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116 |
- import {
- FileWithHandle,
- fileOpen as _fileOpen,
- fileSave as _fileSave,
- FileSystemHandle,
- supported as nativeFileSystemSupported,
- } from "browser-fs-access";
- import { EVENT, MIME_TYPES } from "../constants";
- import { AbortError } from "../errors";
- import { debounce } from "../utils";
- type FILE_EXTENSION =
- | "gif"
- | "jpg"
- | "png"
- | "excalidraw.png"
- | "svg"
- | "excalidraw.svg"
- | "json"
- | "excalidraw"
- | "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(MIME_TYPES[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 };
|