mirror of
https://github.com/excalidraw/excalidraw.git
synced 2026-08-30 16:55:16 +08:00
Document-level schema versioning.
Signed-off-by: Mark Tolmacs <mark@lazycat.hu>
This commit is contained in:
@@ -55,6 +55,7 @@ import {
|
||||
} from "@excalidraw/excalidraw/data/restore";
|
||||
import { newElementWith } from "@excalidraw/element";
|
||||
import { isInitializedImageElement } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION, getSchemaVersion } from "@excalidraw/element";
|
||||
import clsx from "clsx";
|
||||
import {
|
||||
parseLibraryTokensFromUrl,
|
||||
@@ -241,6 +242,7 @@ const initializeScene = async (opts: {
|
||||
elements: restoreElements(localDataState?.elements, null, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
schemaVersion: localDataState?.schemaVersion,
|
||||
}),
|
||||
appState: restoreAppState(localDataState?.appState, null),
|
||||
};
|
||||
@@ -267,6 +269,7 @@ const initializeScene = async (opts: {
|
||||
restoreElements(imported.elements, null, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
schemaVersion: getSchemaVersion(imported),
|
||||
}),
|
||||
localDataState?.elements,
|
||||
),
|
||||
@@ -545,8 +548,12 @@ const ExcalidrawWrapper = () => {
|
||||
loadImages(data);
|
||||
if (data.scene) {
|
||||
excalidrawAPI.updateScene({
|
||||
// NOTE: scene elements passed through restore during
|
||||
// initialization, so they are already at the current
|
||||
// schema version
|
||||
elements: restoreElements(data.scene.elements, null, {
|
||||
repairBindings: true,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
}),
|
||||
appState: restoreAppState(data.scene.appState, null),
|
||||
captureUpdate: CaptureUpdateAction.IMMEDIATELY,
|
||||
|
||||
@@ -38,6 +38,7 @@ export const ROOM_ID_BYTES = 10;
|
||||
|
||||
export const STORAGE_KEYS = {
|
||||
LOCAL_STORAGE_ELEMENTS: "excalidraw",
|
||||
LOCAL_STORAGE_SCHEMA_VERSION: "excalidraw-schema-version",
|
||||
LOCAL_STORAGE_APP_STATE: "excalidraw-state",
|
||||
LOCAL_STORAGE_COLLAB: "excalidraw-collab",
|
||||
LOCAL_STORAGE_THEME: "excalidraw-theme",
|
||||
|
||||
@@ -22,6 +22,7 @@ import { decryptData } from "@excalidraw/excalidraw/data/encryption";
|
||||
import { getVisibleSceneBounds } from "@excalidraw/element";
|
||||
import { newElementWith } from "@excalidraw/element";
|
||||
import { isImageElement, isInitializedImageElement } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION, getSchemaVersion } from "@excalidraw/element";
|
||||
import { AbortError } from "@excalidraw/excalidraw/errors";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import { withBatchedUpdates } from "@excalidraw/excalidraw/reactUtils";
|
||||
@@ -587,8 +588,10 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
const remoteElements = toBrandedType<
|
||||
readonly RemoteExcalidrawElement[]
|
||||
>(decryptedData.payload.elements);
|
||||
const reconciledElements =
|
||||
this._reconcileElements(remoteElements);
|
||||
const reconciledElements = this._reconcileElements(
|
||||
remoteElements,
|
||||
getSchemaVersion(decryptedData.payload),
|
||||
);
|
||||
this.handleRemoteSceneUpdate(reconciledElements);
|
||||
// noop if already resolved via init from firebase
|
||||
scenePromise.resolve({
|
||||
@@ -604,6 +607,7 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
toBrandedType<readonly RemoteExcalidrawElement[]>(
|
||||
decryptedData.payload.elements,
|
||||
),
|
||||
getSchemaVersion(decryptedData.payload),
|
||||
),
|
||||
);
|
||||
break;
|
||||
@@ -753,15 +757,27 @@ class Collab extends PureComponent<CollabProps, CollabState> {
|
||||
|
||||
private _reconcileElements = (
|
||||
remoteElements: readonly RemoteExcalidrawElement[],
|
||||
remoteSchemaVersion: number = 0,
|
||||
): ReconciledExcalidrawElement[] => {
|
||||
const appState = this.excalidrawAPI.getAppState();
|
||||
|
||||
const existingElements = this.getSceneElementsIncludingDeleted();
|
||||
|
||||
if (remoteSchemaVersion > CURRENT_SCHEMA_VERSION) {
|
||||
// NOTE: remote client is newer than us; we can't downgrade its elements,
|
||||
// so restore as-is and rely on unknown properties being preserved
|
||||
console.warn(
|
||||
`Received collab update with schema version ${remoteSchemaVersion} ` +
|
||||
`(local: ${CURRENT_SCHEMA_VERSION})`,
|
||||
);
|
||||
}
|
||||
|
||||
// NOTE ideally we restore _after_ reconciliation but we can't do that
|
||||
// as we'd regenerate even elements such as appState.newElement which would
|
||||
// break the state
|
||||
remoteElements = restoreElements(remoteElements, existingElements);
|
||||
remoteElements = restoreElements(remoteElements, existingElements, {
|
||||
schemaVersion: remoteSchemaVersion,
|
||||
});
|
||||
|
||||
let reconciledElements = reconcileElements(
|
||||
existingElements,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { CaptureUpdateAction } from "@excalidraw/excalidraw";
|
||||
import { trackEvent } from "@excalidraw/excalidraw/analytics";
|
||||
import { encryptData } from "@excalidraw/excalidraw/data/encryption";
|
||||
import { newElementWith } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION } from "@excalidraw/element";
|
||||
import throttle from "lodash.throttle";
|
||||
|
||||
import type { UserIdleState } from "@excalidraw/common";
|
||||
@@ -167,6 +168,7 @@ class Portal {
|
||||
type: updateType,
|
||||
payload: {
|
||||
elements: syncableElements,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
} from "idb-keyval";
|
||||
|
||||
import { getNonDeletedElements } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION } from "@excalidraw/element";
|
||||
|
||||
import type { LibraryPersistedData } from "@excalidraw/excalidraw/data/library";
|
||||
import type { ImportedDataState } from "@excalidraw/excalidraw/data/types";
|
||||
@@ -91,6 +92,10 @@ const saveDataStateToLocalStorage = (
|
||||
STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS,
|
||||
JSON.stringify(getNonDeletedElements(elements)),
|
||||
);
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_SCHEMA_VERSION,
|
||||
JSON.stringify(CURRENT_SCHEMA_VERSION),
|
||||
);
|
||||
localStorage.setItem(
|
||||
STORAGE_KEYS.LOCAL_STORAGE_APP_STATE,
|
||||
JSON.stringify(_appState),
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
} from "@excalidraw/excalidraw/data/encryption";
|
||||
import { restoreElements } from "@excalidraw/excalidraw/data/restore";
|
||||
import { getSceneVersion } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION, getSchemaVersion } from "@excalidraw/element";
|
||||
import { initializeApp } from "firebase/app";
|
||||
import {
|
||||
getFirestore,
|
||||
@@ -86,6 +87,7 @@ export const loadFirebaseStorage = async () => {
|
||||
|
||||
type FirebaseStoredScene = {
|
||||
sceneVersion: number;
|
||||
schemaVersion?: number;
|
||||
iv: Bytes;
|
||||
ciphertext: Bytes;
|
||||
};
|
||||
@@ -179,6 +181,7 @@ const createFirebaseSceneDocument = async (
|
||||
const { ciphertext, iv } = await encryptElements(roomKey, elements);
|
||||
return {
|
||||
sceneVersion,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
ciphertext: Bytes.fromUint8Array(new Uint8Array(ciphertext)),
|
||||
iv: Bytes.fromUint8Array(iv),
|
||||
} as FirebaseStoredScene;
|
||||
@@ -216,7 +219,9 @@ export const saveToFirebase = async (
|
||||
|
||||
const prevStoredScene = snapshot.data() as FirebaseStoredScene;
|
||||
const prevStoredElements = getSyncableElements(
|
||||
restoreElements(await decryptElements(prevStoredScene, roomKey), null),
|
||||
restoreElements(await decryptElements(prevStoredScene, roomKey), null, {
|
||||
schemaVersion: getSchemaVersion(prevStoredScene),
|
||||
}),
|
||||
);
|
||||
const reconciledElements = getSyncableElements(
|
||||
reconcileElements(
|
||||
@@ -238,7 +243,9 @@ export const saveToFirebase = async (
|
||||
});
|
||||
|
||||
const storedElements = getSyncableElements(
|
||||
restoreElements(await decryptElements(storedScene, roomKey), null),
|
||||
restoreElements(await decryptElements(storedScene, roomKey), null, {
|
||||
schemaVersion: getSchemaVersion(storedScene),
|
||||
}),
|
||||
);
|
||||
|
||||
FirebaseSceneVersionCache.set(socket, storedElements);
|
||||
@@ -261,6 +268,7 @@ export const loadFromFirebase = async (
|
||||
const elements = getSyncableElements(
|
||||
restoreElements(await decryptElements(storedScene, roomKey), null, {
|
||||
deleteInvisibleElements: true,
|
||||
schemaVersion: getSchemaVersion(storedScene),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
import { serializeAsJSON } from "@excalidraw/excalidraw/data/json";
|
||||
import { isInvisiblySmallElement } from "@excalidraw/element";
|
||||
import { isInitializedImageElement } from "@excalidraw/element";
|
||||
import { getSchemaVersion } from "@excalidraw/element";
|
||||
import { t } from "@excalidraw/excalidraw/i18n";
|
||||
import { bytesToHexString } from "@excalidraw/common";
|
||||
|
||||
@@ -84,12 +85,14 @@ export type SocketUpdateDataSource = {
|
||||
type: WS_SUBTYPES.INIT;
|
||||
payload: {
|
||||
elements: readonly OrderedExcalidrawElement[];
|
||||
schemaVersion?: number;
|
||||
};
|
||||
};
|
||||
SCENE_UPDATE: {
|
||||
type: WS_SUBTYPES.UPDATE;
|
||||
payload: {
|
||||
elements: readonly OrderedExcalidrawElement[];
|
||||
schemaVersion?: number;
|
||||
};
|
||||
};
|
||||
MOUSE_LOCATION: {
|
||||
@@ -196,6 +199,7 @@ const legacy_decodeFromBackend = async ({
|
||||
return {
|
||||
elements: data.elements || null,
|
||||
appState: data.appState || null,
|
||||
schemaVersion: getSchemaVersion(data),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -226,6 +230,7 @@ export const importFromBackend = async (
|
||||
return {
|
||||
elements: data.elements || null,
|
||||
appState: data.appState || null,
|
||||
schemaVersion: getSchemaVersion(data),
|
||||
};
|
||||
} catch (error: any) {
|
||||
console.warn(
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
getDefaultAppState,
|
||||
} from "@excalidraw/excalidraw/appState";
|
||||
|
||||
import { getSchemaVersion } from "@excalidraw/element";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
import type { AppState } from "@excalidraw/excalidraw/types";
|
||||
|
||||
@@ -37,10 +39,14 @@ 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);
|
||||
@@ -56,6 +62,17 @@ export const importFromLocalStorage = () => {
|
||||
}
|
||||
}
|
||||
|
||||
let schemaVersion: number | undefined;
|
||||
if (savedSchemaVersion) {
|
||||
try {
|
||||
schemaVersion = getSchemaVersion({
|
||||
schemaVersion: JSON.parse(savedSchemaVersion),
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
let appState = null;
|
||||
if (savedState) {
|
||||
try {
|
||||
@@ -70,7 +87,7 @@ export const importFromLocalStorage = () => {
|
||||
// Do nothing because appState is already null
|
||||
}
|
||||
}
|
||||
return { elements, appState };
|
||||
return { elements, appState, schemaVersion };
|
||||
};
|
||||
|
||||
export const getElementsStorageSize = () => {
|
||||
|
||||
@@ -27,7 +27,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#66a80f",
|
||||
"strokeStyle": "solid",
|
||||
@@ -65,7 +64,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#9c36b5",
|
||||
"strokeStyle": "solid",
|
||||
@@ -118,7 +116,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -180,7 +177,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -227,7 +223,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing s
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -271,7 +266,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"originalText": "HEYYYYY",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#c2255c",
|
||||
"strokeStyle": "solid",
|
||||
@@ -318,7 +312,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"originalText": "Whats up ?",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -379,7 +372,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -427,7 +419,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to existing t
|
||||
"originalText": "HELLO WORLD!!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -488,7 +479,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -536,7 +526,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"originalText": "HELLO WORLD!!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -577,7 +566,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -615,7 +603,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to shapes whe
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -673,7 +660,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -721,7 +707,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"originalText": "HELLO WORLD!!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -768,7 +753,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"originalText": "HEYYYYY",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -815,7 +799,6 @@ exports[`Test Transform > Test arrow bindings > should bind arrows to text when
|
||||
"originalText": "WHATS UP ?",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -851,7 +834,6 @@ exports[`Test Transform > should not allow duplicate ids 1`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -897,7 +879,6 @@ exports[`Test Transform > should transform linear elements 1`] = `
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -945,7 +926,6 @@ exports[`Test Transform > should transform linear elements 2`] = `
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": "dot",
|
||||
"startBinding": null,
|
||||
@@ -993,7 +973,6 @@ exports[`Test Transform > should transform linear elements 3`] = `
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -1041,7 +1020,6 @@ exports[`Test Transform > should transform linear elements 4`] = `
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -1076,7 +1054,6 @@ exports[`Test Transform > should transform regular shapes 1`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1109,7 +1086,6 @@ exports[`Test Transform > should transform regular shapes 2`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1142,7 +1118,6 @@ exports[`Test Transform > should transform regular shapes 3`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1175,7 +1150,6 @@ exports[`Test Transform > should transform regular shapes 4`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1208,7 +1182,6 @@ exports[`Test Transform > should transform regular shapes 5`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "dotted",
|
||||
@@ -1241,7 +1214,6 @@ exports[`Test Transform > should transform regular shapes 6`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1971c2",
|
||||
"strokeStyle": "dashed",
|
||||
@@ -1280,7 +1252,6 @@ exports[`Test Transform > should transform text element 1`] = `
|
||||
"originalText": "HELLO WORLD!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1322,7 +1293,6 @@ exports[`Test Transform > should transform text element 2`] = `
|
||||
"originalText": "STYLED HELLO WORLD!",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#5f3dc4",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1369,7 +1339,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1409,7 +1378,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1453,7 +1421,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1501,7 +1468,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1561,7 +1527,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -1626,7 +1591,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
@@ -1676,7 +1640,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"originalText": "B",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1720,7 +1683,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"originalText": "A",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1764,7 +1726,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"originalText": "Alice",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1808,7 +1769,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"originalText": "Bob",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1850,7 +1810,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"originalText": "How are you?",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1892,7 +1851,6 @@ exports[`Test Transform > should transform the elements correctly when linear el
|
||||
"originalText": "Friendship",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1946,7 +1904,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -1999,7 +1956,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -2052,7 +2008,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -2105,7 +2060,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -2146,7 +2100,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"originalText": "LABELED ARROW",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2188,7 +2141,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"originalText": "STYLED LABELED ARROW",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#099268",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2230,7 +2182,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"originalText": "ANOTHER STYLED LABELLED ARROW",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1098ad",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2273,7 +2224,6 @@ exports[`Test Transform > should transform to labelled arrows when label provide
|
||||
"originalText": "ANOTHER STYLED LABELLED ARROW",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#099268",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2315,7 +2265,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2353,7 +2302,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2391,7 +2339,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2429,7 +2376,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2467,7 +2413,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#c2255c",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2505,7 +2450,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#f08c00",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2544,7 +2488,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"originalText": "RECTANGLE TEXT CONTAINER",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2586,7 +2529,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"originalText": "ELLIPSE TEXT CONTAINER",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2630,7 +2572,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
TEXT CONTAINER",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2674,7 +2615,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"originalText": "STYLED DIAMOND TEXT CONTAINER",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#099268",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2717,7 +2657,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"originalText": "TOP LEFT ALIGNED RECTANGLE TEXT CONTAINER",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#c2255c",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2761,7 +2700,6 @@ exports[`Test Transform > should transform to text containers when label provide
|
||||
"originalText": "STYLED ELLIPSE TEXT CONTAINER",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#c2255c",
|
||||
"strokeStyle": "solid",
|
||||
|
||||
@@ -1,72 +1,88 @@
|
||||
import {
|
||||
CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
getElementSchemaVersion,
|
||||
upgradeElementSchema,
|
||||
CURRENT_SCHEMA_VERSION,
|
||||
getSchemaVersion,
|
||||
migrateElement,
|
||||
migrateElementPartial,
|
||||
migrateElements,
|
||||
} from "../versioning";
|
||||
|
||||
describe("element schema versioning", () => {
|
||||
describe("getElementSchemaVersion", () => {
|
||||
it("treats an element without schemaVersion as legacy (version 0)", () => {
|
||||
expect(getElementSchemaVersion({})).toBe(0);
|
||||
expect(getElementSchemaVersion({ schemaVersion: undefined })).toBe(0);
|
||||
describe("scene schema versioning", () => {
|
||||
describe("getSchemaVersion", () => {
|
||||
it("treats a container without schemaVersion as legacy (version 0)", () => {
|
||||
expect(getSchemaVersion({})).toBe(0);
|
||||
expect(getSchemaVersion({ schemaVersion: undefined })).toBe(0);
|
||||
expect(getSchemaVersion(null)).toBe(0);
|
||||
expect(getSchemaVersion(undefined)).toBe(0);
|
||||
});
|
||||
|
||||
it("returns the element's schemaVersion when present", () => {
|
||||
expect(getElementSchemaVersion({ schemaVersion: 1 })).toBe(1);
|
||||
expect(getElementSchemaVersion({ schemaVersion: 3 })).toBe(3);
|
||||
it("treats an invalid schemaVersion as legacy (version 0)", () => {
|
||||
expect(getSchemaVersion({ schemaVersion: "2" })).toBe(0);
|
||||
expect(getSchemaVersion({ schemaVersion: NaN })).toBe(0);
|
||||
expect(getSchemaVersion({ schemaVersion: Infinity })).toBe(0);
|
||||
});
|
||||
|
||||
it("returns the container's schemaVersion when present", () => {
|
||||
expect(getSchemaVersion({ schemaVersion: 1 })).toBe(1);
|
||||
expect(getSchemaVersion({ schemaVersion: 3 })).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("upgradeElementSchema", () => {
|
||||
it("lifts a legacy element to the current schema version", () => {
|
||||
const legacy = { id: "a", type: "rectangle", backgroundColor: "#fff" };
|
||||
describe("migrateElements", () => {
|
||||
it("lifts legacy elements through the whole migration chain", () => {
|
||||
const legacy = [{ id: "a", type: "rectangle", backgroundColor: "#fff" }];
|
||||
|
||||
const upgraded = upgradeElementSchema(legacy);
|
||||
const migrated = migrateElements(legacy, 0);
|
||||
|
||||
expect(upgraded.schemaVersion).toBe(CURRENT_ELEMENT_SCHEMA_VERSION);
|
||||
// v0 -> v1 migration keeps the element shape intact
|
||||
expect(migrated).toEqual(legacy);
|
||||
});
|
||||
|
||||
it("preserves all other properties when upgrading", () => {
|
||||
const legacy = { id: "a", type: "rectangle", backgroundColor: "#fff" };
|
||||
it("returns the input array unchanged when already current", () => {
|
||||
const elements = [{ id: "a", type: "rectangle" }];
|
||||
|
||||
const upgraded = upgradeElementSchema(legacy);
|
||||
|
||||
expect(upgraded).toEqual({
|
||||
...legacy,
|
||||
schemaVersion: CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
});
|
||||
expect(migrateElements(elements, CURRENT_SCHEMA_VERSION)).toBe(elements);
|
||||
});
|
||||
|
||||
it("is idempotent on an already-current element", () => {
|
||||
const current = {
|
||||
id: "a",
|
||||
type: "rectangle",
|
||||
schemaVersion: CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
};
|
||||
it("returns the input array unchanged when coming from a newer client", () => {
|
||||
const elements = [{ id: "a", type: "rectangle" }];
|
||||
|
||||
const upgraded = upgradeElementSchema(current);
|
||||
|
||||
expect(upgraded).toEqual(current);
|
||||
});
|
||||
|
||||
it("does not mutate the input element", () => {
|
||||
const legacy = { id: "a", type: "rectangle" };
|
||||
|
||||
upgradeElementSchema(legacy);
|
||||
|
||||
expect(legacy).not.toHaveProperty("schemaVersion");
|
||||
});
|
||||
|
||||
// Template for future N -> N+1 migrations. When a real migration is added
|
||||
// (e.g. repurposing an attribute at schema version 2), assert here that a
|
||||
// legacy element is transformed as expected end-to-end. Kept minimal on
|
||||
// purpose so it documents the pattern without asserting fake behavior.
|
||||
it("runs migrations in sequence up to the current version", () => {
|
||||
const upgraded = upgradeElementSchema({ id: "a", schemaVersion: 0 });
|
||||
|
||||
expect(getElementSchemaVersion(upgraded)).toBe(
|
||||
CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
expect(migrateElements(elements, CURRENT_SCHEMA_VERSION + 1)).toBe(
|
||||
elements,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrateElement", () => {
|
||||
it("does not mutate the input element", () => {
|
||||
const legacy = { id: "a", type: "rectangle" };
|
||||
|
||||
const migrated = migrateElement(legacy, 0);
|
||||
|
||||
expect(migrated).toEqual({ id: "a", type: "rectangle" });
|
||||
expect(legacy).toEqual({ id: "a", type: "rectangle" });
|
||||
});
|
||||
});
|
||||
|
||||
describe("migrateElementPartial", () => {
|
||||
it("lifts a legacy delta partial to the current schema version", () => {
|
||||
const partial = { backgroundColor: "#fff", version: 2 };
|
||||
|
||||
expect(migrateElementPartial(partial, 0)).toEqual(partial);
|
||||
});
|
||||
|
||||
it("returns the partial unchanged when already current", () => {
|
||||
const partial = { backgroundColor: "#fff" };
|
||||
|
||||
expect(migrateElementPartial(partial, CURRENT_SCHEMA_VERSION)).toBe(
|
||||
partial,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// Template for future migrations
|
||||
it("runs migrations in sequence up to the current version", () => {
|
||||
const migrated = migrateElement({ id: "a" }, 0);
|
||||
|
||||
expect(migrated).toEqual({ id: "a" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -36,6 +36,8 @@ import type {
|
||||
|
||||
import { getObservedAppState } from "./store";
|
||||
|
||||
import { CURRENT_SCHEMA_VERSION, migrateElementPartial } from "./versioning";
|
||||
|
||||
import {
|
||||
BoundElement,
|
||||
BindableElement,
|
||||
@@ -1084,9 +1086,33 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
||||
return delta;
|
||||
}
|
||||
|
||||
public static restore(elementsDeltaDTO: DTO<ElementsDelta>): ElementsDelta {
|
||||
public static restore(
|
||||
elementsDeltaDTO: DTO<ElementsDelta>,
|
||||
schemaVersion: number = CURRENT_SCHEMA_VERSION,
|
||||
): ElementsDelta {
|
||||
const { added, removed, updated } = elementsDeltaDTO;
|
||||
return ElementsDelta.create(added, removed, updated);
|
||||
return ElementsDelta.create(
|
||||
ElementsDelta.migratePartialDeltas(added, schemaVersion),
|
||||
ElementsDelta.migratePartialDeltas(removed, schemaVersion),
|
||||
ElementsDelta.migratePartialDeltas(updated, schemaVersion),
|
||||
);
|
||||
}
|
||||
|
||||
private static migratePartialDeltas(
|
||||
deltas: Record<string, Delta<ElementPartial>>,
|
||||
fromVersion: number,
|
||||
): Record<string, Delta<ElementPartial>> {
|
||||
if (fromVersion >= CURRENT_SCHEMA_VERSION) {
|
||||
return deltas;
|
||||
}
|
||||
|
||||
return Object.entries(deltas).reduce((acc, [id, delta]) => {
|
||||
acc[id] = Delta.create(
|
||||
migrateElementPartial(delta.deleted, fromVersion),
|
||||
migrateElementPartial(delta.inserted, fromVersion),
|
||||
);
|
||||
return acc;
|
||||
}, {} as Record<string, Delta<ElementPartial>>);
|
||||
}
|
||||
|
||||
private static satisfiesAddition = ({
|
||||
|
||||
@@ -27,7 +27,6 @@ import { normalizeText, measureText } from "./textMeasurements";
|
||||
import { wrapText } from "./textWrapping";
|
||||
|
||||
import { isLineElement } from "./typeChecks";
|
||||
import { CURRENT_ELEMENT_SCHEMA_VERSION } from "./versioning";
|
||||
|
||||
import type {
|
||||
ExcalidrawElement,
|
||||
@@ -64,7 +63,6 @@ export type ElementConstructorOpts = MarkOptional<
|
||||
| "seed"
|
||||
| "version"
|
||||
| "versionNonce"
|
||||
| "schemaVersion"
|
||||
| "link"
|
||||
| "strokeStyle"
|
||||
| "fillStyle"
|
||||
@@ -151,7 +149,6 @@ const _newElementBase = <T extends ExcalidrawElement>(
|
||||
seed: rest.seed ?? randomInteger(),
|
||||
version: rest.version || 1,
|
||||
versionNonce: rest.versionNonce ?? 0,
|
||||
schemaVersion: rest.schemaVersion ?? CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
isDeleted: false as false,
|
||||
boundElements,
|
||||
updated: getUpdatedTimestamp(),
|
||||
|
||||
@@ -19,6 +19,8 @@ import { newElementWith } from "./mutateElement";
|
||||
|
||||
import { ElementsDelta, AppStateDelta, Delta } from "./delta";
|
||||
|
||||
import { CURRENT_SCHEMA_VERSION, getSchemaVersion } from "./versioning";
|
||||
|
||||
import {
|
||||
syncInvalidIndicesImmutable,
|
||||
hashElementsVersion,
|
||||
@@ -499,6 +501,7 @@ export class StoreDelta {
|
||||
public readonly id: string,
|
||||
public readonly elements: ElementsDelta,
|
||||
public readonly appState: AppStateDelta,
|
||||
public readonly schemaVersion: number = CURRENT_SCHEMA_VERSION,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -513,7 +516,7 @@ export class StoreDelta {
|
||||
id: randomId(),
|
||||
},
|
||||
) {
|
||||
return new this(opts.id, elements, appState);
|
||||
return new this(opts.id, elements, appState, CURRENT_SCHEMA_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -535,26 +538,31 @@ export class StoreDelta {
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a store delta instance from a DTO.
|
||||
* Restore a store delta instance from a DTO, lifting the contained element
|
||||
* partials to the current schema version.
|
||||
*/
|
||||
public static restore(storeDeltaDTO: DTO<StoreDelta>) {
|
||||
const { id, elements, appState } = storeDeltaDTO;
|
||||
return new this(
|
||||
id,
|
||||
ElementsDelta.restore(elements),
|
||||
ElementsDelta.restore(elements, getSchemaVersion(storeDeltaDTO)),
|
||||
AppStateDelta.restore(appState),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and load the delta from the remote payload.
|
||||
* Parse and load the delta from the remote payload, lifting the contained
|
||||
* element partials to the current schema version.
|
||||
*/
|
||||
public static load({
|
||||
id,
|
||||
elements: { added, removed, updated },
|
||||
appState: { delta: appStateDelta },
|
||||
}: DTO<StoreDelta>) {
|
||||
const elements = ElementsDelta.create(added, removed, updated);
|
||||
public static load(storeDeltaDTO: DTO<StoreDelta>) {
|
||||
const {
|
||||
id,
|
||||
appState: { delta: appStateDelta },
|
||||
} = storeDeltaDTO;
|
||||
const elements = ElementsDelta.restore(
|
||||
storeDeltaDTO.elements,
|
||||
getSchemaVersion(storeDeltaDTO),
|
||||
);
|
||||
const appState = AppStateDelta.create(appStateDelta);
|
||||
|
||||
return new this(id, elements, appState);
|
||||
|
||||
@@ -62,11 +62,6 @@ type _ExcalidrawElementBase = Readonly<{
|
||||
Used for deterministic reconciliation of updates during collaboration,
|
||||
in case the versions (see above) are identical. */
|
||||
versionNonce: number;
|
||||
/** Schema version of the element's shape/semantics. Unrelated to `version`
|
||||
(the collab/reconciliation counter). Legacy elements persisted before
|
||||
schema versioning lack it and are lifted to the current version on
|
||||
restore. See `CURRENT_ELEMENT_SCHEMA_VERSION`. */
|
||||
schemaVersion: number;
|
||||
/** String in a fractional form defined by https://github.com/rocicorp/fractional-indexing.
|
||||
Used for ordering in multiplayer scenarios, such as during reconciliation or undo / redo.
|
||||
Always kept in sync with the array order by `syncMovedIndices` and `syncInvalidIndices`.
|
||||
|
||||
@@ -1,57 +1,87 @@
|
||||
/**
|
||||
* The schema version that the current in-memory element shape corresponds to.
|
||||
* The schema version the current in-memory element shape corresponds to.
|
||||
*/
|
||||
export const CURRENT_ELEMENT_SCHEMA_VERSION = 1;
|
||||
export const CURRENT_SCHEMA_VERSION = 1;
|
||||
|
||||
type SchemaMigration = {
|
||||
// Upgrades a full element from schema version n to n + 1.
|
||||
element: (element: any) => any;
|
||||
// Upgrades a partial element (durable undo/redo).
|
||||
elementPartial: (partial: any) => any;
|
||||
};
|
||||
|
||||
const identity = <T>(value: T): T => value;
|
||||
|
||||
/**
|
||||
* A migration that upgrades an element from one schema version to the next.
|
||||
*/
|
||||
type ElementSchemaMigration = (element: any) => any;
|
||||
|
||||
/**
|
||||
* `migrations[n]` upgrades an element from schema version `n` to `n + 1`.
|
||||
* migrations[n] upgrades data from schema version to n + 1.
|
||||
*
|
||||
* Index `0` migrates legacy (unversioned) elements to version 1.
|
||||
* NOTE: Index 0 migrates legacy (unversioned) data to version 1.
|
||||
*
|
||||
* IMPORTANT: data of unknown provenance (API input, clipboard from older
|
||||
* builds etc.) is considered 'legacy' v0!
|
||||
*/
|
||||
const migrations: Record<number, ElementSchemaMigration> = {
|
||||
0: (element) => element,
|
||||
const migrations: Record<number, SchemaMigration> = {
|
||||
0: { element: identity, elementPartial: identity },
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an element's schema version, treating a missing `schemaVersion` (i.e.
|
||||
* a legacy element persisted before schema versioning) as version 0.
|
||||
* Returns the schema version indicated by a container.
|
||||
*/
|
||||
export const getElementSchemaVersion = (element: {
|
||||
schemaVersion?: number;
|
||||
}): number =>
|
||||
typeof element.schemaVersion === "number" ? element.schemaVersion : 0;
|
||||
export const getSchemaVersion = (source: unknown): number => {
|
||||
const schemaVersion = (source as { schemaVersion?: unknown } | null)
|
||||
?.schemaVersion;
|
||||
|
||||
/**
|
||||
* Lifts an element up to {@link CURRENT_ELEMENT_SCHEMA_VERSION} by running each
|
||||
* migration in sequence, then stamps the current schema version onto it.
|
||||
*
|
||||
* Safe to call on an already-current element (the migration chain is empty and
|
||||
* it is simply re-stamped). If a migration step is missing (which shouldn't
|
||||
* happen), it stops early rather than throwing, so a partially-known element is
|
||||
* still returned rather than lost.
|
||||
*/
|
||||
export const upgradeElementSchema = <T>(
|
||||
element: T,
|
||||
): T & { schemaVersion: number } => {
|
||||
let version = getElementSchemaVersion(element as { schemaVersion?: number });
|
||||
let upgraded: any = element;
|
||||
return typeof schemaVersion === "number" && Number.isFinite(schemaVersion)
|
||||
? schemaVersion
|
||||
: 0;
|
||||
};
|
||||
|
||||
while (version < CURRENT_ELEMENT_SCHEMA_VERSION) {
|
||||
const migrate = migrations[version];
|
||||
if (!migrate) {
|
||||
const runMigrations = <T>(
|
||||
kind: keyof SchemaMigration,
|
||||
value: T,
|
||||
fromVersion: number,
|
||||
): T => {
|
||||
let version = fromVersion;
|
||||
let migrated: any = value;
|
||||
|
||||
// NOTE: data from a newer client (fromVersion > current) is returned as-is;
|
||||
// unknown properties are preserved down the line for forward-compatibility
|
||||
while (version < CURRENT_SCHEMA_VERSION) {
|
||||
const migration = migrations[version];
|
||||
if (!migration) {
|
||||
break;
|
||||
}
|
||||
upgraded = migrate(upgraded);
|
||||
migrated = migration[kind](migrated);
|
||||
version += 1;
|
||||
}
|
||||
|
||||
return {
|
||||
...upgraded,
|
||||
schemaVersion: CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
};
|
||||
return migrated;
|
||||
};
|
||||
|
||||
/**
|
||||
* Lifts a full element from `fromVersion` up to
|
||||
* {@link CURRENT_SCHEMA_VERSION} by running each migration in sequence.
|
||||
*/
|
||||
export const migrateElement = <T>(element: T, fromVersion: number): T =>
|
||||
runMigrations("element", element, fromVersion);
|
||||
|
||||
/**
|
||||
* Lifts all elements of a container from `fromVersion` up to
|
||||
* {@link CURRENT_SCHEMA_VERSION}.
|
||||
*/
|
||||
export const migrateElements = <T>(
|
||||
elements: readonly T[],
|
||||
fromVersion: number,
|
||||
): readonly T[] =>
|
||||
fromVersion >= CURRENT_SCHEMA_VERSION
|
||||
? elements
|
||||
: elements.map((element) => migrateElement(element, fromVersion));
|
||||
|
||||
/**
|
||||
* Lifts a partial element shape (as contained in deltas) from `fromVersion`
|
||||
* up to {@link CURRENT_SCHEMA_VERSION}.
|
||||
*/
|
||||
export const migrateElementPartial = <T>(partial: T, fromVersion: number): T =>
|
||||
fromVersion >= CURRENT_SCHEMA_VERSION
|
||||
? partial
|
||||
: runMigrations("elementPartial", partial, fromVersion);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
|
||||
import { mutateElement } from "@excalidraw/element";
|
||||
import { deepCopyElement } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION, getSchemaVersion } from "@excalidraw/element";
|
||||
import {
|
||||
isFrameLikeElement,
|
||||
isInitializedImageElement,
|
||||
@@ -38,6 +39,7 @@ import type { BinaryFiles } from "./types";
|
||||
|
||||
type ElementsClipboard = {
|
||||
type: typeof EXPORT_DATA_TYPES.excalidrawClipboard;
|
||||
schemaVersion: number;
|
||||
elements: readonly NonDeletedExcalidrawElement[];
|
||||
files: BinaryFiles | undefined;
|
||||
};
|
||||
@@ -46,6 +48,7 @@ export type PastedMixedContent = { type: "text" | "imageUrl"; value: string }[];
|
||||
|
||||
export interface ClipboardData {
|
||||
elements?: readonly ExcalidrawElement[];
|
||||
schemaVersion?: number;
|
||||
files?: BinaryFiles;
|
||||
text?: string;
|
||||
mixedContent?: PastedMixedContent;
|
||||
@@ -172,6 +175,7 @@ export const serializeAsClipboardJSON = ({
|
||||
// select bound text elements when copying
|
||||
const contents: ElementsClipboard = {
|
||||
type: EXPORT_DATA_TYPES.excalidrawClipboard,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
elements: elements.map((element) => {
|
||||
if (
|
||||
getContainingFrame(element, elementsMap) &&
|
||||
@@ -542,6 +546,7 @@ export const parseClipboard = async (
|
||||
if (clipboardContainsElements(systemClipboardData)) {
|
||||
return {
|
||||
elements: systemClipboardData.elements,
|
||||
schemaVersion: getSchemaVersion(systemClipboardData),
|
||||
files: systemClipboardData.files,
|
||||
text: isPlainPaste
|
||||
? JSON.stringify(systemClipboardData.elements, null, 2)
|
||||
|
||||
@@ -258,6 +258,8 @@ import {
|
||||
isEligibleFrameChildType,
|
||||
getBindingStrategyForDraggingBindingElementEndpoints,
|
||||
isNonDeletedElement,
|
||||
CURRENT_SCHEMA_VERSION,
|
||||
getSchemaVersion,
|
||||
} from "@excalidraw/element";
|
||||
|
||||
import type { GlobalPoint, LocalPoint, Radians } from "@excalidraw/math";
|
||||
@@ -3482,6 +3484,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
const restoredElements = restoreElements(initialData?.elements, null, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
schemaVersion: getSchemaVersion(initialData),
|
||||
});
|
||||
let restoredAppState = restoreAppState(initialData?.appState, null);
|
||||
const activeTool = restoredAppState.activeTool;
|
||||
@@ -4496,6 +4499,7 @@ class App extends React.Component<AppProps, AppState> {
|
||||
this.editorInterface.formFactor === "desktop" ? "cursor" : "center",
|
||||
retainSeed: isPlainPaste,
|
||||
preserveFrameChildrenOrder: true,
|
||||
schemaVersion: data.programmaticAPI ? undefined : data.schemaVersion,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -4644,9 +4648,11 @@ class App extends React.Component<AppProps, AppState> {
|
||||
retainSeed?: boolean;
|
||||
fit?: SetViewportOptions["fit"];
|
||||
preserveFrameChildrenOrder?: boolean;
|
||||
schemaVersion?: number;
|
||||
}) => {
|
||||
const elements = restoreElements(opts.elements, null, {
|
||||
deleteInvisibleElements: true,
|
||||
schemaVersion: opts.schemaVersion ?? CURRENT_SCHEMA_VERSION,
|
||||
});
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(elements);
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
getExportSource,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { CURRENT_SCHEMA_VERSION } from "@excalidraw/element";
|
||||
|
||||
import { EditorLocalStorage } from "../data/EditorLocalStorage";
|
||||
import { canvasToBlob, resizeImageFile } from "../data/blob";
|
||||
import { t } from "../i18n";
|
||||
@@ -280,6 +282,7 @@ const PublishLibrary = ({
|
||||
const libContent: ExportedLibraryData = {
|
||||
type: EXPORT_DATA_TYPES.excalidrawLibrary,
|
||||
version: VERSIONS.excalidrawLibrary,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
source: getExportSource(),
|
||||
libraryItems: clonedLibItems,
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
isPromiseLike,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { getSchemaVersion } from "@excalidraw/element";
|
||||
|
||||
import type { ValueOf } from "@excalidraw/common/utility-types";
|
||||
import type { ExcalidrawElement, FileId } from "@excalidraw/element/types";
|
||||
|
||||
@@ -164,6 +166,7 @@ export const loadSceneOrLibraryFromBlob = async (
|
||||
elements: restoreElements(data.elements, localElements, {
|
||||
repairBindings: true,
|
||||
deleteInvisibleElements: true,
|
||||
schemaVersion: getSchemaVersion(data),
|
||||
}),
|
||||
appState: restoreAppState(
|
||||
{
|
||||
@@ -223,7 +226,9 @@ export const parseLibraryJSON = (
|
||||
throw new Error("Invalid library");
|
||||
}
|
||||
const libraryItems = data.libraryItems || data.library;
|
||||
return restoreLibraryItems(libraryItems, defaultStatus);
|
||||
return restoreLibraryItems(libraryItems, defaultStatus, {
|
||||
schemaVersion: getSchemaVersion(data),
|
||||
});
|
||||
};
|
||||
|
||||
export const loadLibraryFromBlob = async (
|
||||
|
||||
@@ -5,6 +5,8 @@ import {
|
||||
VERSIONS,
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { CURRENT_SCHEMA_VERSION } from "@excalidraw/element";
|
||||
|
||||
import type { ExcalidrawElement } from "@excalidraw/element/types";
|
||||
|
||||
import type { MaybePromise } from "@excalidraw/common/utility-types";
|
||||
@@ -58,6 +60,7 @@ export const serializeAsJSON = (
|
||||
const data: ExportedDataState = {
|
||||
type: EXPORT_DATA_TYPES.excalidraw,
|
||||
version: VERSIONS.excalidraw,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
source: getExportSource(),
|
||||
elements,
|
||||
appState:
|
||||
@@ -138,6 +141,7 @@ export const serializeLibraryAsJSON = (libraryItems: LibraryItems) => {
|
||||
const data: ExportedLibraryData = {
|
||||
type: EXPORT_DATA_TYPES.excalidrawLibrary,
|
||||
version: VERSIONS.excalidrawLibrary,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
source: getExportSource(),
|
||||
libraryItems,
|
||||
};
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
} from "@excalidraw/common";
|
||||
|
||||
import { hashElementsVersion, hashString } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION, getSchemaVersion } from "@excalidraw/element";
|
||||
|
||||
import { getCommonBoundingBox } from "@excalidraw/element";
|
||||
|
||||
@@ -65,9 +66,11 @@ type LibraryUpdate = {
|
||||
updatedItems: Map<LibraryItem["id"], LibraryItem>;
|
||||
};
|
||||
|
||||
// an object so that we can later add more properties to it without breaking,
|
||||
// such as schema version
|
||||
export type LibraryPersistedData = { libraryItems: LibraryItems };
|
||||
// an object so that we can later add more properties to it without breaking
|
||||
export type LibraryPersistedData = {
|
||||
libraryItems: LibraryItems;
|
||||
schemaVersion?: number;
|
||||
};
|
||||
|
||||
const onLibraryUpdateEmitter = new Emitter<
|
||||
[update: LibraryUpdate, libraryItems: LibraryItems]
|
||||
@@ -554,7 +557,11 @@ class AdapterTransaction {
|
||||
new Promise<LibraryItems>(async (resolve, reject) => {
|
||||
try {
|
||||
const data = await adapter.load({ source });
|
||||
resolve(restoreLibraryItems(data?.libraryItems || [], "published"));
|
||||
resolve(
|
||||
restoreLibraryItems(data?.libraryItems || [], "published", {
|
||||
schemaVersion: getSchemaVersion(data),
|
||||
}),
|
||||
);
|
||||
} catch (error: any) {
|
||||
reject(error);
|
||||
}
|
||||
@@ -662,7 +669,10 @@ const persistLibraryUpdate = async (
|
||||
const version = getLibraryItemsHash(nextLibraryItems);
|
||||
|
||||
if (version !== lastSavedLibraryItemsHash) {
|
||||
await adapter.save({ libraryItems: nextLibraryItems });
|
||||
await adapter.save({
|
||||
libraryItems: nextLibraryItems,
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
});
|
||||
}
|
||||
|
||||
lastSavedLibraryItemsHash = version;
|
||||
@@ -862,6 +872,7 @@ export const useHandleLibrary = (
|
||||
restoredData = restoreLibraryItems(
|
||||
libraryData.libraryItems || [],
|
||||
"published",
|
||||
{ schemaVersion: getSchemaVersion(libraryData) },
|
||||
);
|
||||
|
||||
// we don't queue this operation because it's running inside
|
||||
|
||||
@@ -39,10 +39,7 @@ import {
|
||||
} from "@excalidraw/element";
|
||||
import { LinearElementEditor } from "@excalidraw/element";
|
||||
import { bumpVersion } from "@excalidraw/element";
|
||||
import {
|
||||
CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
upgradeElementSchema,
|
||||
} from "@excalidraw/element";
|
||||
import { migrateElements } from "@excalidraw/element";
|
||||
import { getContainerElement } from "@excalidraw/element";
|
||||
import { detectLineHeight } from "@excalidraw/element";
|
||||
import {
|
||||
@@ -434,7 +431,6 @@ const restoreElementWithProperties = <
|
||||
// newly added elements
|
||||
version: element.version || 1,
|
||||
versionNonce: element.versionNonce ?? 0,
|
||||
schemaVersion: element.schemaVersion || CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
index: element.index ?? null,
|
||||
isDeleted: element.isDeleted ?? false,
|
||||
id: element.id || randomId(),
|
||||
@@ -507,7 +503,7 @@ export const restoreElement = (
|
||||
deleteInvisibleElements?: boolean;
|
||||
},
|
||||
): typeof element | null => {
|
||||
element = upgradeElementSchema({ ...element });
|
||||
element = { ...element };
|
||||
|
||||
switch (element.type) {
|
||||
case "text":
|
||||
@@ -829,18 +825,23 @@ export const restoreElements = <T extends ExcalidrawElement>(
|
||||
refreshDimensions?: boolean;
|
||||
repairBindings?: boolean;
|
||||
deleteInvisibleElements?: boolean;
|
||||
schemaVersion?: number;
|
||||
}
|
||||
| undefined,
|
||||
): CombineBrandsIfNeeded<T, OrderedExcalidrawElement> => {
|
||||
// used to detect duplicate top-level element ids
|
||||
const existingIds = new Set<string>();
|
||||
const targetElementsMap = arrayToMap(targetElements || []);
|
||||
const migratedTargetElements = migrateElements(
|
||||
targetElements || [],
|
||||
opts?.schemaVersion ?? 0,
|
||||
);
|
||||
const targetElementsMap = arrayToMap(migratedTargetElements);
|
||||
const existingElementsMap = existingElements
|
||||
? arrayToMap(existingElements)
|
||||
: null;
|
||||
|
||||
const restoredElements = syncInvalidIndices(
|
||||
(targetElements || []).reduce((elements, element) => {
|
||||
migratedTargetElements.reduce((elements, element) => {
|
||||
// filtering out selection, which is legacy, no longer kept in elements,
|
||||
// and causing issues if retained
|
||||
if (element.type === "selection") {
|
||||
@@ -1163,9 +1164,14 @@ export const restoreAppState = (
|
||||
};
|
||||
};
|
||||
|
||||
const restoreLibraryItem = (libraryItem: LibraryItem): LibraryItem | null => {
|
||||
const restoreLibraryItem = (
|
||||
libraryItem: LibraryItem,
|
||||
schemaVersion: number | undefined,
|
||||
) => {
|
||||
const elements = getNonDeletedElements(
|
||||
restoreElements(libraryItem.elements, null),
|
||||
restoreElements(getNonDeletedElements(libraryItem.elements), null, {
|
||||
schemaVersion,
|
||||
}),
|
||||
);
|
||||
return elements.length ? { ...libraryItem, elements } : null;
|
||||
};
|
||||
@@ -1173,17 +1179,24 @@ const restoreLibraryItem = (libraryItem: LibraryItem): LibraryItem | null => {
|
||||
export const restoreLibraryItems = (
|
||||
libraryItems: ImportedDataState["libraryItems"] = [],
|
||||
defaultStatus: LibraryItem["status"],
|
||||
opts?: {
|
||||
/** @see restoreElements */
|
||||
schemaVersion?: number;
|
||||
},
|
||||
) => {
|
||||
const restoredItems: LibraryItem[] = [];
|
||||
for (const item of libraryItems) {
|
||||
// migrate older libraries
|
||||
if (Array.isArray(item)) {
|
||||
const restoredItem = restoreLibraryItem({
|
||||
status: defaultStatus,
|
||||
elements: item,
|
||||
id: randomId(),
|
||||
created: Date.now(),
|
||||
});
|
||||
const restoredItem = restoreLibraryItem(
|
||||
{
|
||||
status: defaultStatus,
|
||||
elements: item,
|
||||
id: randomId(),
|
||||
created: Date.now(),
|
||||
},
|
||||
opts?.schemaVersion,
|
||||
);
|
||||
if (restoredItem) {
|
||||
restoredItems.push(restoredItem);
|
||||
}
|
||||
@@ -1192,12 +1205,15 @@ export const restoreLibraryItems = (
|
||||
LibraryItem,
|
||||
"id" | "status" | "created"
|
||||
>;
|
||||
const restoredItem = restoreLibraryItem({
|
||||
..._item,
|
||||
id: _item.id || randomId(),
|
||||
status: _item.status || defaultStatus,
|
||||
created: _item.created || Date.now(),
|
||||
});
|
||||
const restoredItem = restoreLibraryItem(
|
||||
{
|
||||
..._item,
|
||||
id: _item.id || randomId(),
|
||||
status: _item.status || defaultStatus,
|
||||
created: _item.created || Date.now(),
|
||||
},
|
||||
opts?.schemaVersion,
|
||||
);
|
||||
if (restoredItem) {
|
||||
restoredItems.push(restoredItem);
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {
|
||||
export interface ExportedDataState {
|
||||
type: string;
|
||||
version: number;
|
||||
schemaVersion: number;
|
||||
source: string;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
appState: ReturnType<typeof cleanAppStateForExport>;
|
||||
@@ -35,6 +36,7 @@ export type LegacyAppState = {
|
||||
export interface ImportedDataState {
|
||||
type?: string;
|
||||
version?: number;
|
||||
schemaVersion?: number;
|
||||
source?: string;
|
||||
elements?: readonly ExcalidrawElement[] | null;
|
||||
appState?: Readonly<
|
||||
@@ -52,6 +54,7 @@ export interface ImportedDataState {
|
||||
export interface ExportedLibraryData {
|
||||
type: string;
|
||||
version: typeof VERSIONS.excalidrawLibrary;
|
||||
schemaVersion: number;
|
||||
source: string;
|
||||
libraryItems: LibraryItems;
|
||||
}
|
||||
|
||||
@@ -1019,7 +1019,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1054,7 +1053,6 @@ exports[`contextMenu element > right-clicking on a group should select whole gro
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1216,7 +1214,6 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1272,7 +1269,6 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -1291,6 +1287,7 @@ exports[`contextMenu element > selecting 'Add to library' in context menu adds e
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -1432,7 +1429,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1014066025,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1465,7 +1461,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1521,7 +1516,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -1540,6 +1534,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -1576,7 +1571,6 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -1595,6 +1589,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -1628,6 +1623,7 @@ exports[`contextMenu element > selecting 'Bring forward' in context menu brings
|
||||
},
|
||||
},
|
||||
"id": "id7",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -1769,7 +1765,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1014066025,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1802,7 +1797,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -1858,7 +1852,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -1877,6 +1870,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -1913,7 +1907,6 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -1932,6 +1925,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -1965,6 +1959,7 @@ exports[`contextMenu element > selecting 'Bring to front' in context menu brings
|
||||
},
|
||||
},
|
||||
"id": "id7",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -2108,7 +2103,6 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2164,7 +2158,6 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -2183,6 +2176,7 @@ exports[`contextMenu element > selecting 'Copy styles' in context menu copies st
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -2322,7 +2316,6 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2378,7 +2371,6 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -2397,6 +2389,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -2428,6 +2421,7 @@ exports[`contextMenu element > selecting 'Delete' in context menu deletes elemen
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id4",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -2569,7 +2563,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2602,7 +2595,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1014066025,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2658,7 +2650,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -2677,6 +2668,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -2713,7 +2705,6 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -2732,6 +2723,7 @@ exports[`contextMenu element > selecting 'Duplicate' in context menu duplicates
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -2880,7 +2872,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2915,7 +2906,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1014066025,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -2971,7 +2961,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -2990,6 +2979,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3026,7 +3016,6 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -3045,6 +3034,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3065,6 +3055,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id8",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3110,6 +3101,7 @@ exports[`contextMenu element > selecting 'Group selection' in context menu group
|
||||
},
|
||||
},
|
||||
"id": "id11",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -3253,7 +3245,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"opacity": 60,
|
||||
"roughness": 2,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#e03131",
|
||||
"strokeStyle": "dotted",
|
||||
@@ -3286,7 +3277,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"opacity": 60,
|
||||
"roughness": 2,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 406373543,
|
||||
"strokeColor": "#e03131",
|
||||
"strokeStyle": "dotted",
|
||||
@@ -3342,7 +3332,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -3361,6 +3350,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3397,7 +3387,6 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -3416,6 +3405,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3441,6 +3431,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id7",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3466,6 +3457,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id9",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3491,6 +3483,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id11",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3516,6 +3509,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id13",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3541,6 +3535,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id15",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3566,6 +3561,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id17",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3591,6 +3587,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id19",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3636,6 +3633,7 @@ exports[`contextMenu element > selecting 'Paste styles' in context menu pastes s
|
||||
},
|
||||
},
|
||||
"id": "id21",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -3777,7 +3775,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 238820263,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -3810,7 +3807,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -3866,7 +3862,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -3885,6 +3880,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3921,7 +3917,6 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -3940,6 +3935,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -3965,6 +3961,7 @@ exports[`contextMenu element > selecting 'Send backward' in context menu sends e
|
||||
},
|
||||
},
|
||||
"id": "id7",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -4106,7 +4103,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1014066025,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -4139,7 +4135,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -4195,7 +4190,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -4214,6 +4208,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -4250,7 +4245,6 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -4269,6 +4263,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -4294,6 +4289,7 @@ exports[`contextMenu element > selecting 'Send to back' in context menu sends el
|
||||
},
|
||||
},
|
||||
"id": "id7",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -4438,7 +4434,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -4471,7 +4466,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 400692809,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -4527,7 +4521,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -4546,6 +4539,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -4582,7 +4576,6 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -4601,6 +4594,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -4621,6 +4615,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id8",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -4666,6 +4661,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
},
|
||||
},
|
||||
"id": "id11",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -4711,6 +4707,7 @@ exports[`contextMenu element > selecting 'Ungroup selection' in context menu ung
|
||||
},
|
||||
},
|
||||
"id": "id13",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -5729,7 +5726,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -5762,7 +5758,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 400692809,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -5818,7 +5813,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -5837,6 +5831,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -5873,7 +5868,6 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -5892,6 +5886,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -5914,6 +5909,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id8",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -5934,6 +5930,7 @@ exports[`contextMenu element > shows 'Group selection' in context menu for multi
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id11",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -6956,7 +6953,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -6991,7 +6987,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 400692809,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -7047,7 +7042,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -7066,6 +7060,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -7102,7 +7097,6 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -7121,6 +7115,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id5",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -7143,6 +7138,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id8",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -7163,6 +7159,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id11",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
{
|
||||
"appState": AppStateDelta {
|
||||
@@ -7208,6 +7205,7 @@ exports[`contextMenu element > shows 'Ungroup selection' in context menu for gro
|
||||
},
|
||||
},
|
||||
"id": "id14",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
@@ -9910,7 +9908,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -9943,7 +9940,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -9976,7 +9972,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] el
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -10038,7 +10033,6 @@ exports[`contextMenu element > shows context menu for element > [end of test] un
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
"strokeWidth": 2,
|
||||
@@ -10057,6 +10051,7 @@ exports[`contextMenu element > shows context menu for element > [end of test] un
|
||||
"updated": {},
|
||||
},
|
||||
"id": "id2",
|
||||
"schemaVersion": 1,
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
||||
@@ -35,7 +35,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -72,7 +71,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -107,7 +105,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -153,7 +150,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -190,7 +186,6 @@ exports[`Test dragCreate > add element to the scene when pointer dragging long e
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`export > export svg-embedded scene > svg-embdedded scene export output 1`] = `
|
||||
"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="36" height="36"><!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTwW7bMFxmve8rXGb3WqxOiu6QW7u2aFx1MDAwZu2hXHUwMDE5tkOxg2oxNlx1MDAxMUVcdTAwMTIkuklcdTAwMTZcdTAwMDTYZ+zWX+wnlFI8K3bSXHUwMDA0MKBHUnx8fNp8ybKc1lx1MDAxNvJJlsOqXHUwMDE0XG6lXHUwMDEzy/w04K/gPFx1MDAxYc2hcTx707gyZtZEdnJ2plxmXHUwMDE31MbT5Lwoil1cdTAwMTEoWIAmz2nPfM6yTfxyXHUwMDA0ZSi9jGlcdTAwMTH435dgRVx0XTFUdKd177RESTUjo29cdTAwMWRUXHUwMDAzVjX1MaErXHUwMDA1vUJPzszhu1HGhY4nI1xi/9T0RZTzyplGyy6HnNDeXG7Hw6S8XHUwMDE5KjWldbyd9WC18kGPXy3F8Vx1MDAwMP+siptWtVx1MDAwNlx1MDAxZlx1MDAwNFx1MDAxYnWosaJEXG7Dj4o0RWBo72XU9nfi5MRcdTAwMDLug7i6UaqDUUtYXHLBOGLbrVx1MDAxN/BcdTAwMDCyxyAt/1x1MDAwMHs0ulx1MDAxY8hb1rBcdTAwMTA/j5Sgv2ZHULx8JpSHJHmgcpPc0qPTWClowEihnlx1MDAwZvPYgfMjd0dHsdbvb//+7i3PaJrin0B9XFz00FuxQFx1MDAxNdS+6F1xqbBcbuPkXG5meyZgXHUwMDE5XGLZ+V2YjE3Rku9cdTAwMTOowVx1MDAxZK7EOKxQXHUwMDBi9eMoPdGQeVx1MDAwMr8jSK6B/cnhrvP51/FFXGZs+Vx1MDAxYl2QXHUwMDBia6fEenF099rYKShcdTAwMDej7jBcdTAwMDKbxozQg5Fwo8WLXHUwMDFh6pi/XCIsr1x1MDAwZVx1MDAxZsbJLP5a4u1cblx1MDAxZVx1MDAxYUU45VWXxFx1MDAxNlxi+9xcdTAwMDR+28iPX1xytND2XHUwMDAzo9gvdiJ9<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
"<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="36" height="36"><!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nHVTTW/UMFx1MDAxML3zK6L0imh2UTnsrUCr9lBcdTAwMGUsglx1MDAwM+LgxrPJaL22ZU/2g9VK/FxmbvxFflx1MDAwMmMnjddpSaRIfjOeee/N5PiqKEo6WChcdTAwMTdFXHT7WiiUTuzK11x1MDAwMd+C82g0h+bx7OtcdTAwMTY24uuIznrUdK6O91tcIru4vFSGy7TG0+JtVVV9KVCwXHUwMDAxTZ7TvvO5KI7xy1x1MDAxMZTh6nVMi8BcdTAwMTNcdTAwMWKCPSV0z1A1nlx1MDAwZdlph5LawOfdXGK1gE1LOSZ0oyC76MmZNXwwyrjQ8WJcdTAwMDbhTU1cdTAwMWZFvW6c6bRcdTAwMWNzyFx07a1wLCblrVCpJVx1MDAxZGJ19oM9LCc9vlxyXHUwMDE051x1MDAxM/x/t7hp02rw/snmiFx1MDAxYStqpCB+ViVcdTAwMTWBob2X0dtcdTAwMWaJk1x1MDAxM1x1MDAxYrhcdTAwMGbm6k6pXHUwMDExRi1hP1x1MDAwNaPEoVtcdTAwMTbwXHUwMDAwMmOwzYd/jn0yus7tRf+R506xxEooXHUwMDBmydjQ8CbtRNa0s1LQpK9CvZ7m8Z6tX6hcdTAwMWT3hlx1MDAxZP375/evs1x1MDAxMVx1MDAxOU1L/Fx1MDAxOVxizqtcZr1cdTAwMTVcdTAwMWJUwdOrrMS1wiboLFx1MDAxNazORs1iXHR5v8cwXHUwMDE5m6I111x1MDAxM6jBPTfeOGxQXHUwMDBi9eVFeqIj81x1MDAxOXxPkFxcXHUwMDA358rhbtzmN/OrXHUwMDE4OPE3zrpcdTAwMTTWLon94mj/T/E+oJxI7TFcdTAwMDKbZEbowUi40eJRTX0st1xiu/fP1/9iXHUwMDE1n4H4MIKHTlx1MDAxMS551DXxXHUwMDFlhHlcdTAwMWVcdTAwMDO/U+TH/1x1MDAwNlxm0OlcdTAwMWZm+S72In0=<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
</style></defs><rect x="0" y="0" width="36" height="36" fill="#ffffff"></rect><g transform="translate(10 10) rotate(0 8 8)" data-id="A"><text x="0" y="17.619999999999997" font-family="Excalifont, Xiaolai, sans-serif, Segoe UI Emoji" font-size="20px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="alphabetic">😀</text></g></svg>"
|
||||
`;
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,6 @@ exports[`duplicate element on move when ALT is clicked > rectangle 5`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -51,7 +50,6 @@ exports[`duplicate element on move when ALT is clicked > rectangle 6`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1505387817,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -84,7 +82,6 @@ exports[`move element > rectangle 5`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -122,7 +119,6 @@ exports[`move element > rectangles with binding arrow 5`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -160,7 +156,6 @@ exports[`move element > rectangles with binding arrow 6`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1116226695,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -216,7 +211,6 @@ exports[`move element > rectangles with binding arrow 7`] = `
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": 23633383,
|
||||
"startArrowhead": null,
|
||||
"startBinding": {
|
||||
|
||||
@@ -37,7 +37,6 @@ exports[`multi point mode in linear elements > arrow 3`] = `
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -89,7 +88,6 @@ exports[`multi point mode in linear elements > line 3`] = `
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -33,7 +33,6 @@ exports[`select single element on the scene > arrow 1`] = `
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -81,7 +80,6 @@ exports[`select single element on the scene > arrow escape 1`] = `
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -116,7 +114,6 @@ exports[`select single element on the scene > diamond 1`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -149,7 +146,6 @@ exports[`select single element on the scene > ellipse 1`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -182,7 +178,6 @@ exports[`select single element on the scene > rectangle 1`] = `
|
||||
"opacity": 100,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": 1278240551,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
|
||||
@@ -31,7 +31,6 @@ exports[`repairing bindings > should strip arrow binding if repair throws 1`] =
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -79,7 +78,6 @@ exports[`restoreElements > should restore arrow element correctly 1`] = `
|
||||
],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -120,7 +118,6 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"roundness": {
|
||||
"type": 3,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "red",
|
||||
"strokeStyle": "dashed",
|
||||
@@ -159,7 +156,6 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "red",
|
||||
"strokeStyle": "dashed",
|
||||
@@ -198,7 +194,6 @@ exports[`restoreElements > should restore correctly with rectangle, ellipse and
|
||||
"roundness": {
|
||||
"type": 2,
|
||||
},
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "red",
|
||||
"strokeStyle": "dashed",
|
||||
@@ -242,7 +237,6 @@ exports[`restoreElements > should restore freedraw element correctly 1`] = `
|
||||
"pressures": [],
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"simulatePressure": true,
|
||||
"strokeColor": "#1e1e1e",
|
||||
@@ -293,7 +287,6 @@ exports[`restoreElements > should restore line and draw elements correctly 1`] =
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -341,7 +334,6 @@ exports[`restoreElements > should restore line and draw elements correctly 2`] =
|
||||
"polygon": false,
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"startArrowhead": null,
|
||||
"startBinding": null,
|
||||
@@ -382,7 +374,6 @@ exports[`restoreElements > should restore text element correctly passing value f
|
||||
"originalText": "text",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
@@ -425,7 +416,6 @@ exports[`restoreElements > should restore text element correctly with unknown fo
|
||||
"originalText": "",
|
||||
"roughness": 1,
|
||||
"roundness": null,
|
||||
"schemaVersion": 1,
|
||||
"seed": Any<Number>,
|
||||
"strokeColor": "#1e1e1e",
|
||||
"strokeStyle": "solid",
|
||||
|
||||
@@ -4,7 +4,7 @@ import { vi } from "vitest";
|
||||
import { DEFAULT_SIDEBAR, FONT_FAMILY, ROUNDNESS } from "@excalidraw/common";
|
||||
|
||||
import { newElementWith } from "@excalidraw/element";
|
||||
import { CURRENT_ELEMENT_SCHEMA_VERSION } from "@excalidraw/element";
|
||||
import { CURRENT_SCHEMA_VERSION } from "@excalidraw/element";
|
||||
import * as sizeHelpers from "@excalidraw/element";
|
||||
|
||||
import type { LocalPoint } from "@excalidraw/math";
|
||||
@@ -44,7 +44,7 @@ describe("restoreElements", () => {
|
||||
expect(restoredElements.length).toBe(elements.length);
|
||||
});
|
||||
|
||||
it("lifts a legacy (unversioned) element to the current schema version", () => {
|
||||
it("restores legacy (unversioned) elements through the migration pipeline", () => {
|
||||
// element persisted before schema versioning existed
|
||||
const legacyElement = {
|
||||
type: "rectangle",
|
||||
@@ -53,30 +53,18 @@ describe("restoreElements", () => {
|
||||
y: 0,
|
||||
} as any as ExcalidrawElement;
|
||||
|
||||
expect(legacyElement).not.toHaveProperty("schemaVersion");
|
||||
|
||||
const [restored] = restore.restoreElements([legacyElement], null);
|
||||
expect(restored.schemaVersion).toBe(CURRENT_ELEMENT_SCHEMA_VERSION);
|
||||
expect(restored.id).toBe("legacy");
|
||||
expect(restored.type).toBe("rectangle");
|
||||
});
|
||||
|
||||
it("keeps the schema version of an already-versioned element", () => {
|
||||
const versionedElement = API.createElement({ type: "rectangle" });
|
||||
expect(versionedElement.schemaVersion).toBe(CURRENT_ELEMENT_SCHEMA_VERSION);
|
||||
it("restores elements coming from a container at the current schema version", () => {
|
||||
const element = API.createElement({ type: "rectangle" });
|
||||
|
||||
const [restored] = restore.restoreElements([versionedElement], null);
|
||||
expect(restored.schemaVersion).toBe(CURRENT_ELEMENT_SCHEMA_VERSION);
|
||||
});
|
||||
|
||||
it("stamps newly created elements with the current schema version", () => {
|
||||
expect(API.createElement({ type: "rectangle" }).schemaVersion).toBe(
|
||||
CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
);
|
||||
expect(API.createElement({ type: "text" }).schemaVersion).toBe(
|
||||
CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
);
|
||||
expect(API.createElement({ type: "arrow" }).schemaVersion).toBe(
|
||||
CURRENT_ELEMENT_SCHEMA_VERSION,
|
||||
);
|
||||
const [restored] = restore.restoreElements([element], null, {
|
||||
schemaVersion: CURRENT_SCHEMA_VERSION,
|
||||
});
|
||||
expect(restored.id).toBe(element.id);
|
||||
});
|
||||
|
||||
it("when imported data state is null it should return an empty array of elements", () => {
|
||||
|
||||
@@ -25,7 +25,6 @@ const elementBase: Omit<ExcalidrawElement, "type"> = {
|
||||
seed: 1041657908,
|
||||
version: 120,
|
||||
versionNonce: 1188004276,
|
||||
schemaVersion: 1,
|
||||
isDeleted: false,
|
||||
boundElements: null,
|
||||
updated: 1,
|
||||
|
||||
@@ -253,7 +253,6 @@ export class API {
|
||||
| "type"
|
||||
| "version"
|
||||
| "versionNonce"
|
||||
| "schemaVersion"
|
||||
| "isDeleted"
|
||||
| "groupIds"
|
||||
| "link"
|
||||
|
||||
@@ -359,7 +359,6 @@ describe("Basic lasso selection tests", () => {
|
||||
...e,
|
||||
angle: e.angle as Radians,
|
||||
index: null,
|
||||
schemaVersion: 1,
|
||||
} as ExcalidrawElement),
|
||||
);
|
||||
|
||||
@@ -1045,7 +1044,6 @@ describe("Special cases", () => {
|
||||
...e,
|
||||
index: null,
|
||||
angle: e.angle as Radians,
|
||||
schemaVersion: 1,
|
||||
})) as ExcalidrawElement[];
|
||||
|
||||
h.elements = elements;
|
||||
@@ -1765,7 +1763,6 @@ describe("Special cases", () => {
|
||||
...e,
|
||||
index: null,
|
||||
angle: e.angle as Radians,
|
||||
schemaVersion: 1,
|
||||
})) as ExcalidrawElement[];
|
||||
|
||||
h.elements = elements;
|
||||
|
||||
@@ -240,7 +240,7 @@ exports[`exportToSvg > with elements that have a link 1`] = `
|
||||
`;
|
||||
|
||||
exports[`exportToSvg > with exportEmbedScene 1`] = `
|
||||
"<!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1WS4/TMFx1MDAxML7vr4jMdcU6abstvVx1MDAxNZbHSlxiJIpYXHTEwVx1MDAxYk9cdTAwMTIrjp21nT6o+t+xXHUwMDFkXHUwMDFhp2GFOLK0qVx1MDAxNGneM998o3R3XHUwMDExRchsa0DzXGLBJiWcUUXW6NLpV6A0k8KaXHUwMDEyL2vZqNR7XHUwMDE2xtTzqysubUAhtZmPMMZtXHUwMDEwcKhAXHUwMDE4bd2+WTmKdv5tLYy60NWdevi4yEomaPKQVtP3N4u3X32od9pYn3E87uStqz6advKaUVNYXYxxpyuA5YVcdTAwMTkoici56zVotFGyhFeSS+VcdTAwMWF5hv1cdTAwMTNK35O0zJVsXHUwMDA0XHI+8YSQ+yz4ZIzzpdnyXHUwMDE2XHUwMDA1klx1MDAxNo1cdTAwMDI0qHB3aHGg7+K0tCiHKFsyL1x1MDAwNGh9XHUwMDE0I2uSMrNcdTAwMWRM5fqrb6lH93voSpFcbm5cdTAwMWS8ouG8n1jQX4mPXGZcdTAwMTZ7cEAj0lx1MDAxYl9cdTAwMDNQX21cdTAwMWNfT6Yv8KyzXHUwMDA0XHUwMDFlxFx0XHUwMDFlaj9I4TlcdTAwMTHHs1x1MDAxOcbjZHpcdTAwMWQyplx1MDAwNVTkS4hcdTAwMGVccuhcdTAwMWLLXHUwMDEz41x1MDAwYmaEa1xiS3A9v1x1MDAwZVx1MDAxYzrqu6kpaYNCKs5EOfSzvCxcdTAwMWbJfeA5ZaSSglwir99fnln6r7M0PkWWXHUwMDAy56zW8DRYXHUwMDFhg/udNkuTU2SpgY3prVRcbrNkP5ylN53TviFcdTAwMTXjblx1MDAwM5OQwoXaXHUwMDE0UrGcXHTCo+NcXFx1MDAwN/XnP7s5acFZ7kBBXHUwMDFjsp7JomqY/YfSmY2sgzW1XVx1MDAxMSZA/b5si1x1MDAwM7zrXHUwMDBl4XlcdTAwMTJ6Jo2Rn0C3I3pEzuf5RM5zdD7PvzjPMNL/dJ727SmESF0vjVx1MDAwNdaa22NFK1x1MDAwNuuXj5xD5lx1MDAxZvf19cftzlx1MDAwMdyWdvuL/U+/32JDIn0=<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
"<!-- svg-source:excalidraw --><metadata><!-- payload-type:application/vnd.excalidraw+json --><!-- payload-version:2 --><!-- payload-start -->eyJ2ZXJzaW9uIjoiMSIsImVuY29kaW5nIjoiYnN0cmluZyIsImNvbXByZXNzZWQiOnRydWUsImVuY29kZWQiOiJ4nO1WW2/TMFx1MDAxOH3fr4jM68SctF1L31xu4zJcdIFEXHUwMDExk0A8ePGXxIpjZ7bTXHUwMDBiVf/7bIfGaZh4ZqWpXHUwMDE0yee7+Pj4fEp3XHUwMDE3UYTMtlx1MDAwNjSPXHUwMDEwbFLCXHUwMDE5VWSNLlx1MDAxZL5cdTAwMDKlmVx1MDAxNDaU+LVOXHUwMDBiqMi3XHUwMDBljVtUNir19YUx9fzqikvbppDazEdcdTAwMTjjtlx1MDAxNXCoQFx1MDAxOG3Tfth1XHUwMDE07fzbRlx1MDAxOHWlqzv18HmRlUzQ5CGtplx1MDAxZm9cdTAwMTbvv/tSn7SxOeN43K23jtNo2q3XjJrCMcK4w1xuYHlhXHUwMDA2IFx1MDAxMTl3XFxcdTAwMDOijZIlvJFcXCpH5Fx1MDAwNfZP2PqepGWuZCNoyIknhNxnISdjnC/NlrcqkLRoXHUwMDE0oMFcdTAwMGV3XHUwMDA3ilx1MDAwM7yr09JqXHUwMDFmquyWeSFA66NcdTAwMWFZk5SZ7eBUjl99S726P1x1MDAwMytFKrh18oqG835jQX83PlxuWO3BXHSNSO/4XHUwMDFhgPrdxvH1ZPpcbs+6SHBHnOAh+klcbu+JOJ7NMFx1MDAxZSfT67CNvrFuML5tRriGILVj9jY45YhdU1PSXHUwMDE2XHUwMDA1NThcdTAwMTPlMM+6r3yi98HjlJFKXG6KPL6/PHvxX/difLpeXHUwMDA0zlmt4Xl4MVx1MDAwNvf7v72YnK5cdTAwMTdccmxM7+KkMEv2y0V6Z3DoO1Ix7nSehFx1MDAxNq7UtpCK5UxcdTAwMTBcdTAwMWVcdTAwMWT3OsBf/57mVlx1MDAwYs5yJ1x1MDAxY+KQ9UJWO8Psf4oubGRcdTAwMWSiqWVFmFx1MDAwMPXnlVpcdTAwMWTgQ2f3l0ngTFx1MDAxYSO/gG6P6Fx1MDAxNTlcdTAwMGbhM1x1MDAxOcLReVxiuyFcZsRPaVxi7dtcdTAwMWJcdTAwMDWRul5cdTAwMWErrFxytyOJVlxm1q+fMH3mXHUwMDFm9yX1I+xMXHUwMDBm7pZ2+4v9Iz9vTG8ifQ==<!-- payload-end --></metadata><defs><style class="style-fonts">
|
||||
@font-face { font-family: Excalifont; src: url(data:font/woff2;base64,d09GMgABAAAAAAf0AA4AAAAADbQAAAegAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGhYbgjgcNAZgAHwRCAqPYItcCxoAATYCJAMwBCAFgxgHIBujClGUblKX7Edh3Pg8OJbt5MlDFPwWk6FmJ08terdayeDx9O8Fa5eunTJ1ykAUGuAvgOF5bPfuH0nXrvQrNkFoNlERBLIQDd/mj2kP7fdg8avL4M/sm4cbu4L2i1VQVjShz/+90/YE3IJFmOC+f0Z1Xhs3bVxiJZZGa1IgY5sHCJ2EM2iBndkLWsZvFxCAhRBGCknKEOQenHCpmE2fVQTl1atzWyjvzs3aQPnWd20PJQZeG3mQp1lnLimFBAhOKFIQSrGa+vOB69gLCRkjLr4NIjd5rXiz8hhu+LIe9DZCgkJjmZEy34MkhXNBJ8GhNMMnJUgmSYA0AowAwT2zRor05iZAeN/80c4UkYlgSTOaKgOQCUOSE5F+z6+DXqndDTk4CEHP2v3/aKw7lXQXgAUAQP5+kgIpTYMHT9MEjIyWxJ5V5jAzgnrNtNBKWx101tXee86rA5O1f1fiIzEtJsS4GBMjYlikIhEvwwXDjYgiApUFdjgCFAA1m2GorwgdA5a6fzxxX0ei+Eqw6MspHfbmte7eLSh2FSEREgnySedVN+VfsbKAVQygCrGALXgDQoSA0kZqfB2OiyyaZolc/i3tnXswySlmifL9eo1ReyWQcYadAK/DD9c80uaI4pamykJM2hp/MF2uj+teZm9LiRlrltgBlcVYlWVWf+WPTpVJj40Kz9Gvi5p21Zcov8bKEL+5Uirz+j2Hjzr+99zosad2XtbpfsbKgyIQ3RJHxrTH1ysQQAjTUaii13B+OTtxrFoLubjpMYtit2uul6DdSPvKaQl/9FkppthYCXEvjoEdStVVjti2fUQf34q9YjychmIsxCAjKkj0GW4mIhlX1snPOEVRVq4UMRQwkzJNhK143IsBOlghz9meJAGf4apbAmYRMj2hsj5X1OGDPuIKXoOyN8yIMYbeusXTa9f4a+1UvYG9FgQxzoQBUoSPD5/ezAuQIP8s8IyVrUGrwSxC95WPAdlx3EkkQq5yNjcaKAAZ4LEHheIMvgHV3fe6X41/6rzCiMBwSxrnvl9RcR0gK5fy4jDVigBySy99iwlppT9aTlWIMSMKBx9WkbupSbZXNC/CKUC6z53jA0mS7340zruutV7xYguW6p/01tYHOQZQ7ptAagFiLAzFWqyWUwN8/eNGSyn2elPguvF9At0XUNlNv4t202nq+EnkKvdW6aLVlJUxhpXYUqMsn5svp9eoTpvKKf6BY8QwAkaJWYSggEuximEYiCVE0TjN5ojkVoznAP0GytsVT1SesrOn/s8LsMpVpXtSc6To4sT6XIl1q0jH96rd5/g5vmi35+y6eeYPNqjBGHtX0nKFGywfPr19aO9Gh/Q1sWZGuqckOoOJmlK2pSPTSgm/46h389ap+VmOnxsPdspP82Xyy2l7u6i2908pVjRkDbEddOKOprOPJ/94eJ59fi6jDtB6VbzuwXb5puJcqcKnUeM72fFnIlssAv/Z6uMq2s1Jd8qpd7DZo538w5PG7cHm2TU2TD41PjYiaUSikYspa/kjJ84AdgqXsT226/L+fW6j99QmJZlWN99tfcdbUPO17tLu8+gVw73h3OfmurdS9BCa5twNfokJhx7/agK3fHC9rXVewbT0kZG9vXqRBOtS6xGOZ5dC3pwleZIyidL1le+xC3btOlwJkXqGESosoDUG2TqcuE/6OcolnOykkpKdD+5l7+JODWBpAycPke+STDMRWidsWCLvfQJbKM7JpnkLG1WV73qjg6eZ1EMVF1NgG5rTaZF2r7tJNs2VzabNrTQfhSOtgxRxRLP5mldJB6/b6R5LWk9ss9nNUGyTTx8iytdCS9xlAlvtN/1hTrYObLVe5T8zLWNk2oko/bq0dv1cvjXrGC5k+pfYm+L1MtXCFsSzKDTY/dhu66JJWqlfkIxw7T5ufjDBa4pCy7ObPC0cojzlZMlCh2VjKwfX8qy5cpyRt1hp6DeKs/UZjc6zfGxC0rJHl1IUtIm2p7Tg+6VKn8vbfWR9fHljiebwTXPXTQ13HmoSbMssDsa32NnSfmmoH92xuU/E7U2qS8vWYFD9D73VJodqvWU8l7tB+b9ZdPiXvC2qoboerfoamG78+oOX7uWn1/WJy87rJPSHcVmUO7sN0ypzcG5zdfzU8k/NSh5OCbHvUSJlOk2xGrkmubA+nV7YtUCdoJk2vZ/dNSvrAkFnE0toKlC7rHllSrrOzt4Yb91LaLga9TtY+etg/uCpE8rVr604yv7YaE2x450wAACIfUvVNvFrrWXyNylLvwSAR70CtQDwePGbPuLQ/32Y10wCAClKi8AXKzralCj+/e0Dwri9RW2ArkwEqBW+cYJzghDkKyuPEB0FElKG8DIXEQkASS4s/2PSN2DGs1Bi6eh9EhVoZDAH0NblyEbw9tsoChtttEDDbQxvDTZWKm+YcYcDOj01qtdWK8110F5XIQq+nMJuwnpSopmb7KJVWbUgQqhwkyQxlOd76aglFYEKoxAJ8OeFkYPjBPTuQejsbXStQgY5klp1cr1LoQHSUa8StKqVbKnrXAqNAgg8wm1EB6RBL7ejLWnSmSI9hGJQZdAW2trTXRJoBsJm6M5VNQlFM3yJ/7EAAAA=); }
|
||||
@font-face { font-family: Excalifont; src: url(data:font/woff2;base64,d09GMgABAAAAAAHcAA0AAAAAA9gAAAGMAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx4cNAZgAAQRCAoAKgsEAAE2AiQDBAQgBYMYByAbHQPIrgp4MjSeIQLqpl4jnPFQwveqm205gmrZevZ2/8kKGaJ6ictBuCwMMnsKo8hC4RwaLBrJ7dXaFrFk0Re1e/Dk3t9wCUVrYAiZkgiZ5rGDVFdY04wBF4APOCzXHr/ljrNIDqLAY/slkQf0SyjxLNBaAq8Z1AJqYW5j0GjbckRLQhoJCgwYoyfY3q33QS5DrSAwYB4hFruNve4WoN2On4P29GEFWsC+nVM/UvSlVwIcsWycI/5gbqfUhE/9Q2P7UppTOAVeIR4TYIocELVKIEACZJgXCJ5GXl0o+esbFP3g+763DcDPx6/60MrnBaBPAsGPUDRC/8+FGIqGUSBA+NIJOwTWe13HRCIrgEvfRhj1RjLiE42eG7I5DIpVNuqkNNhxwaItTI2srRz4dfHGjhZoO0O8nb2pilFYQKhenFaycLUxsYcoAQRyoRBEnPvcgfysPq+npClt8UxLyvHWjaudqbGJA+TCckNECBGBGFforuTs0M4CUMbCAvp+P4in4o864XECREBtFQAAAAA=); }
|
||||
@font-face { font-family: Nunito; src: url(data:font/woff2;base64,d09GMgABAAAAAAIsAA4AAAAABLQAAAHYAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGx4cLgZgP1NUQVREAAQRCAoAKgsEAAE2AiQDBAQgBYQkByAb5wPIrgp4Mt6IIcJZFNCfWmZY6KzqazTt6AiVHCFawzJ7V0CQBKoSQQEpFKrCVxiSFbqqqsaTevKs/q7s6uBYmujMyA9wxb6a7XnEOcNnDLgG4nW8PoPzHzYpS2uqp51pL3eB/xZoxIllCQc80B9o4j/4xMbxQB+j+SC3hsm6JmI8RMaHj+aJApW6ZbkXlg4vXSE5FECg0og6LzxP9pOarug4tF1RLpbHeZqLX0pIt2mfy3pNG6eyGaRIjrnrr/gv2c//yGdjpJ/7DuJLin5eIZRLaObBMM/NpYpuXJ8z3SE088mEFANcCARESfwChCioAwESsVxBgeyxp6+vZ3Xzv8uz7Ae8tRk+p6RUTPOR7BmlEgh+STsAimNuKibilyluhBNe5/wCACSBKrcykVfgyb+RYcK/TGq9yMyCt3bOskSnR1FqTLMSVBsMGLQVltDKKw2IYA+wcGxHJCINY9mDOCaN4IZEo1ChY4xNg8DaB0RxDqnbMNjXJJRzF9iInqahtm67M8dOHFvXaYbn+wrGxKG3YZI+2V4CW58ovdfV1tFHXFJJiPH7T1FAJxEgYo5BKkA5iDIVQluOqZYWhQapGF6TAFhaFAAoTBIZsCFHi/0oV3gpVKwbAA==); }
|
||||
|
||||
Reference in New Issue
Block a user