Compare commits

...

2 Commits

Author SHA1 Message Date
dwelle
f5f4ec7528 remove fileHandle from IDB if user rejects permission 2021-06-13 17:59:18 +02:00
dwelle
ba705a099a persist fileHandle to IDB across sessions 2021-06-13 17:59:17 +02:00
7 changed files with 83 additions and 3 deletions

View File

@ -31,6 +31,7 @@
"clsx": "1.1.1",
"firebase": "8.3.3",
"i18next-browser-languagedetector": "6.1.0",
"idb-keyval": "5.0.6",
"lodash.throttle": "4.1.1",
"nanoid": "3.1.22",
"open-color": "1.8.0",

View File

@ -14,10 +14,11 @@ import { register } from "./register";
import { supported as fsSupported } from "browser-fs-access";
import { CheckboxItem } from "../components/CheckboxItem";
import { getExportSize } from "../scene/export";
import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES } from "../constants";
import { DEFAULT_EXPORT_PADDING, EXPORT_SCALES, IDB_KEYS } from "../constants";
import { getSelectedElements, isSomeElementSelected } from "../scene";
import { getNonDeletedElements } from "../element";
import { ActiveFile } from "../components/ActiveFile";
import * as idb from "idb-keyval";
export const actionChangeProjectName = register({
name: "changeProjectName",
@ -149,7 +150,22 @@ export const actionSaveToActiveFile = register({
if (error?.name !== "AbortError") {
console.error(error);
}
return { commitToHistory: false };
if (fileHandleExists && error.name === "AbortError") {
try {
await idb.del(IDB_KEYS.fileHandle);
} catch (error) {
console.error(error);
}
return {
commitToHistory: false,
appState: { ...appState, fileHandle: null },
};
}
return {
commitToHistory: false,
};
}
},
keyTest: (event) =>
@ -170,6 +186,13 @@ export const actionSaveFileToDisk = register({
...appState,
fileHandle: null,
});
try {
if (fileHandle) {
await idb.set(IDB_KEYS.fileHandle, fileHandle);
}
} catch (error) {
console.error(error);
}
return { commitToHistory: false, appState: { ...appState, fileHandle } };
} catch (error) {
if (error?.name !== "AbortError") {

View File

@ -52,6 +52,7 @@ import {
ENV,
EVENT,
GRID_SIZE,
IDB_KEYS,
LINE_CONFIRM_THRESHOLD,
MIME_TYPES,
MQ_MAX_HEIGHT_LANDSCAPE,
@ -194,6 +195,7 @@ import LayerUI from "./LayerUI";
import { Stats } from "./Stats";
import { Toast } from "./Toast";
import { actionToggleViewMode } from "../actions/actionToggleViewMode";
import * as idb from "idb-keyval";
const IsMobileContext = React.createContext(false);
export const useIsMobile = () => useContext(IsMobileContext);
@ -807,6 +809,15 @@ class App extends React.Component<AppProps, AppState> {
} else {
this.updateDOMRect(this.initializeScene);
}
try {
const fileHandle = await idb.get(IDB_KEYS.fileHandle);
if (fileHandle) {
this.setState({ fileHandle });
}
} catch (error) {
console.error(error);
}
}
public componentWillUnmount() {

View File

@ -97,6 +97,10 @@ export const STORAGE_KEYS = {
LOCAL_STORAGE_LIBRARY: "excalidraw-library",
} as const;
export const IDB_KEYS = {
fileHandle: "fileHandle",
} as const;
// time in milliseconds
export const TAP_TWICE_TIMEOUT = 300;
export const TOUCH_CTX_MENU_TIMEOUT = 500;

View File

@ -1,4 +1,4 @@
import { fileOpen, fileSave } from "browser-fs-access";
import { fileOpen, fileSave, FileSystemHandle } from "browser-fs-access";
import { cleanAppStateForExport } from "../appState";
import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants";
import { clearElementsForExport } from "../element";
@ -12,6 +12,7 @@ import {
ExportedLibraryData,
} from "./types";
import Library from "./library";
import { AbortError } from "../errors";
export const serializeAsJSON = (
elements: readonly ExcalidrawElement[],
@ -28,6 +29,26 @@ export const serializeAsJSON = (
return JSON.stringify(data, null, 2);
};
// adapted from https://web.dev/file-system-access
const verifyPermission = async (fileHandle: FileSystemHandle) => {
try {
const options = { mode: "readwrite" } as any;
// Check if permission was already granted. If so, return true.
if ((await fileHandle.queryPermission(options)) === "granted") {
return true;
}
// Request permission. If the user grants permission, return true.
if ((await fileHandle.requestPermission(options)) === "granted") {
return true;
}
// The user didn't grant permission, so return false.
return false;
} catch (error) {
console.error(error);
return false;
}
};
export const saveAsJSON = async (
elements: readonly ExcalidrawElement[],
appState: AppState,
@ -37,6 +58,12 @@ export const saveAsJSON = async (
type: MIME_TYPES.excalidraw,
});
if (appState.fileHandle) {
if (!(await verifyPermission(appState.fileHandle))) {
throw new AbortError();
}
}
const fileHandle = await fileSave(
blob,
{

View File

@ -1,4 +1,5 @@
type CANVAS_ERROR_NAMES = "CANVAS_ERROR" | "CANVAS_POSSIBLY_TOO_BIG";
export class CanvasError extends Error {
constructor(
message: string = "Couldn't export canvas.",
@ -9,3 +10,11 @@ export class CanvasError extends Error {
this.message = message;
}
}
export class AbortError extends Error {
constructor(message: string = "Request aborted") {
super();
this.name = "AbortError";
this.message = message;
}
}

View File

@ -6536,6 +6536,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1:
dependencies:
postcss "^7.0.14"
idb-keyval@5.0.6:
version "5.0.6"
resolved "https://registry.yarnpkg.com/idb-keyval/-/idb-keyval-5.0.6.tgz#62fe4a6703fb5ec86661f41330c94fda65e6d0e6"
integrity sha512-6lJuVbwyo82mKSH6Wq2eHkt9LcbwHAelMIcMe0tP4p20Pod7tTxq9zf0ge2n/YDfMOpDryerfmmYyuQiaFaKOg==
idb@3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz"