mirror of
				https://github.com/excalidraw/excalidraw.git
				synced 2025-10-31 10:54:33 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			v0.11.0
			...
			persist_fi
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | f5f4ec7528 | ||
|   | ba705a099a | 
| @@ -31,6 +31,7 @@ | |||||||
|     "clsx": "1.1.1", |     "clsx": "1.1.1", | ||||||
|     "firebase": "8.3.3", |     "firebase": "8.3.3", | ||||||
|     "i18next-browser-languagedetector": "6.1.0", |     "i18next-browser-languagedetector": "6.1.0", | ||||||
|  |     "idb-keyval": "5.0.6", | ||||||
|     "lodash.throttle": "4.1.1", |     "lodash.throttle": "4.1.1", | ||||||
|     "nanoid": "3.1.22", |     "nanoid": "3.1.22", | ||||||
|     "open-color": "1.8.0", |     "open-color": "1.8.0", | ||||||
|   | |||||||
| @@ -14,10 +14,11 @@ import { register } from "./register"; | |||||||
| import { supported as fsSupported } from "browser-fs-access"; | import { supported as fsSupported } from "browser-fs-access"; | ||||||
| import { CheckboxItem } from "../components/CheckboxItem"; | import { CheckboxItem } from "../components/CheckboxItem"; | ||||||
| import { getExportSize } from "../scene/export"; | 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 { getSelectedElements, isSomeElementSelected } from "../scene"; | ||||||
| import { getNonDeletedElements } from "../element"; | import { getNonDeletedElements } from "../element"; | ||||||
| import { ActiveFile } from "../components/ActiveFile"; | import { ActiveFile } from "../components/ActiveFile"; | ||||||
|  | import * as idb from "idb-keyval"; | ||||||
|  |  | ||||||
| export const actionChangeProjectName = register({ | export const actionChangeProjectName = register({ | ||||||
|   name: "changeProjectName", |   name: "changeProjectName", | ||||||
| @@ -149,7 +150,22 @@ export const actionSaveToActiveFile = register({ | |||||||
|       if (error?.name !== "AbortError") { |       if (error?.name !== "AbortError") { | ||||||
|         console.error(error); |         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) => |   keyTest: (event) => | ||||||
| @@ -170,6 +186,13 @@ export const actionSaveFileToDisk = register({ | |||||||
|         ...appState, |         ...appState, | ||||||
|         fileHandle: null, |         fileHandle: null, | ||||||
|       }); |       }); | ||||||
|  |       try { | ||||||
|  |         if (fileHandle) { | ||||||
|  |           await idb.set(IDB_KEYS.fileHandle, fileHandle); | ||||||
|  |         } | ||||||
|  |       } catch (error) { | ||||||
|  |         console.error(error); | ||||||
|  |       } | ||||||
|       return { commitToHistory: false, appState: { ...appState, fileHandle } }; |       return { commitToHistory: false, appState: { ...appState, fileHandle } }; | ||||||
|     } catch (error) { |     } catch (error) { | ||||||
|       if (error?.name !== "AbortError") { |       if (error?.name !== "AbortError") { | ||||||
|   | |||||||
| @@ -52,6 +52,7 @@ import { | |||||||
|   ENV, |   ENV, | ||||||
|   EVENT, |   EVENT, | ||||||
|   GRID_SIZE, |   GRID_SIZE, | ||||||
|  |   IDB_KEYS, | ||||||
|   LINE_CONFIRM_THRESHOLD, |   LINE_CONFIRM_THRESHOLD, | ||||||
|   MIME_TYPES, |   MIME_TYPES, | ||||||
|   MQ_MAX_HEIGHT_LANDSCAPE, |   MQ_MAX_HEIGHT_LANDSCAPE, | ||||||
| @@ -194,6 +195,7 @@ import LayerUI from "./LayerUI"; | |||||||
| import { Stats } from "./Stats"; | import { Stats } from "./Stats"; | ||||||
| import { Toast } from "./Toast"; | import { Toast } from "./Toast"; | ||||||
| import { actionToggleViewMode } from "../actions/actionToggleViewMode"; | import { actionToggleViewMode } from "../actions/actionToggleViewMode"; | ||||||
|  | import * as idb from "idb-keyval"; | ||||||
|  |  | ||||||
| const IsMobileContext = React.createContext(false); | const IsMobileContext = React.createContext(false); | ||||||
| export const useIsMobile = () => useContext(IsMobileContext); | export const useIsMobile = () => useContext(IsMobileContext); | ||||||
| @@ -807,6 +809,15 @@ class App extends React.Component<AppProps, AppState> { | |||||||
|     } else { |     } else { | ||||||
|       this.updateDOMRect(this.initializeScene); |       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() { |   public componentWillUnmount() { | ||||||
|   | |||||||
| @@ -97,6 +97,10 @@ export const STORAGE_KEYS = { | |||||||
|   LOCAL_STORAGE_LIBRARY: "excalidraw-library", |   LOCAL_STORAGE_LIBRARY: "excalidraw-library", | ||||||
| } as const; | } as const; | ||||||
|  |  | ||||||
|  | export const IDB_KEYS = { | ||||||
|  |   fileHandle: "fileHandle", | ||||||
|  | } as const; | ||||||
|  |  | ||||||
| // time in milliseconds | // time in milliseconds | ||||||
| export const TAP_TWICE_TIMEOUT = 300; | export const TAP_TWICE_TIMEOUT = 300; | ||||||
| export const TOUCH_CTX_MENU_TIMEOUT = 500; | export const TOUCH_CTX_MENU_TIMEOUT = 500; | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| import { fileOpen, fileSave } from "browser-fs-access"; | import { fileOpen, fileSave, FileSystemHandle } from "browser-fs-access"; | ||||||
| import { cleanAppStateForExport } from "../appState"; | import { cleanAppStateForExport } from "../appState"; | ||||||
| import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants"; | import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants"; | ||||||
| import { clearElementsForExport } from "../element"; | import { clearElementsForExport } from "../element"; | ||||||
| @@ -12,6 +12,7 @@ import { | |||||||
|   ExportedLibraryData, |   ExportedLibraryData, | ||||||
| } from "./types"; | } from "./types"; | ||||||
| import Library from "./library"; | import Library from "./library"; | ||||||
|  | import { AbortError } from "../errors"; | ||||||
|  |  | ||||||
| export const serializeAsJSON = ( | export const serializeAsJSON = ( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly ExcalidrawElement[], | ||||||
| @@ -28,6 +29,26 @@ export const serializeAsJSON = ( | |||||||
|   return JSON.stringify(data, null, 2); |   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 ( | export const saveAsJSON = async ( | ||||||
|   elements: readonly ExcalidrawElement[], |   elements: readonly ExcalidrawElement[], | ||||||
|   appState: AppState, |   appState: AppState, | ||||||
| @@ -37,6 +58,12 @@ export const saveAsJSON = async ( | |||||||
|     type: MIME_TYPES.excalidraw, |     type: MIME_TYPES.excalidraw, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   if (appState.fileHandle) { | ||||||
|  |     if (!(await verifyPermission(appState.fileHandle))) { | ||||||
|  |       throw new AbortError(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   const fileHandle = await fileSave( |   const fileHandle = await fileSave( | ||||||
|     blob, |     blob, | ||||||
|     { |     { | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| type CANVAS_ERROR_NAMES = "CANVAS_ERROR" | "CANVAS_POSSIBLY_TOO_BIG"; | type CANVAS_ERROR_NAMES = "CANVAS_ERROR" | "CANVAS_POSSIBLY_TOO_BIG"; | ||||||
|  |  | ||||||
| export class CanvasError extends Error { | export class CanvasError extends Error { | ||||||
|   constructor( |   constructor( | ||||||
|     message: string = "Couldn't export canvas.", |     message: string = "Couldn't export canvas.", | ||||||
| @@ -9,3 +10,11 @@ export class CanvasError extends Error { | |||||||
|     this.message = message; |     this.message = message; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | export class AbortError extends Error { | ||||||
|  |   constructor(message: string = "Request aborted") { | ||||||
|  |     super(); | ||||||
|  |     this.name = "AbortError"; | ||||||
|  |     this.message = message; | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -6536,6 +6536,11 @@ icss-utils@^4.0.0, icss-utils@^4.1.1: | |||||||
|   dependencies: |   dependencies: | ||||||
|     postcss "^7.0.14" |     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: | idb@3.0.2: | ||||||
|   version "3.0.2" |   version "3.0.2" | ||||||
|   resolved "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz" |   resolved "https://registry.npmjs.org/idb/-/idb-3.0.2.tgz" | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user