refactor: remove payload-level schema version

This commit is contained in:
Ryan Di
2026-03-23 17:30:11 +11:00
parent 11ba6784aa
commit 0e4ae079ac
12 changed files with 23 additions and 183 deletions
+9 -54
View File
@@ -53,11 +53,6 @@ import {
restoreAppState,
restoreElements,
} from "@excalidraw/excalidraw/data/restore";
import {
hasElementSchemaVersion,
migrateSceneElements,
SCHEMA_VERSIONS,
} from "@excalidraw/excalidraw/data/schema";
import { newElementWith } from "@excalidraw/element";
import { isInitializedImageElement } from "@excalidraw/element";
import clsx from "clsx";
@@ -232,17 +227,6 @@ const initializeScene = async (opts: {
const externalUrlMatch = window.location.hash.match(/^#url=(.*)$/);
const localDataState = importFromLocalStorage();
const localStorageSchemaVersionSource = hasElementSchemaVersion(
localDataState?.elements,
)
? {
payloadSchemaVersion: undefined,
fallbackVersion: SCHEMA_VERSIONS.initial,
}
: {
payloadSchemaVersion: localDataState?.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
};
let scene: Omit<
RestoredDataState,
@@ -252,17 +236,10 @@ const initializeScene = async (opts: {
> & {
scrollToContent?: boolean;
} = {
elements: restoreElements(
migrateSceneElements(
localDataState?.elements,
localStorageSchemaVersionSource,
),
null,
{
repairBindings: true,
deleteInvisibleElements: true,
},
),
elements: restoreElements(localDataState?.elements, null, {
repairBindings: true,
deleteInvisibleElements: true,
}),
appState: restoreAppState(localDataState?.appState, null),
};
@@ -285,17 +262,10 @@ const initializeScene = async (opts: {
scene = {
elements: bumpElementVersions(
restoreElements(
migrateSceneElements(imported.elements, {
payloadSchemaVersion: imported.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
}),
null,
{
repairBindings: true,
deleteInvisibleElements: true,
},
),
restoreElements(imported.elements, null, {
repairBindings: true,
deleteInvisibleElements: true,
}),
scene.elements,
),
appState: restoreAppState(
@@ -579,24 +549,9 @@ const ExcalidrawWrapper = () => {
if (isBrowserStorageStateNewer(STORAGE_KEYS.VERSION_DATA_STATE)) {
const localDataState = importFromLocalStorage();
const username = importUsernameFromLocalStorage();
const localStorageSchemaVersionSource = hasElementSchemaVersion(
localDataState?.elements,
)
? {
payloadSchemaVersion: undefined,
fallbackVersion: SCHEMA_VERSIONS.initial,
}
: {
payloadSchemaVersion: localDataState?.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
};
const migratedElements = migrateSceneElements(
localDataState?.elements,
localStorageSchemaVersionSource,
);
setLangCode(getPreferredLanguage());
excalidrawAPI.updateScene({
elements: restoreElements(migratedElements, null, {
elements: restoreElements(localDataState?.elements, null, {
repairBindings: true,
deleteInvisibleElements: true,
}),
-1
View File
@@ -39,7 +39,6 @@ export const ROOM_ID_BYTES = 10;
export const STORAGE_KEYS = {
LOCAL_STORAGE_ELEMENTS: "excalidraw",
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
LOCAL_STORAGE_SCHEMA_VERSION: "excalidraw-schema-version",
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
LOCAL_STORAGE_THEME: "excalidraw-theme",
LOCAL_STORAGE_DEBUG: "excalidraw-debug",
+1 -11
View File
@@ -28,7 +28,6 @@ import {
import { appJotaiStore, atom } from "excalidraw-app/app-jotai";
import { getNonDeletedElements } from "@excalidraw/element";
import { SCHEMA_VERSIONS } from "@excalidraw/excalidraw/data/schema";
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
@@ -87,12 +86,7 @@ const saveDataStateToLocalStorage = (
_appState.openSidebar = null;
}
const persistedElements = getNonDeletedElements(elements).map(
(element) => ({
...element,
schemaVersion: SCHEMA_VERSIONS.latest,
}),
);
const persistedElements = getNonDeletedElements(elements);
localStorage.setItem(
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
@@ -102,10 +96,6 @@ const saveDataStateToLocalStorage = (
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
JSON.stringify(_appState),
);
localStorage.setItem(
STORAGE_KEYS.LOCAL_STORAGE_SCHEMA_VERSION,
`${SCHEMA_VERSIONS.latest}`,
);
updateBrowserStateVersion(STORAGE_KEYS.VERSION_DATA_STATE);
if (localStorageQuotaExceeded) {
appJotaiStore.set(localStorageQuotaExceededAtom, false);
-2
View File
@@ -196,7 +196,6 @@ const legacy_decodeFromBackend = async ({
return {
elements: data.elements || null,
appState: data.appState || null,
schemaVersion: data.schemaVersion,
};
};
@@ -227,7 +226,6 @@ export const importFromBackend = async (
return {
elements: data.elements || null,
appState: data.appState || null,
schemaVersion: data.schemaVersion,
};
} catch (error: any) {
console.warn(
+1 -14
View File
@@ -2,10 +2,6 @@ import {
clearAppStateForLocalStorage,
getDefaultAppState,
} from "@excalidraw/excalidraw/appState";
import {
resolveSchemaVersion,
SCHEMA_VERSIONS,
} from "@excalidraw/excalidraw/data/schema";
import type { ExcalidrawElement } from "@excalidraw/element/types";
import type { AppState } from "@excalidraw/excalidraw/types";
@@ -41,14 +37,10 @@ export const importUsernameFromLocalStorage = (): string | null => {
export const importFromLocalStorage = () => {
let savedElements = null;
let savedState = null;
let savedSchemaVersion = null;
try {
savedElements = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS);
savedState = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_APP_STATE);
savedSchemaVersion = localStorage.getItem(
STORAGE_KEYS.LOCAL_STORAGE_SCHEMA_VERSION,
);
} catch (error: any) {
// Unable to access localStorage
console.error(error);
@@ -78,12 +70,7 @@ export const importFromLocalStorage = () => {
// Do nothing because appState is already null
}
}
const schemaVersion = resolveSchemaVersion(
Number(savedSchemaVersion),
SCHEMA_VERSIONS.initial,
);
return { elements, appState, schemaVersion };
return { elements, appState };
};
export const getElementsStorageSize = () => {
+2 -13
View File
@@ -35,8 +35,6 @@ import {
import { tryParseSpreadsheet, VALID_SPREADSHEET } from "./charts";
import { SCHEMA_VERSIONS } from "./data/schema";
import type { FileSystemHandle } from "./data/filesystem";
import type { Spreadsheet } from "./charts";
@@ -45,7 +43,6 @@ import type { BinaryFiles } from "./types";
type ElementsClipboard = {
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
schemaVersion: number;
elements: readonly (NonDeletedExcalidrawElement & {
schemaVersion?: number;
})[];
@@ -58,7 +55,6 @@ export interface ClipboardData {
spreadsheet?: Spreadsheet;
elements?: readonly ExcalidrawElement[];
files?: BinaryFiles;
schemaVersion?: number;
text?: string;
mixedContent?: PastedMixedContent;
errorMessage?: string;
@@ -88,7 +84,6 @@ const clipboardContainsElements = (
): contents is {
elements: ExcalidrawElement[];
files?: BinaryFiles;
schemaVersion?: number;
} => {
if (
[
@@ -188,24 +183,19 @@ export const serializeAsClipboardJSON = ({
// select bound text elements when copying
const contents: ElementsClipboard = {
type: EXPORT_DATA_TYPES.excalidrawClipboard,
schemaVersion: SCHEMA_VERSIONS.latest,
elements: elements.map((element) => {
const elementWithSchema = {
...element,
schemaVersion: SCHEMA_VERSIONS.latest,
};
if (
getContainingFrame(element, elementsMap) &&
!framesToCopy.has(getContainingFrame(element, elementsMap)!)
) {
const copiedElement = deepCopyElement(elementWithSchema);
const copiedElement = deepCopyElement(element);
mutateElement(copiedElement, elementsMap, {
frameId: null,
});
return copiedElement;
}
return elementWithSchema;
return element;
}),
files: files ? _files : undefined,
};
@@ -587,7 +577,6 @@ export const parseClipboard = async (
return {
elements: systemClipboardData.elements,
files: systemClipboardData.files,
schemaVersion: systemClipboardData.schemaVersion,
text: isPlainPaste
? JSON.stringify(systemClipboardData.elements, null, 2)
: undefined,
+5 -48
View File
@@ -33,7 +33,6 @@ import {
ELEMENT_SHIFT_TRANSLATE_AMOUNT,
ELEMENT_TRANSLATE_AMOUNT,
EVENT,
EXPORT_DATA_TYPES,
FRAME_STYLE,
IMAGE_MIME_TYPES,
IMAGE_RENDER_TIMEOUT,
@@ -355,15 +354,6 @@ import {
import { exportCanvas, loadFromBlob } from "../data";
import Library, { distributeLibraryItemsOnSquareGrid } from "../data/library";
import { restoreAppState, restoreElements } from "../data/restore";
import {
migrateAPIElements,
migrateClipboardElements,
migrateLibraryElements,
migrateSceneElements,
SCHEMA_VERSIONS,
type SchemaVersionSource,
type SchemaMigrationScope,
} from "../data/schema";
import { getCenter, getDistance } from "../gesture";
import { History } from "../history";
import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n";
@@ -2335,8 +2325,6 @@ class App extends React.Component<AppProps, AppState> {
elements,
position: "center",
files: null,
schemaVersionSource: SCHEMA_VERSIONS.latest,
migrationScope: "api",
});
};
@@ -2817,21 +2805,10 @@ class App extends React.Component<AppProps, AppState> {
},
};
}
const initialDataSchemaFallback =
initialData?.type === EXPORT_DATA_TYPES.excalidraw
? SCHEMA_VERSIONS.initial
: SCHEMA_VERSIONS.latest;
const restoredElements = restoreElements(
migrateSceneElements(initialData?.elements, {
payloadSchemaVersion: initialData?.schemaVersion,
fallbackVersion: initialDataSchemaFallback,
}),
null,
{
repairBindings: true,
deleteInvisibleElements: true,
},
);
const restoredElements = restoreElements(initialData?.elements, null, {
repairBindings: true,
deleteInvisibleElements: true,
});
let restoredAppState = restoreAppState(initialData?.appState, null);
const activeTool = restoredAppState.activeTool;
@@ -3596,13 +3573,6 @@ class App extends React.Component<AppProps, AppState> {
this.addElementsFromPasteOrLibrary({
elements,
files: data.files || null,
schemaVersionSource: data.programmaticAPI
? SCHEMA_VERSIONS.latest
: {
payloadSchemaVersion: data.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
},
migrationScope: data.programmaticAPI ? "api" : "clipboard",
position:
this.editorInterface.formFactor === "desktop" ? "cursor" : "center",
retainSeed: isPlainPaste,
@@ -3629,8 +3599,6 @@ class App extends React.Component<AppProps, AppState> {
this.addElementsFromPasteOrLibrary({
elements,
files,
schemaVersionSource: SCHEMA_VERSIONS.latest,
migrationScope: "api",
position:
this.editorInterface.formFactor === "desktop" ? "cursor" : "center",
});
@@ -3748,20 +3716,11 @@ class App extends React.Component<AppProps, AppState> {
addElementsFromPasteOrLibrary = (opts: {
elements: readonly ExcalidrawElement[];
files: BinaryFiles | null;
schemaVersionSource: SchemaVersionSource;
migrationScope: SchemaMigrationScope;
position: { clientX: number; clientY: number } | "cursor" | "center";
retainSeed?: boolean;
fitToContent?: boolean;
}) => {
const migratedElements =
opts.migrationScope === "clipboard"
? migrateClipboardElements(opts.elements, opts.schemaVersionSource)
: opts.migrationScope === "library"
? migrateLibraryElements(opts.elements, opts.schemaVersionSource)
: migrateAPIElements(opts.elements, opts.schemaVersionSource);
const elements = restoreElements(migratedElements, null, {
const elements = restoreElements(opts.elements, null, {
deleteInvisibleElements: true,
});
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
@@ -11532,8 +11491,6 @@ class App extends React.Component<AppProps, AppState> {
elements: distributeLibraryItemsOnSquareGrid(libraryItems),
position: event,
files: null,
schemaVersionSource: SCHEMA_VERSIONS.latest,
migrationScope: "library",
});
}
} catch (error: any) {
@@ -10,8 +10,6 @@ import {
getExportSource,
} from "@excalidraw/common";
import { SCHEMA_VERSIONS } from "../data/schema";
import { EditorLocalStorage } from "../data/EditorLocalStorage";
import { canvasToBlob, resizeImageFile } from "../data/blob";
import { t } from "../i18n";
@@ -282,7 +280,6 @@ const PublishLibrary = ({
const libContent: ExportedLibraryData = {
type: EXPORT_DATA_TYPES.excalidrawLibrary,
version: VERSIONS.excalidrawLibrary,
schemaVersion: SCHEMA_VERSIONS.latest,
source: getExportSource(),
libraryItems: clonedLibItems,
};
@@ -6,7 +6,6 @@ import type {
} from "@excalidraw/element/types";
import { EditorLocalStorage } from "../../data/EditorLocalStorage";
import { SCHEMA_VERSIONS } from "../../data/schema";
import {
convertToExcalidrawElements,
exportToCanvas,
@@ -149,8 +148,6 @@ export const insertToEditor = ({
app.addElementsFromPasteOrLibrary({
elements: newElements,
files,
schemaVersionSource: SCHEMA_VERSIONS.latest,
migrationScope: "api",
position: "center",
fitToContent: true,
});
+2 -10
View File
@@ -24,7 +24,6 @@ import {
restoreElements,
restoreLibraryItems,
} from "./restore";
import { migrateSceneElements, SCHEMA_VERSIONS } from "./schema";
import type { AppState, DataURL, LibraryItem } from "../types";
@@ -158,14 +157,10 @@ export const loadSceneOrLibraryFromBlob = async (
throw error;
}
if (isValidExcalidrawData(data)) {
const migratedElements = migrateSceneElements(data.elements, {
payloadSchemaVersion: data.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
});
return {
type: MIME_TYPES.excalidraw,
data: {
elements: restoreElements(migratedElements, localElements, {
elements: restoreElements(data.elements, localElements, {
repairBindings: true,
deleteInvisibleElements: true,
}),
@@ -227,10 +222,7 @@ export const parseLibraryJSON = (
throw new Error("Invalid library");
}
const libraryItems = data.libraryItems || data.library;
return restoreLibraryItems(libraryItems, defaultStatus, {
payloadSchemaVersion: data.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
});
return restoreLibraryItems(libraryItems, defaultStatus);
};
export const loadLibraryFromBlob = async (
-3
View File
@@ -12,7 +12,6 @@ import { cleanAppStateForExport, clearAppStateForDatabase } from "../appState";
import { isImageFileHandle, loadFromBlob } from "./blob";
import { fileOpen, fileSave } from "./filesystem";
import { SCHEMA_VERSIONS } from "./schema";
import type { AppState, BinaryFiles, LibraryItems } from "../types";
import type {
@@ -52,7 +51,6 @@ export const serializeAsJSON = (
const data: ExportedDataState = {
type: EXPORT_DATA_TYPES.excalidraw,
version: VERSIONS.excalidraw,
schemaVersion: SCHEMA_VERSIONS.latest,
source: getExportSource(),
elements,
appState:
@@ -131,7 +129,6 @@ export const serializeLibraryAsJSON = (libraryItems: LibraryItems) => {
const data: ExportedLibraryData = {
type: EXPORT_DATA_TYPES.excalidrawLibrary,
version: VERSIONS.excalidrawLibrary,
schemaVersion: SCHEMA_VERSIONS.latest,
source: getExportSource(),
libraryItems,
};
+3 -21
View File
@@ -33,7 +33,6 @@ import { t } from "../i18n";
import { loadLibraryFromBlob } from "./blob";
import { restoreLibraryItems } from "./restore";
import { SCHEMA_VERSIONS } from "./schema";
import type App from "../components/App";
@@ -68,7 +67,6 @@ type LibraryUpdate = {
export type LibraryPersistedData = {
libraryItems: LibraryItems;
schemaVersion?: number;
};
const onLibraryUpdateEmitter = new Emitter<
@@ -91,10 +89,7 @@ export interface LibraryPersistenceAdapter {
* purposes, in which case host app can implement more aggressive caching.
*/
source: LibraryAdatapterSource;
}): MaybePromise<{
libraryItems: LibraryItems_anyVersion;
schemaVersion?: number;
} | null>;
}): MaybePromise<{ libraryItems: LibraryItems_anyVersion } | null>;
/** Should persist to the database as is (do no change the data structure). */
save(libraryData: LibraryPersistedData): MaybePromise<void>;
}
@@ -106,7 +101,6 @@ export interface LibraryMigrationAdapter {
*/
load(): MaybePromise<{
libraryItems: LibraryItems_anyVersion;
schemaVersion?: number;
} | null>;
/** clears entire storage afterwards */
@@ -562,12 +556,7 @@ class AdapterTransaction {
new Promise<LibraryItems>(async (resolve, reject) => {
try {
const data = await adapter.load({ source });
resolve(
restoreLibraryItems(data?.libraryItems || [], "published", {
payloadSchemaVersion: data?.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
}),
);
resolve(restoreLibraryItems(data?.libraryItems || [], "published"));
} catch (error: any) {
reject(error);
}
@@ -675,10 +664,7 @@ const persistLibraryUpdate = async (
const version = getLibraryItemsHash(nextLibraryItems);
if (version !== lastSavedLibraryItemsHash) {
await adapter.save({
libraryItems: nextLibraryItems,
schemaVersion: SCHEMA_VERSIONS.latest,
});
await adapter.save({ libraryItems: nextLibraryItems });
}
lastSavedLibraryItemsHash = version;
@@ -878,10 +864,6 @@ export const useHandleLibrary = (
restoredData = restoreLibraryItems(
libraryData.libraryItems || [],
"published",
{
payloadSchemaVersion: libraryData.schemaVersion,
fallbackVersion: SCHEMA_VERSIONS.initial,
},
);
// we don't queue this operation because it's running inside