Skip to content

Commit

Permalink
fileHandle refactor & fixes (excalidraw#2252)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwelle authored Oct 19, 2020
1 parent 4a26845 commit 1484c5a
Show file tree
Hide file tree
Showing 17 changed files with 163 additions and 41 deletions.
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@types/react": "16.9.52",
"@types/react-dom": "16.9.8",
"@types/socket.io-client": "1.4.34",
"browser-nativefs": "0.10.3",
"browser-nativefs": "0.11.0",
"firebase": "7.23.0",
"i18next-browser-languagedetector": "6.0.1",
"lodash.throttle": "4.1.1",
Expand Down
2 changes: 0 additions & 2 deletions src/actions/actionCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ export const actionClearCanvas = register({
showAriaLabel={useIsMobile()}
onClick={() => {
if (window.confirm(t("alerts.clearReset"))) {
// TODO: Make this part of `AppState`.
(window as any).handle = null;
updateData(null);
}
}}
Expand Down
34 changes: 23 additions & 11 deletions src/actions/actionExport.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,16 @@ export const actionChangeShouldAddWatermark = register({

export const actionSaveScene = register({
name: "saveScene",
perform: (elements, appState, value) => {
// TODO: Make this part of `AppState`.
saveAsJSON(elements, appState, (window as any).handle)
.catch(muteFSAbortError)
.catch((error) => console.error(error));
return { commitToHistory: false };
perform: async (elements, appState, value) => {
try {
const { fileHandle } = await saveAsJSON(elements, appState);
return { commitToHistory: false, appState: { ...appState, fileHandle } };
} catch (error) {
if (error?.name !== "AbortError") {
console.error(error);
}
return { commitToHistory: false };
}
},
keyTest: (event) => {
return event.key === "s" && event[KEYS.CTRL_OR_CMD] && !event.shiftKey;
Expand All @@ -109,11 +113,19 @@ export const actionSaveScene = register({

export const actionSaveAsScene = register({
name: "saveAsScene",
perform: (elements, appState, value) => {
saveAsJSON(elements, appState, null)
.catch(muteFSAbortError)
.catch((error) => console.error(error));
return { commitToHistory: false };
perform: async (elements, appState, value) => {
try {
const { fileHandle } = await saveAsJSON(elements, {
...appState,
fileHandle: null,
});
return { commitToHistory: false, appState: { ...appState, fileHandle } };
} catch (error) {
if (error?.name !== "AbortError") {
console.error(error);
}
return { commitToHistory: false };
}
},
keyTest: (event) => {
return event.key === "s" && event.shiftKey && event[KEYS.CTRL_OR_CMD];
Expand Down
13 changes: 11 additions & 2 deletions src/actions/manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
UpdaterFn,
ActionFilterFn,
ActionName,
ActionResult,
} from "./types";
import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types";
Expand All @@ -13,7 +14,7 @@ import { t } from "../i18n";
export class ActionManager implements ActionsManagerInterface {
actions = {} as ActionsManagerInterface["actions"];

updater: UpdaterFn;
updater: (actionResult: ActionResult | Promise<ActionResult>) => void;

getAppState: () => Readonly<AppState>;

Expand All @@ -24,7 +25,15 @@ export class ActionManager implements ActionsManagerInterface {
getAppState: () => AppState,
getElementsIncludingDeleted: () => readonly ExcalidrawElement[],
) {
this.updater = updater;
this.updater = (actionResult) => {
if (actionResult && "then" in actionResult) {
actionResult.then((actionResult) => {
return updater(actionResult);
});
} else {
return updater(actionResult);
}
};
this.getAppState = getAppState;
this.getElementsIncludingDeleted = getElementsIncludingDeleted;
}
Expand Down
4 changes: 2 additions & 2 deletions src/actions/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ type ActionFn = (
elements: readonly ExcalidrawElement[],
appState: Readonly<AppState>,
formData: any,
) => ActionResult;
) => ActionResult | Promise<ActionResult>;

export type UpdaterFn = (res: ActionResult, commitToHistory?: boolean) => void;
export type UpdaterFn = (res: ActionResult) => void;
export type ActionFilterFn = (action: Action) => void;

export type ActionName =
Expand Down
2 changes: 2 additions & 0 deletions src/appState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const getDefaultAppState = (): Omit<
width: window.innerWidth,
height: window.innerHeight,
isLibraryOpen: false,
fileHandle: null,
};
};

Expand Down Expand Up @@ -145,6 +146,7 @@ const APP_STATE_STORAGE_CONF = (<
zoom: { browser: true, export: false },
offsetTop: { browser: false, export: false },
offsetLeft: { browser: false, export: false },
fileHandle: { browser: false, export: false },
});

const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
Expand Down
2 changes: 1 addition & 1 deletion src/clipboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ export const parseClipboard = async (
export const copyCanvasToClipboardAsPng = async (canvas: HTMLCanvasElement) =>
new Promise((resolve, reject) => {
try {
canvas.toBlob(async (blob: any) => {
canvas.toBlob(async (blob) => {
try {
await navigator.clipboard.write([
new window.ClipboardItem({ "image/png": blob }),
Expand Down
4 changes: 2 additions & 2 deletions src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
return;
}
const fileHandle = launchParams.files[0];
const blob = await fileHandle.getFile();
const blob: Blob = await fileHandle.getFile();
blob.handle = fileHandle;
loadFromBlob(blob, this.state)
.then(({ elements, appState }) =>
Expand Down Expand Up @@ -3845,7 +3845,7 @@ class App extends React.Component<ExcalidrawProps, AppState> {
// but can be safely ignored on older releases.
const item = event.dataTransfer.items[0];
// TODO: Make this part of `AppState`.
(window as any).handle = await (item as any).getAsFileSystemHandle();
(file as any).handle = await (item as any).getAsFileSystemHandle();
} catch (error) {
console.warn(error.name, error.message);
}
Expand Down
26 changes: 20 additions & 6 deletions src/data/blob.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { t } from "../i18n";
import { AppState } from "../types";
import { LibraryData, ImportedDataState } from "./types";
import { calculateScrollCenter } from "../scene";
import { MIME_TYPES } from "../constants";

export const parseFileContents = async (blob: Blob | File) => {
let contents: string;
Expand Down Expand Up @@ -53,16 +54,22 @@ export const parseFileContents = async (blob: Blob | File) => {
return contents;
};

const getMimeType = (blob: Blob): string => {
if (blob.type) {
return blob.type;
}
const name = blob.name || "";
if (/\.(excalidraw|json)$/.test(name)) {
return "application/json";
}
return "";
};

export const loadFromBlob = async (
blob: any,
blob: Blob,
/** @see restore.localAppState */
localAppState: AppState | null,
) => {
if (blob.handle) {
// TODO: Make this part of `AppState`.
(window as any).handle = blob.handle;
}

const contents = await parseFileContents(blob);
try {
const data: ImportedDataState = JSON.parse(contents);
Expand All @@ -74,6 +81,13 @@ export const loadFromBlob = async (
elements: data.elements,
appState: {
appearance: localAppState?.appearance,
fileHandle:
blob.handle &&
["application/json", MIME_TYPES.excalidraw].includes(
getMimeType(blob),
)
? blob.handle
: null,
...cleanAppStateForExport(data.appState || {}),
...(localAppState
? calculateScrollCenter(data.elements || [], localAppState, null)
Expand Down
3 changes: 0 additions & 3 deletions src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ export type SocketUpdateDataIncoming =
type: "INVALID_RESPONSE";
};

// TODO: Make this part of `AppState`.
(window as any).handle = null;

const byteToHex = (byte: number): string => `0${byte.toString(16)}`.slice(-2);

const generateRandomID = async () => {
Expand Down
12 changes: 6 additions & 6 deletions src/data/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,23 +27,23 @@ export const serializeAsJSON = (
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
fileHandle: any,
) => {
const serialized = serializeAsJSON(elements, appState);
const blob = new Blob([serialized], {
type: "application/json",
});
const name = `${appState.name}.excalidraw`;
// TODO: Make this part of `AppState`.
(window as any).handle = await fileSave(

const fileHandle = await fileSave(
blob,
{
fileName: name,
fileName: appState.name,
description: "Excalidraw file",
extensions: [".excalidraw"],
},
fileHandle || null,
appState.fileHandle,
);

return { fileHandle };
};

export const loadFromJSON = async (localAppState: AppState) => {
Expand Down
2 changes: 1 addition & 1 deletion src/data/library.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { loadLibrary, saveLibrary } from "./localStorage";

export class Library {
/** imports library (currently merges, removing duplicates) */
static async importLibrary(blob: any) {
static async importLibrary(blob: Blob) {
const libraryFile = await loadLibraryFromBlob(blob);
if (!libraryFile || !libraryFile.library) {
return;
Expand Down
7 changes: 7 additions & 0 deletions src/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,10 @@ type CallableType<T extends (...args: any[]) => any> = (
type ForwardRef<T, P = any> = Parameters<
CallableType<React.ForwardRefRenderFunction<T, P>>
>[1];

// --------------------------------------------------------------------------—

interface Blob {
handle?: import("browser-nativefs").FileSystemHandle;
name?: string;
}
1 change: 0 additions & 1 deletion src/scene/browser-native.d.ts

This file was deleted.

Loading

0 comments on commit 1484c5a

Please sign in to comment.