fix: abstract and fix legacy fs (#4032)

This commit is contained in:
David Luzar 2021-10-07 13:19:40 +02:00 committed by GitHub
parent 75aeaa6c38
commit 54739cd2df
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 179 additions and 50 deletions

View file

@ -1,4 +1,3 @@
import { FileSystemHandle } from "browser-fs-access";
import { cleanAppStateForExport } from "../appState";
import { EXPORT_DATA_TYPES } from "../constants";
import { clearElementsForExport } from "../element";
@ -7,6 +6,7 @@ import { CanvasError } from "../errors";
import { t } from "../i18n";
import { calculateScrollCenter } from "../scene";
import { AppState } from "../types";
import { FileSystemHandle } from "./filesystem";
import { isValidExcalidrawData } from "./json";
import { restore } from "./restore";
import { ImportedLibraryData } from "./types";

122
src/data/filesystem.ts Normal file
View file

@ -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 };

View file

@ -1,4 +1,3 @@
import { fileSave, FileSystemHandle } from "browser-fs-access";
import {
copyBlobToClipboardAsPng,
copyTextToSystemClipboard,
@ -10,6 +9,7 @@ import { exportToCanvas, exportToSvg } from "../scene/export";
import { ExportType } from "../scene/types";
import { AppState } from "../types";
import { canvasToBlob } from "./blob";
import { fileSave, FileSystemHandle } from "./filesystem";
import { serializeAsJSON } from "./json";
export { loadFromBlob } from "./blob";
@ -49,10 +49,10 @@ export const exportCanvas = async (
return await fileSave(
new Blob([tempSvg.outerHTML], { type: "image/svg+xml" }),
{
fileName: `${name}.svg`,
extensions: [".svg"],
name,
extension: "svg",
fileHandle,
},
fileHandle,
);
} else if (type === "clipboard-svg") {
await copyTextToSystemClipboard(tempSvg.outerHTML);
@ -71,7 +71,6 @@ export const exportCanvas = async (
tempCanvas.remove();
if (type === "png") {
const fileName = `${name}.png`;
if (appState.exportEmbedScene) {
blob = await (
await import(/* webpackChunkName: "image" */ "./image")
@ -81,14 +80,11 @@ export const exportCanvas = async (
});
}
return await fileSave(
blob,
{
fileName,
extensions: [".png"],
},
return await fileSave(blob, {
name,
extension: "png",
fileHandle,
);
});
} else if (type === "clipboard") {
try {
await copyBlobToClipboardAsPng(blob);

View file

@ -1,4 +1,4 @@
import { fileOpen, fileSave } from "browser-fs-access";
import { fileOpen, fileSave } from "./filesystem";
import { cleanAppStateForExport } from "../appState";
import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants";
import { clearElementsForExport } from "../element";
@ -37,15 +37,14 @@ export const saveAsJSON = async (
type: MIME_TYPES.excalidraw,
});
const fileHandle = await fileSave(
blob,
{
fileName: `${appState.name}.excalidraw`,
description: "Excalidraw file",
extensions: [".excalidraw"],
},
isImageFileHandle(appState.fileHandle) ? null : appState.fileHandle,
);
const fileHandle = await fileSave(blob, {
name: appState.name,
extension: "excalidraw",
description: "Excalidraw file",
fileHandle: isImageFileHandle(appState.fileHandle)
? null
: appState.fileHandle,
});
return { fileHandle };
};
@ -101,15 +100,16 @@ export const saveLibraryAsJSON = async (library: Library) => {
library: libraryItems,
};
const serialized = JSON.stringify(data, null, 2);
const fileName = "library.excalidrawlib";
const blob = new Blob([serialized], {
type: MIME_TYPES.excalidrawlib,
});
await fileSave(blob, {
fileName,
description: "Excalidraw library file",
extensions: [".excalidrawlib"],
});
await fileSave(
new Blob([serialized], {
type: MIME_TYPES.excalidrawlib,
}),
{
name: "library",
extension: "excalidrawlib",
description: "Excalidraw library file",
},
);
};
export const importLibraryFromJSON = async (library: Library) => {