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 {
|
* Config containing all AppState keys. Used to determine whether given state
|
||||||
draggingElement,
|
* prop should be stripped when exporting to given storage type.
|
||||||
resizingElement,
|
*/
|
||||||
multiElement,
|
const APP_STATE_STORAGE_CONF = (<
|
||||||
editingElement,
|
Values extends {
|
||||||
selectionElement,
|
/** whether to keep when storing to browser storage (localStorage/IDB) */
|
||||||
isResizing,
|
browser: boolean;
|
||||||
isRotating,
|
/** whether to keep when exporting to file/database */
|
||||||
collaborators,
|
export: boolean;
|
||||||
isCollaborating,
|
},
|
||||||
isLoading,
|
T extends Record<keyof AppState, Values>
|
||||||
errorMessage,
|
>(
|
||||||
showShortcutsDialog,
|
config: { [K in keyof T]: K extends keyof AppState ? T[K] : never },
|
||||||
editingLinearElement,
|
) => config)({
|
||||||
isLibraryOpen,
|
collaborators: { browser: false, export: false },
|
||||||
...exportedState
|
currentItemBackgroundColor: { browser: true, export: false },
|
||||||
} = appState;
|
currentItemFillStyle: { browser: true, export: false },
|
||||||
return exportedState;
|
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) => {
|
export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
|
||||||
return {
|
return _clearAppStateForStorage(appState, "browser");
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
};
|
||||||
gridSize: appState.gridSize,
|
|
||||||
};
|
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 { restore } from "./restore";
|
||||||
import { t } from "../i18n";
|
import { t } from "../i18n";
|
||||||
|
import { AppState } from "../types";
|
||||||
|
|
||||||
export const loadFromBlob = async (blob: any) => {
|
export const loadFromBlob = async (blob: any) => {
|
||||||
const updateAppState = (contents: string) => {
|
const updateAppState = (contents: string) => {
|
||||||
@ -13,7 +14,10 @@ export const loadFromBlob = async (blob: any) => {
|
|||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
}
|
}
|
||||||
elements = data.elements || [];
|
elements = data.elements || [];
|
||||||
appState = { ...defaultAppState, ...data.appState };
|
appState = {
|
||||||
|
...defaultAppState,
|
||||||
|
...cleanAppStateForExport(data.appState as Partial<AppState>),
|
||||||
|
};
|
||||||
} catch {
|
} catch {
|
||||||
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
throw new Error(t("alerts.couldNotLoadInvalidFile"));
|
||||||
}
|
}
|
||||||
|
@ -374,7 +374,7 @@ export const loadScene = async (id: string | null, privateKey?: string) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
elements: data.elements,
|
elements: data.elements,
|
||||||
appState: data.appState && { ...data.appState },
|
appState: data.appState,
|
||||||
commitToHistory: false,
|
commitToHistory: false,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState, LibraryItems } from "../types";
|
import { AppState, LibraryItems } from "../types";
|
||||||
import { clearAppStateForLocalStorage } from "../appState";
|
import { clearAppStateForLocalStorage, getDefaultAppState } from "../appState";
|
||||||
import { restore } from "./restore";
|
import { restore } from "./restore";
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = "excalidraw";
|
const LOCAL_STORAGE_KEY = "excalidraw";
|
||||||
@ -111,7 +111,8 @@ export const restoreFromLocalStorage = () => {
|
|||||||
if (savedElements) {
|
if (savedElements) {
|
||||||
try {
|
try {
|
||||||
elements = JSON.parse(savedElements);
|
elements = JSON.parse(savedElements);
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
// Do nothing because elements array is already empty
|
// Do nothing because elements array is already empty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -119,13 +120,14 @@ export const restoreFromLocalStorage = () => {
|
|||||||
let appState = null;
|
let appState = null;
|
||||||
if (savedState) {
|
if (savedState) {
|
||||||
try {
|
try {
|
||||||
appState = JSON.parse(savedState) as AppState;
|
appState = {
|
||||||
// If we're retrieving from local storage, we should not be collaborating
|
...getDefaultAppState(),
|
||||||
appState.isCollaborating = false;
|
...clearAppStateForLocalStorage(
|
||||||
appState.collaborators = new Map();
|
JSON.parse(savedState) as Partial<AppState>,
|
||||||
delete appState.width;
|
),
|
||||||
delete appState.height;
|
};
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
// Do nothing because appState is already null
|
// Do nothing because appState is already null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user