diff --git a/src/components/App.tsx b/src/components/App.tsx index 35519e19..4b4b6c92 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -714,6 +714,13 @@ class App extends React.Component { initialData = (await this.props.initialData) || null; } catch (error) { console.error(error); + initialData = { + appState: { + errorMessage: + error.message || + "Encountered an error during importing or restoring scene data", + }, + }; } const scene = restore(initialData, null); diff --git a/src/constants.ts b/src/constants.ts index f458705f..56e0f998 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -91,6 +91,8 @@ export const EXPORT_DATA_TYPES = { excalidrawLibrary: "excalidrawlib", } as const; +export const EXPORT_SOURCE = window.location.origin; + export const STORAGE_KEYS = { LOCAL_STORAGE_LIBRARY: "excalidraw-library", } as const; diff --git a/src/data/blob.ts b/src/data/blob.ts index 0a065038..a9a6cefa 100644 --- a/src/data/blob.ts +++ b/src/data/blob.ts @@ -7,7 +7,7 @@ import { calculateScrollCenter } from "../scene"; import { AppState } from "../types"; import { isValidExcalidrawData } from "./json"; import { restore } from "./restore"; -import { LibraryData } from "./types"; +import { ImportedLibraryData } from "./types"; const parseFileContents = async (blob: Blob | File) => { let contents: string; @@ -114,7 +114,7 @@ export const loadFromBlob = async ( export const loadLibraryFromBlob = async (blob: Blob) => { const contents = await parseFileContents(blob); - const data: LibraryData = JSON.parse(contents); + const data: ImportedLibraryData = JSON.parse(contents); if (data.type !== EXPORT_DATA_TYPES.excalidrawLibrary) { throw new Error(t("alerts.couldNotLoadInvalidFile")); } diff --git a/src/data/json.ts b/src/data/json.ts index 59ac5eb3..fd9f1a99 100644 --- a/src/data/json.ts +++ b/src/data/json.ts @@ -1,28 +1,31 @@ import { fileOpen, fileSave } from "browser-fs-access"; import { cleanAppStateForExport } from "../appState"; -import { EXPORT_DATA_TYPES, MIME_TYPES } from "../constants"; +import { EXPORT_DATA_TYPES, EXPORT_SOURCE, MIME_TYPES } from "../constants"; import { clearElementsForExport } from "../element"; import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; import { loadFromBlob } from "./blob"; import { Library } from "./library"; -import { ImportedDataState } from "./types"; +import { + ExportedDataState, + ImportedDataState, + ExportedLibraryData, +} from "./types"; export const serializeAsJSON = ( elements: readonly ExcalidrawElement[], appState: AppState, -): string => - JSON.stringify( - { - type: EXPORT_DATA_TYPES.excalidraw, - version: 2, - source: window.location.origin, - elements: clearElementsForExport(elements), - appState: cleanAppStateForExport(appState), - }, - null, - 2, - ); +): string => { + const data: ExportedDataState = { + type: EXPORT_DATA_TYPES.excalidraw, + version: 2, + source: EXPORT_SOURCE, + elements: clearElementsForExport(elements), + appState: cleanAppStateForExport(appState), + }; + + return JSON.stringify(data, null, 2); +}; export const saveAsJSON = async ( elements: readonly ExcalidrawElement[], @@ -87,15 +90,13 @@ export const isValidLibrary = (json: any) => { export const saveLibraryAsJSON = async () => { const library = await Library.loadLibrary(); - const serialized = JSON.stringify( - { - type: EXPORT_DATA_TYPES.excalidrawLibrary, - version: 1, - library, - }, - null, - 2, - ); + const data: ExportedLibraryData = { + type: EXPORT_DATA_TYPES.excalidrawLibrary, + version: 1, + source: EXPORT_SOURCE, + library, + }; + const serialized = JSON.stringify(data, null, 2); const fileName = "library.excalidrawlib"; const blob = new Blob([serialized], { type: MIME_TYPES.excalidrawlib, diff --git a/src/data/restore.ts b/src/data/restore.ts index 172cf58d..a63a48c7 100644 --- a/src/data/restore.ts +++ b/src/data/restore.ts @@ -4,7 +4,7 @@ import { ExcalidrawSelectionElement, } from "../element/types"; import { AppState, NormalizedZoomValue } from "../types"; -import { DataState, ImportedDataState } from "./types"; +import { ImportedDataState } from "./types"; import { isInvisiblySmallElement, getNormalizedDimensions } from "../element"; import { isLinearElementType } from "../element/typeChecks"; import { randomId } from "../random"; @@ -16,6 +16,16 @@ import { } from "../constants"; import { getDefaultAppState } from "../appState"; +type RestoredAppState = Omit< + AppState, + "offsetTop" | "offsetLeft" | "width" | "height" +>; + +export type RestoredDataState = { + elements: ExcalidrawElement[]; + appState: RestoredAppState; +}; + const getFontFamilyByName = (fontFamilyName: string): FontFamily => { for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) { if (fontFamilyString.includes(fontFamilyName)) { @@ -144,7 +154,7 @@ export const restoreElements = ( export const restoreAppState = ( appState: ImportedDataState["appState"], localAppState: Partial | null, -): DataState["appState"] => { +): RestoredAppState => { appState = appState || {}; const defaultAppState = getDefaultAppState(); @@ -186,7 +196,7 @@ export const restore = ( * Supply `null` if you can't get access to it. */ localAppState: Partial | null | undefined, -): DataState => { +): RestoredDataState => { return { elements: restoreElements(data?.elements), appState: restoreAppState(data?.appState, localAppState || null), diff --git a/src/data/types.ts b/src/data/types.ts index e414c7ca..6e5a17b2 100644 --- a/src/data/types.ts +++ b/src/data/types.ts @@ -1,26 +1,29 @@ import { ExcalidrawElement } from "../element/types"; import { AppState, LibraryItems } from "../types"; +import type { cleanAppStateForExport } from "../appState"; -export interface DataState { - type?: string; - version?: string; - source?: string; +export interface ExportedDataState { + type: string; + version: number; + source: string; elements: readonly ExcalidrawElement[]; - appState: Omit; + appState: ReturnType; } export interface ImportedDataState { type?: string; - version?: string; + version?: number; source?: string; - elements?: DataState["elements"] | null; - appState?: Partial | null; + elements?: readonly ExcalidrawElement[] | null; + appState?: Readonly> | null; scrollToContent?: boolean; } -export interface LibraryData { - type?: string; - version?: number; - source?: string; - library?: LibraryItems; +export interface ExportedLibraryData { + type: string; + version: number; + source: string; + library: LibraryItems; } + +export interface ImportedLibraryData extends Partial {} diff --git a/src/excalidraw-app/data/index.ts b/src/excalidraw-app/data/index.ts index 5769d780..6715f6b8 100644 --- a/src/excalidraw-app/data/index.ts +++ b/src/excalidraw-app/data/index.ts @@ -245,10 +245,10 @@ const importFromBackend = async ( export const loadScene = async ( id: string | null, privateKey: string | null, - // Supply initialData even if importing from backend to ensure we restore + // Supply local state even if importing from backend to ensure we restore // localStorage user settings which we do not persist on server. // Non-optional so we don't forget to pass it even if `undefined`. - initialData: ImportedDataState | undefined | null, + localDataState: ImportedDataState | undefined | null, ) => { let data; if (id != null) { @@ -256,10 +256,10 @@ export const loadScene = async ( // extra care not to leak it data = restore( await importFromBackend(id, privateKey), - initialData?.appState, + localDataState?.appState, ); } else { - data = restore(initialData || null, null); + data = restore(localDataState || null, null); } return { diff --git a/src/excalidraw-app/index.tsx b/src/excalidraw-app/index.tsx index 963e5039..3a9763af 100644 --- a/src/excalidraw-app/index.tsx +++ b/src/excalidraw-app/index.tsx @@ -19,7 +19,7 @@ import { VERSION_TIMEOUT, } from "../constants"; import { loadFromBlob } from "../data/blob"; -import { DataState, ImportedDataState } from "../data/types"; +import { ImportedDataState } from "../data/types"; import { ExcalidrawElement, NonDeletedExcalidrawElement, @@ -50,6 +50,7 @@ import { saveToLocalStorage, } from "./data/localStorage"; import CustomStats from "./CustomStats"; +import { RestoredDataState } from "../data/restore"; const languageDetector = new LanguageDetector(); languageDetector.init({ @@ -81,13 +82,11 @@ const initializeScene = async (opts: { ); const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/); - const initialData = importFromLocalStorage(); + const localDataState = importFromLocalStorage(); - let scene: DataState & { scrollToContent?: boolean } = await loadScene( - null, - null, - initialData, - ); + let scene: RestoredDataState & { + scrollToContent?: boolean; + } = await loadScene(null, null, localDataState); let roomLinkData = getCollaborationLinkData(window.location.href); const isExternalScene = !!(id || jsonBackendMatch || roomLinkData); @@ -102,12 +101,12 @@ const initializeScene = async (opts: { ) { // Backwards compatibility with legacy url format if (id) { - scene = await loadScene(id, null, initialData); + scene = await loadScene(id, null, localDataState); } else if (jsonBackendMatch) { scene = await loadScene( jsonBackendMatch[1], jsonBackendMatch[2], - initialData, + localDataState, ); } scene.scrollToContent = true; diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 23469809..71a2b354 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -18,6 +18,12 @@ Please add the latest change on the top under the correct section. - Recompute offsets on `scroll` of the nearest scrollable container [#3408](https://github.com/excalidraw/excalidraw/pull/3408). This can be disabled by setting [`detectScroll`](https://github.com/excalidraw/excalidraw/blob/master/src/packages/excalidraw/README.md#detectScroll) to `false`. - Add `onPaste` prop to handle custom clipboard behaviours [#3420](https://github.com/excalidraw/excalidraw/pull/3420). +## Types + +- Renamed the following types in case you depend on them (via [#3427](https://github.com/excalidraw/excalidraw/pull/3427)): + - `DataState` → `ExportedDataState` + - `LibraryData` → `ExportedLibraryData` + ## Excalidraw Library ### Features diff --git a/tsconfig.json b/tsconfig.json index e8d60c05..c8b09c92 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,5 +16,6 @@ "noEmit": true, "jsx": "react-jsx" }, - "include": ["src"] + "include": ["src"], + "exclude": ["src/packages/excalidraw/types"] }