123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- import { unstable_batchedUpdates } from "react-dom";
- import { fileOpen as _fileOpen } from "browser-fs-access";
- import { MIME_TYPES } from "@excalidraw/excalidraw";
- 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);
- };
|