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

This commit is contained in:
David Luzar 2021-04-10 19:17:49 +02:00 committed by GitHub
parent c19c8ecd27
commit a7cbe68ae8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 84 additions and 55 deletions

View File

@ -714,6 +714,13 @@ class App extends React.Component<AppProps, AppState> {
initialData = (await this.props.initialData) || null; initialData = (await this.props.initialData) || null;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
initialData = {
appState: {
errorMessage:
error.message ||
"Encountered an error during importing or restoring scene data",
},
};
} }
const scene = restore(initialData, null); const scene = restore(initialData, null);

View File

@ -91,6 +91,8 @@ export const EXPORT_DATA_TYPES = {
excalidrawLibrary: "excalidrawlib", excalidrawLibrary: "excalidrawlib",
} as const; } as const;
export const EXPORT_SOURCE = window.location.origin;
export const STORAGE_KEYS = { export const STORAGE_KEYS = {
LOCAL_STORAGE_LIBRARY: "excalidraw-library", LOCAL_STORAGE_LIBRARY: "excalidraw-library",
} as const; } as const;

View File

@ -7,7 +7,7 @@ import { calculateScrollCenter } from "../scene";
import { AppState } from "../types"; import { AppState } from "../types";
import { isValidExcalidrawData } from "./json"; import { isValidExcalidrawData } from "./json";
import { restore } from "./restore"; import { restore } from "./restore";
import { LibraryData } from "./types"; import { ImportedLibraryData } from "./types";
const parseFileContents = async (blob: Blob | File) => { const parseFileContents = async (blob: Blob | File) => {
let contents: string; let contents: string;
@ -114,7 +114,7 @@ export const loadFromBlob = async (
export const loadLibraryFromBlob = async (blob: Blob) => { export const loadLibraryFromBlob = async (blob: Blob) => {
const contents = await parseFileContents(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) { if (data.type !== EXPORT_DATA_TYPES.excalidrawLibrary) {
throw new Error(t("alerts.couldNotLoadInvalidFile")); throw new Error(t("alerts.couldNotLoadInvalidFile"));
} }

View File

@ -1,28 +1,31 @@
import { fileOpen, fileSave } from "browser-fs-access"; import { fileOpen, fileSave } from "browser-fs-access";
import { cleanAppStateForExport } from "../appState"; 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 { clearElementsForExport } from "../element";
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { AppState } from "../types"; import { AppState } from "../types";
import { loadFromBlob } from "./blob"; import { loadFromBlob } from "./blob";
import { Library } from "./library"; import { Library } from "./library";
import { ImportedDataState } from "./types"; import {
ExportedDataState,
ImportedDataState,
ExportedLibraryData,
} from "./types";
export const serializeAsJSON = ( export const serializeAsJSON = (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
appState: AppState, appState: AppState,
): string => ): string => {
JSON.stringify( const data: ExportedDataState = {
{ type: EXPORT_DATA_TYPES.excalidraw,
type: EXPORT_DATA_TYPES.excalidraw, version: 2,
version: 2, source: EXPORT_SOURCE,
source: window.location.origin, elements: clearElementsForExport(elements),
elements: clearElementsForExport(elements), appState: cleanAppStateForExport(appState),
appState: cleanAppStateForExport(appState), };
},
null, return JSON.stringify(data, null, 2);
2, };
);
export const saveAsJSON = async ( export const saveAsJSON = async (
elements: readonly ExcalidrawElement[], elements: readonly ExcalidrawElement[],
@ -87,15 +90,13 @@ export const isValidLibrary = (json: any) => {
export const saveLibraryAsJSON = async () => { export const saveLibraryAsJSON = async () => {
const library = await Library.loadLibrary(); const library = await Library.loadLibrary();
const serialized = JSON.stringify( const data: ExportedLibraryData = {
{ type: EXPORT_DATA_TYPES.excalidrawLibrary,
type: EXPORT_DATA_TYPES.excalidrawLibrary, version: 1,
version: 1, source: EXPORT_SOURCE,
library, library,
}, };
null, const serialized = JSON.stringify(data, null, 2);
2,
);
const fileName = "library.excalidrawlib"; const fileName = "library.excalidrawlib";
const blob = new Blob([serialized], { const blob = new Blob([serialized], {
type: MIME_TYPES.excalidrawlib, type: MIME_TYPES.excalidrawlib,

View File

@ -4,7 +4,7 @@ import {
ExcalidrawSelectionElement, ExcalidrawSelectionElement,
} from "../element/types"; } from "../element/types";
import { AppState, NormalizedZoomValue } from "../types"; import { AppState, NormalizedZoomValue } from "../types";
import { DataState, ImportedDataState } from "./types"; import { ImportedDataState } from "./types";
import { isInvisiblySmallElement, getNormalizedDimensions } from "../element"; import { isInvisiblySmallElement, getNormalizedDimensions } from "../element";
import { isLinearElementType } from "../element/typeChecks"; import { isLinearElementType } from "../element/typeChecks";
import { randomId } from "../random"; import { randomId } from "../random";
@ -16,6 +16,16 @@ import {
} from "../constants"; } from "../constants";
import { getDefaultAppState } from "../appState"; import { getDefaultAppState } from "../appState";
type RestoredAppState = Omit<
AppState,
"offsetTop" | "offsetLeft" | "width" | "height"
>;
export type RestoredDataState = {
elements: ExcalidrawElement[];
appState: RestoredAppState;
};
const getFontFamilyByName = (fontFamilyName: string): FontFamily => { const getFontFamilyByName = (fontFamilyName: string): FontFamily => {
for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) { for (const [id, fontFamilyString] of Object.entries(FONT_FAMILY)) {
if (fontFamilyString.includes(fontFamilyName)) { if (fontFamilyString.includes(fontFamilyName)) {
@ -144,7 +154,7 @@ export const restoreElements = (
export const restoreAppState = ( export const restoreAppState = (
appState: ImportedDataState["appState"], appState: ImportedDataState["appState"],
localAppState: Partial<AppState> | null, localAppState: Partial<AppState> | null,
): DataState["appState"] => { ): RestoredAppState => {
appState = appState || {}; appState = appState || {};
const defaultAppState = getDefaultAppState(); const defaultAppState = getDefaultAppState();
@ -186,7 +196,7 @@ export const restore = (
* Supply `null` if you can't get access to it. * Supply `null` if you can't get access to it.
*/ */
localAppState: Partial<AppState> | null | undefined, localAppState: Partial<AppState> | null | undefined,
): DataState => { ): RestoredDataState => {
return { return {
elements: restoreElements(data?.elements), elements: restoreElements(data?.elements),
appState: restoreAppState(data?.appState, localAppState || null), appState: restoreAppState(data?.appState, localAppState || null),

View File

@ -1,26 +1,29 @@
import { ExcalidrawElement } from "../element/types"; import { ExcalidrawElement } from "../element/types";
import { AppState, LibraryItems } from "../types"; import { AppState, LibraryItems } from "../types";
import type { cleanAppStateForExport } from "../appState";
export interface DataState { export interface ExportedDataState {
type?: string; type: string;
version?: string; version: number;
source?: string; source: string;
elements: readonly ExcalidrawElement[]; elements: readonly ExcalidrawElement[];
appState: Omit<AppState, "offsetTop" | "offsetLeft" | "width" | "height">; appState: ReturnType<typeof cleanAppStateForExport>;
} }
export interface ImportedDataState { export interface ImportedDataState {
type?: string; type?: string;
version?: string; version?: number;
source?: string; source?: string;
elements?: DataState["elements"] | null; elements?: readonly ExcalidrawElement[] | null;
appState?: Partial<DataState["appState"]> | null; appState?: Readonly<Partial<AppState>> | null;
scrollToContent?: boolean; scrollToContent?: boolean;
} }
export interface LibraryData { export interface ExportedLibraryData {
type?: string; type: string;
version?: number; version: number;
source?: string; source: string;
library?: LibraryItems; library: LibraryItems;
} }
export interface ImportedLibraryData extends Partial<ExportedLibraryData> {}

View File

@ -245,10 +245,10 @@ const importFromBackend = async (
export const loadScene = async ( export const loadScene = async (
id: string | null, id: string | null,
privateKey: 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. // localStorage user settings which we do not persist on server.
// Non-optional so we don't forget to pass it even if `undefined`. // Non-optional so we don't forget to pass it even if `undefined`.
initialData: ImportedDataState | undefined | null, localDataState: ImportedDataState | undefined | null,
) => { ) => {
let data; let data;
if (id != null) { if (id != null) {
@ -256,10 +256,10 @@ export const loadScene = async (
// extra care not to leak it // extra care not to leak it
data = restore( data = restore(
await importFromBackend(id, privateKey), await importFromBackend(id, privateKey),
initialData?.appState, localDataState?.appState,
); );
} else { } else {
data = restore(initialData || null, null); data = restore(localDataState || null, null);
} }
return { return {

View File

@ -19,7 +19,7 @@ import {
VERSION_TIMEOUT, VERSION_TIMEOUT,
} from "../constants"; } from "../constants";
import { loadFromBlob } from "../data/blob"; import { loadFromBlob } from "../data/blob";
import { DataState, ImportedDataState } from "../data/types"; import { ImportedDataState } from "../data/types";
import { import {
ExcalidrawElement, ExcalidrawElement,
NonDeletedExcalidrawElement, NonDeletedExcalidrawElement,
@ -50,6 +50,7 @@ import {
saveToLocalStorage, saveToLocalStorage,
} from "./data/localStorage"; } from "./data/localStorage";
import CustomStats from "./CustomStats"; import CustomStats from "./CustomStats";
import { RestoredDataState } from "../data/restore";
const languageDetector = new LanguageDetector(); const languageDetector = new LanguageDetector();
languageDetector.init({ languageDetector.init({
@ -81,13 +82,11 @@ const initializeScene = async (opts: {
); );
const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/); const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/);
const initialData = importFromLocalStorage(); const localDataState = importFromLocalStorage();
let scene: DataState & { scrollToContent?: boolean } = await loadScene( let scene: RestoredDataState & {
null, scrollToContent?: boolean;
null, } = await loadScene(null, null, localDataState);
initialData,
);
let roomLinkData = getCollaborationLinkData(window.location.href); let roomLinkData = getCollaborationLinkData(window.location.href);
const isExternalScene = !!(id || jsonBackendMatch || roomLinkData); const isExternalScene = !!(id || jsonBackendMatch || roomLinkData);
@ -102,12 +101,12 @@ const initializeScene = async (opts: {
) { ) {
// Backwards compatibility with legacy url format // Backwards compatibility with legacy url format
if (id) { if (id) {
scene = await loadScene(id, null, initialData); scene = await loadScene(id, null, localDataState);
} else if (jsonBackendMatch) { } else if (jsonBackendMatch) {
scene = await loadScene( scene = await loadScene(
jsonBackendMatch[1], jsonBackendMatch[1],
jsonBackendMatch[2], jsonBackendMatch[2],
initialData, localDataState,
); );
} }
scene.scrollToContent = true; scene.scrollToContent = true;

View File

@ -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`. - 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). - 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 ## Excalidraw Library
### Features ### Features

View File

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