| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 | import { MIME_TYPES } from "@excalidraw/excalidraw";import { fileOpen as _fileOpen } from "browser-fs-access";import { unstable_batchedUpdates } from "react-dom";type FILE_EXTENSION = Exclude<keyof typeof MIME_TYPES, "binary">;const INPUT_CHANGE_INTERVAL_MS = 500;export type ResolvablePromise<T> = Promise<T> & {  resolve: [T] extends [undefined] ? (value?: T) => void : (value: T) => void;  reject: (error: Error) => void;};export const resolvablePromise = <T>() => {  let resolve!: any;  let reject!: any;  const promise = new Promise((_resolve, _reject) => {    resolve = _resolve;    reject = _reject;  });  (promise as any).resolve = resolve;  (promise as any).reject = reject;  return promise as ResolvablePromise<T>;};export const distance2d = (x1: number, y1: number, x2: number, y2: number) => {  const xd = x2 - x1;  const yd = y2 - y1;  return Math.hypot(xd, yd);};export const fileOpen = <M extends boolean | undefined = false>(opts: {  extensions?: FILE_EXTENSION[];  description: string;  multiple?: M;}): Promise<M extends false | undefined ? File : File[]> => {  // an unsafe TS hack, alas not much we can do AFAIK  type RetType = M extends false | undefined ? File : File[];  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("keyup", scheduleRejection);        document.addEventListener("pointerup", 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("focus", focusHandler);      });      const interval = window.setInterval(() => {        checkForFile();      }, INPUT_CHANGE_INTERVAL_MS);      return (rejectPromise) => {        clearInterval(interval);        scheduleRejection.cancel();        window.removeEventListener("focus", focusHandler);        document.removeEventListener("keyup", scheduleRejection);        document.removeEventListener("pointerup", 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 Error("Request Aborted"));        }      };    },  }) as Promise<RetType>;};export const debounce = <T extends any[]>(  fn: (...args: T) => void,  timeout: number,) => {  let handle = 0;  let lastArgs: T | null = null;  const ret = (...args: T) => {    lastArgs = args;    clearTimeout(handle);    handle = window.setTimeout(() => {      lastArgs = null;      fn(...args);    }, timeout);  };  ret.flush = () => {    clearTimeout(handle);    if (lastArgs) {      const _lastArgs = lastArgs;      lastArgs = null;      fn(..._lastArgs);    }  };  ret.cancel = () => {    lastArgs = null;    clearTimeout(handle);  };  return ret;};export const withBatchedUpdates = <  TFunction extends ((event: any) => void) | (() => void),>(  func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,) =>  ((event) => {    unstable_batchedUpdates(func as TFunction, event);  }) as TFunction;/** * barches React state updates and throttles the calls to a single call per * animation frame */export const withBatchedUpdatesThrottled = <  TFunction extends ((event: any) => void) | (() => void),>(  func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never,) => {  // @ts-ignore  return throttleRAF<Parameters<TFunction>>(((event) => {    unstable_batchedUpdates(func, event);  }) as TFunction);};
 |