Browse Source

refactor: improve types around dataState and libraryData (#3427)

David Luzar 4 năm trước cách đây
mục cha
commit
a7cbe68ae8

+ 7 - 0
src/components/App.tsx

@@ -714,6 +714,13 @@ class App extends React.Component<AppProps, AppState> {
       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);

+ 2 - 0
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;

+ 2 - 2
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"));
   }

+ 24 - 23
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,

+ 13 - 3
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<AppState> | 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<AppState> | null | undefined,
-): DataState => {
+): RestoredDataState => {
   return {
     elements: restoreElements(data?.elements),
     appState: restoreAppState(data?.appState, localAppState || null),

+ 16 - 13
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, "offsetTop" | "offsetLeft" | "width" | "height">;
+  appState: ReturnType<typeof cleanAppStateForExport>;
 }
 
 export interface ImportedDataState {
   type?: string;
-  version?: string;
+  version?: number;
   source?: string;
-  elements?: DataState["elements"] | null;
-  appState?: Partial<DataState["appState"]> | null;
+  elements?: readonly ExcalidrawElement[] | null;
+  appState?: Readonly<Partial<AppState>> | 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<ExportedLibraryData> {}

+ 4 - 4
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 {

+ 8 - 9
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;

+ 6 - 0
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

+ 2 - 1
tsconfig.json

@@ -16,5 +16,6 @@
     "noEmit": true,
     "jsx": "react-jsx"
   },
-  "include": ["src"]
+  "include": ["src"],
+  "exclude": ["src/packages/excalidraw/types"]
 }