refactor: improve types around dataState and libraryData (#3427)
This commit is contained in:
parent
c19c8ecd27
commit
a7cbe68ae8
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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"));
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
@ -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> {}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -16,5 +16,6 @@
|
|||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx"
|
"jsx": "react-jsx"
|
||||||
},
|
},
|
||||||
"include": ["src"]
|
"include": ["src"],
|
||||||
|
"exclude": ["src/packages/excalidraw/types"]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user