make clearing state for storage more type-safe (#1884)
This commit is contained in:
parent
6428b59ccb
commit
0ee2c15929
113
src/appState.ts
113
src/appState.ts
@ -62,30 +62,95 @@ export const getDefaultAppState = (): AppState => {
|
||||
};
|
||||
};
|
||||
|
||||
export const clearAppStateForLocalStorage = (appState: AppState) => {
|
||||
const {
|
||||
draggingElement,
|
||||
resizingElement,
|
||||
multiElement,
|
||||
editingElement,
|
||||
selectionElement,
|
||||
isResizing,
|
||||
isRotating,
|
||||
collaborators,
|
||||
isCollaborating,
|
||||
isLoading,
|
||||
errorMessage,
|
||||
showShortcutsDialog,
|
||||
editingLinearElement,
|
||||
isLibraryOpen,
|
||||
...exportedState
|
||||
} = appState;
|
||||
return exportedState;
|
||||
/**
|
||||
* Config containing all AppState keys. Used to determine whether given state
|
||||
* prop should be stripped when exporting to given storage type.
|
||||
*/
|
||||
const APP_STATE_STORAGE_CONF = (<
|
||||
Values extends {
|
||||
/** whether to keep when storing to browser storage (localStorage/IDB) */
|
||||
browser: boolean;
|
||||
/** whether to keep when exporting to file/database */
|
||||
export: boolean;
|
||||
},
|
||||
T extends Record<keyof AppState, Values>
|
||||
>(
|
||||
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
|
||||
) => config)({
|
||||
collaborators: { browser: false, export: false },
|
||||
currentItemBackgroundColor: { browser: true, export: false },
|
||||
currentItemFillStyle: { browser: true, export: false },
|
||||
currentItemFontFamily: { browser: true, export: false },
|
||||
currentItemFontSize: { browser: true, export: false },
|
||||
currentItemOpacity: { browser: true, export: false },
|
||||
currentItemRoughness: { browser: true, export: false },
|
||||
currentItemStrokeColor: { browser: true, export: false },
|
||||
currentItemStrokeStyle: { browser: true, export: false },
|
||||
currentItemStrokeWidth: { browser: true, export: false },
|
||||
currentItemTextAlign: { browser: true, export: false },
|
||||
cursorButton: { browser: true, export: false },
|
||||
cursorX: { browser: true, export: false },
|
||||
cursorY: { browser: true, export: false },
|
||||
draggingElement: { browser: false, export: false },
|
||||
editingElement: { browser: false, export: false },
|
||||
editingGroupId: { browser: true, export: false },
|
||||
editingLinearElement: { browser: false, export: false },
|
||||
elementLocked: { browser: true, export: false },
|
||||
elementType: { browser: true, export: false },
|
||||
errorMessage: { browser: false, export: false },
|
||||
exportBackground: { browser: true, export: false },
|
||||
gridSize: { browser: true, export: true },
|
||||
height: { browser: false, export: false },
|
||||
isCollaborating: { browser: false, export: false },
|
||||
isLibraryOpen: { browser: false, export: false },
|
||||
isLoading: { browser: false, export: false },
|
||||
isResizing: { browser: false, export: false },
|
||||
isRotating: { browser: false, export: false },
|
||||
lastPointerDownWith: { browser: true, export: false },
|
||||
multiElement: { browser: false, export: false },
|
||||
name: { browser: true, export: false },
|
||||
openMenu: { browser: true, export: false },
|
||||
previousSelectedElementIds: { browser: true, export: false },
|
||||
resizingElement: { browser: false, export: false },
|
||||
scrolledOutside: { browser: true, export: false },
|
||||
scrollX: { browser: true, export: false },
|
||||
scrollY: { browser: true, export: false },
|
||||
selectedElementIds: { browser: true, export: false },
|
||||
selectedGroupIds: { browser: true, export: false },
|
||||
selectionElement: { browser: false, export: false },
|
||||
shouldAddWatermark: { browser: true, export: false },
|
||||
shouldCacheIgnoreZoom: { browser: true, export: false },
|
||||
showShortcutsDialog: { browser: false, export: false },
|
||||
username: { browser: true, export: false },
|
||||
viewBackgroundColor: { browser: true, export: true },
|
||||
width: { browser: false, export: false },
|
||||
zenModeEnabled: { browser: true, export: false },
|
||||
zoom: { browser: true, export: false },
|
||||
});
|
||||
|
||||
const _clearAppStateForStorage = <ExportType extends "export" | "browser">(
|
||||
appState: Partial<AppState>,
|
||||
exportType: ExportType,
|
||||
) => {
|
||||
type ExportableKeys = {
|
||||
[K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
|
||||
? K
|
||||
: never;
|
||||
}[keyof typeof APP_STATE_STORAGE_CONF];
|
||||
const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
|
||||
for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
|
||||
if (APP_STATE_STORAGE_CONF[key][exportType]) {
|
||||
// @ts-ignore see https://github.com/microsoft/TypeScript/issues/31445
|
||||
stateForExport[key] = appState[key];
|
||||
}
|
||||
}
|
||||
return stateForExport;
|
||||
};
|
||||
|
||||
export const cleanAppStateForExport = (appState: AppState) => {
|
||||
return {
|
||||
viewBackgroundColor: appState.viewBackgroundColor,
|
||||
gridSize: appState.gridSize,
|
||||
};
|
||||
export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "browser");
|
||||
};
|
||||
|
||||
export const cleanAppStateForExport = (appState: Partial<AppState>) => {
|
||||
return _clearAppStateForStorage(appState, "export");
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { getDefaultAppState } from "../appState";
|
||||
import { getDefaultAppState, cleanAppStateForExport } from "../appState";
|
||||
import { restore } from "./restore";
|
||||
import { t } from "../i18n";
|
||||
import { AppState } from "../types";
|
||||
|
||||
export const loadFromBlob = async (blob: any) => {
|
||||
const updateAppState = (contents: string) => {
|
||||
@ -13,7 +14,10 @@ export const loadFromBlob = async (blob: any) => {
|
||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||
}
|
||||
elements = data.elements || [];
|
||||
appState = { ...defaultAppState, ...data.appState };
|
||||
appState = {
|
||||
...defaultAppState,
|
||||
...cleanAppStateForExport(data.appState as Partial<AppState>),
|
||||
};
|
||||
} catch {
|
||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ export const loadScene = async (id: string | null, privateKey?: string) => {
|
||||
|
||||
return {
|
||||
elements: data.elements,
|
||||
appState: data.appState && { ...data.appState },
|
||||
appState: data.appState,
|
||||
commitToHistory: false,
|
||||
};
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { AppState, LibraryItems } from "../types";
|
||||
import { clearAppStateForLocalStorage } from "../appState";
|
||||
import { clearAppStateForLocalStorage, getDefaultAppState } from "../appState";
|
||||
import { restore } from "./restore";
|
||||
|
||||
const LOCAL_STORAGE_KEY = "excalidraw";
|
||||
@ -111,7 +111,8 @@ export const restoreFromLocalStorage = () => {
|
||||
if (savedElements) {
|
||||
try {
|
||||
elements = JSON.parse(savedElements);
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// Do nothing because elements array is already empty
|
||||
}
|
||||
}
|
||||
@ -119,13 +120,14 @@ export const restoreFromLocalStorage = () => {
|
||||
let appState = null;
|
||||
if (savedState) {
|
||||
try {
|
||||
appState = JSON.parse(savedState) as AppState;
|
||||
// If we're retrieving from local storage, we should not be collaborating
|
||||
appState.isCollaborating = false;
|
||||
appState.collaborators = new Map();
|
||||
delete appState.width;
|
||||
delete appState.height;
|
||||
} catch {
|
||||
appState = {
|
||||
...getDefaultAppState(),
|
||||
...clearAppStateForLocalStorage(
|
||||
JSON.parse(savedState) as Partial<AppState>,
|
||||
),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
// Do nothing because appState is already null
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user