diff --git a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
index 3d6fb90eb..909cbf5a7 100644
--- a/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
+++ b/dev-docs/docs/@excalidraw/excalidraw/api/props/excalidraw-api.mdx
@@ -65,7 +65,7 @@ You can use this function to update the scene with the sceneData. It accepts the
| `elements` | [`ImportedDataState["elements"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L38) | The `elements` to be updated in the scene |
| `appState` | [`ImportedDataState["appState"]`](https://github.com/excalidraw/excalidraw/blob/master/packages/excalidraw/data/types.ts#L39) | The `appState` to be updated in the scene. |
| `collaborators` | MapCollaborator>
| The list of collaborators to be updated in the scene. |
-| `SnapshotAction` | `SnapshotAction` | Implies if the change should be captured by the `store`. Captured changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `SnapshotAction.CAPTURE`. |
+| `storeAction` | `StoreAction` | Implies if the change should be captured by the `store`. Captured changes are emmitted and listened to by other components, such as `History` for undo / redo purposes. Defaults to `StoreAction.CAPTURE`. |
```jsx live
function App() {
diff --git a/excalidraw-app/App.tsx b/excalidraw-app/App.tsx
index 2df267b79..42ea7b37f 100644
--- a/excalidraw-app/App.tsx
+++ b/excalidraw-app/App.tsx
@@ -24,7 +24,7 @@ import {
Excalidraw,
LiveCollaborationTrigger,
TTDDialogTrigger,
- SnapshotAction,
+ StoreAction,
reconcileElements,
newElementWith,
} from "../packages/excalidraw";
@@ -527,7 +527,7 @@ const ExcalidrawWrapper = () => {
excalidrawAPI.updateScene({
...data.scene,
...restore(data.scene, null, null, { repairBindings: true }),
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
}
});
@@ -554,7 +554,7 @@ const ExcalidrawWrapper = () => {
setLangCode(getPreferredLanguage());
excalidrawAPI.updateScene({
...localDataState,
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
LibraryIndexedDBAdapter.load().then((data) => {
if (data) {
@@ -686,7 +686,7 @@ const ExcalidrawWrapper = () => {
if (didChange) {
excalidrawAPI.updateScene({
elements,
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
}
}
@@ -856,9 +856,15 @@ const ExcalidrawWrapper = () => {
const debouncedTimeTravel = debounce(
(value: number, direction: "forward" | "backward") => {
- let elements = new Map(
- excalidrawAPI?.getSceneElements().map((x) => [x.id, x]),
- );
+ if (!excalidrawAPI) {
+ return;
+ }
+
+ let nextAppState = excalidrawAPI.getAppState();
+ // CFDO: retrieve the scene map already
+ let nextElements = new Map(
+ excalidrawAPI.getSceneElements().map((x) => [x.id, x]),
+ ) as SceneElementsMap;
let deltas: StoreDelta[] = [];
@@ -879,19 +885,20 @@ const ExcalidrawWrapper = () => {
}
for (const delta of deltas) {
- [elements] = delta.elements.applyTo(
- elements as SceneElementsMap,
- excalidrawAPI?.store.snapshot.elements!,
+ [nextElements, nextAppState] = excalidrawAPI.store.applyDeltaTo(
+ delta,
+ nextElements,
+ nextAppState,
);
}
excalidrawAPI?.updateScene({
appState: {
- ...excalidrawAPI?.getAppState(),
+ ...nextAppState,
viewModeEnabled: value !== acknowledgedDeltas.length,
},
- elements: Array.from(elements.values()),
- snapshotAction: SnapshotAction.NONE,
+ elements: Array.from(nextElements.values()),
+ storeAction: StoreAction.UPDATE,
});
},
0,
@@ -918,7 +925,6 @@ const ExcalidrawWrapper = () => {
value={sliderVersion}
onChange={(value) => {
const nextSliderVersion = value as number;
- // CFDO II: should be disabled when offline! (later we could have speculative changes in the versioning log as well)
// CFDO: in safari the whole canvas gets selected when dragging
if (nextSliderVersion !== acknowledgedDeltas.length) {
// don't listen to updates in the detached mode
diff --git a/excalidraw-app/collab/Collab.tsx b/excalidraw-app/collab/Collab.tsx
index 0b8f317c5..931659476 100644
--- a/excalidraw-app/collab/Collab.tsx
+++ b/excalidraw-app/collab/Collab.tsx
@@ -15,7 +15,7 @@ import type {
OrderedExcalidrawElement,
} from "../../packages/excalidraw/element/types";
import {
- SnapshotAction,
+ StoreAction,
getSceneVersion,
restoreElements,
zoomToFitBounds,
@@ -393,7 +393,7 @@ class Collab extends PureComponent {
this.excalidrawAPI.updateScene({
elements,
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
}
};
@@ -544,7 +544,7 @@ class Collab extends PureComponent {
// to database even if deleted before creating the room.
this.excalidrawAPI.updateScene({
elements,
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
this.saveCollabRoomToFirebase(getSyncableElements(elements));
@@ -782,7 +782,7 @@ class Collab extends PureComponent {
) => {
this.excalidrawAPI.updateScene({
elements,
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
this.loadImageFiles();
diff --git a/excalidraw-app/collab/Portal.tsx b/excalidraw-app/collab/Portal.tsx
index 8da42d3f0..0a16f9584 100644
--- a/excalidraw-app/collab/Portal.tsx
+++ b/excalidraw-app/collab/Portal.tsx
@@ -19,7 +19,7 @@ import throttle from "lodash.throttle";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { encryptData } from "../../packages/excalidraw/data/encryption";
import type { Socket } from "socket.io-client";
-import { SnapshotAction } from "../../packages/excalidraw";
+import { StoreAction } from "../../packages/excalidraw";
class Portal {
collab: TCollabClass;
@@ -133,7 +133,7 @@ class Portal {
if (isChanged) {
this.collab.excalidrawAPI.updateScene({
elements: newElements,
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
}
}, FILE_UPLOAD_TIMEOUT);
diff --git a/excalidraw-app/data/FileManager.ts b/excalidraw-app/data/FileManager.ts
index fd061e9fb..56f832b68 100644
--- a/excalidraw-app/data/FileManager.ts
+++ b/excalidraw-app/data/FileManager.ts
@@ -1,4 +1,4 @@
-import { SnapshotAction } from "../../packages/excalidraw";
+import { StoreAction } from "../../packages/excalidraw";
import { compressData } from "../../packages/excalidraw/data/encode";
import { newElementWith } from "../../packages/excalidraw/element/mutateElement";
import { isInitializedImageElement } from "../../packages/excalidraw/element/typeChecks";
@@ -268,6 +268,6 @@ export const updateStaleImageStatuses = (params: {
}
return element;
}),
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
};
diff --git a/excalidraw-app/tests/collab.test.tsx b/excalidraw-app/tests/collab.test.tsx
index 9ba9687ad..be3399548 100644
--- a/excalidraw-app/tests/collab.test.tsx
+++ b/excalidraw-app/tests/collab.test.tsx
@@ -11,7 +11,7 @@ import {
createRedoAction,
createUndoAction,
} from "../../packages/excalidraw/actions/actionHistory";
-import { SnapshotAction, newElementWith } from "../../packages/excalidraw";
+import { StoreAction, newElementWith } from "../../packages/excalidraw";
const { h } = window;
@@ -89,7 +89,7 @@ describe("collaboration", () => {
API.updateScene({
elements: syncInvalidIndices([rect1, rect2]),
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
API.updateScene({
@@ -97,7 +97,7 @@ describe("collaboration", () => {
rect1,
newElementWith(h.elements[1], { isDeleted: true }),
]),
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
await waitFor(() => {
@@ -144,7 +144,7 @@ describe("collaboration", () => {
// simulate force deleting the element remotely
API.updateScene({
elements: syncInvalidIndices([rect1]),
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
await waitFor(() => {
@@ -182,7 +182,7 @@ describe("collaboration", () => {
h.elements[0],
newElementWith(h.elements[1], { x: 100 }),
]),
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
await waitFor(() => {
@@ -217,7 +217,7 @@ describe("collaboration", () => {
// simulate force deleting the element remotely
API.updateScene({
elements: syncInvalidIndices([rect1]),
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// snapshot was correctly updated and marked the element as deleted
diff --git a/packages/excalidraw/actions/actionAddToLibrary.ts b/packages/excalidraw/actions/actionAddToLibrary.ts
index 180cb0715..93fddf0c4 100644
--- a/packages/excalidraw/actions/actionAddToLibrary.ts
+++ b/packages/excalidraw/actions/actionAddToLibrary.ts
@@ -3,7 +3,7 @@ import { deepCopyElement } from "../element/newElement";
import { randomId } from "../random";
import { t } from "../i18n";
import { LIBRARY_DISABLED_TYPES } from "../constants";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionAddToLibrary = register({
name: "addToLibrary",
@@ -18,7 +18,7 @@ export const actionAddToLibrary = register({
for (const type of LIBRARY_DISABLED_TYPES) {
if (selectedElements.some((element) => element.type === type)) {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
errorMessage: t(`errors.libraryElementTypeError.${type}`),
@@ -42,7 +42,7 @@ export const actionAddToLibrary = register({
})
.then(() => {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
toast: { message: t("toast.addedToLibrary") },
@@ -51,7 +51,7 @@ export const actionAddToLibrary = register({
})
.catch((error) => {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
errorMessage: error.message,
diff --git a/packages/excalidraw/actions/actionAlign.tsx b/packages/excalidraw/actions/actionAlign.tsx
index 666fe0ab9..2acc12bfe 100644
--- a/packages/excalidraw/actions/actionAlign.tsx
+++ b/packages/excalidraw/actions/actionAlign.tsx
@@ -16,7 +16,7 @@ import { updateFrameMembershipOfSelectedElements } from "../frame";
import { t } from "../i18n";
import { KEYS } from "../keys";
import { isSomeElementSelected } from "../scene";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import type { AppClassProperties, AppState, UIAppState } from "../types";
import { arrayToMap, getShortcutKey } from "../utils";
import { register } from "./register";
@@ -72,7 +72,7 @@ export const actionAlignTop = register({
position: "start",
axis: "y",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
@@ -106,7 +106,7 @@ export const actionAlignBottom = register({
position: "end",
axis: "y",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
@@ -140,7 +140,7 @@ export const actionAlignLeft = register({
position: "start",
axis: "x",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
@@ -174,7 +174,7 @@ export const actionAlignRight = register({
position: "end",
axis: "x",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
@@ -208,7 +208,7 @@ export const actionAlignVerticallyCentered = register({
position: "center",
axis: "y",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => (
@@ -238,7 +238,7 @@ export const actionAlignHorizontallyCentered = register({
position: "center",
axis: "x",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => (
diff --git a/packages/excalidraw/actions/actionBoundText.tsx b/packages/excalidraw/actions/actionBoundText.tsx
index 76718d4c2..f47346036 100644
--- a/packages/excalidraw/actions/actionBoundText.tsx
+++ b/packages/excalidraw/actions/actionBoundText.tsx
@@ -34,7 +34,7 @@ import type { Mutable } from "../utility-types";
import { arrayToMap, getFontString } from "../utils";
import { register } from "./register";
import { syncMovedIndices } from "../fractionalIndex";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionUnbindText = register({
name: "unbindText",
@@ -86,7 +86,7 @@ export const actionUnbindText = register({
return {
elements,
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
});
@@ -163,7 +163,7 @@ export const actionBindText = register({
return {
elements: pushTextAboveContainer(elements, container, textElement),
appState: { ...appState, selectedElementIds: { [container.id]: true } },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
});
@@ -323,7 +323,7 @@ export const actionWrapTextInContainer = register({
...appState,
selectedElementIds: containerIds,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
});
diff --git a/packages/excalidraw/actions/actionCanvas.tsx b/packages/excalidraw/actions/actionCanvas.tsx
index e22a55fdf..3b3a12b98 100644
--- a/packages/excalidraw/actions/actionCanvas.tsx
+++ b/packages/excalidraw/actions/actionCanvas.tsx
@@ -37,7 +37,7 @@ import {
import { DEFAULT_CANVAS_BACKGROUND_PICKS } from "../colors";
import type { SceneBounds } from "../element/bounds";
import { setCursor } from "../cursor";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { clamp, roundToStep } from "../../math";
export const actionChangeViewBackgroundColor = register({
@@ -55,8 +55,8 @@ export const actionChangeViewBackgroundColor = register({
return {
appState: { ...appState, ...value },
storeAction: !!value.viewBackgroundColor
- ? SnapshotAction.CAPTURE
- : SnapshotAction.NONE,
+ ? StoreAction.CAPTURE
+ : StoreAction.NONE,
};
},
PanelComponent: ({ elements, appState, updateData, appProps }) => {
@@ -115,7 +115,7 @@ export const actionClearCanvas = register({
? { ...appState.activeTool, type: "selection" }
: appState.activeTool,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
});
@@ -140,7 +140,7 @@ export const actionZoomIn = register({
),
userToFollow: null,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ updateData, appState }) => (
@@ -181,7 +181,7 @@ export const actionZoomOut = register({
),
userToFollow: null,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ updateData, appState }) => (
@@ -222,7 +222,7 @@ export const actionResetZoom = register({
),
userToFollow: null,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ updateData, appState }) => (
@@ -341,7 +341,7 @@ export const zoomToFitBounds = ({
scrollY: centerScroll.scrollY,
zoom: { value: newZoomValue },
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
};
@@ -472,7 +472,7 @@ export const actionToggleTheme = register({
theme:
value || (appState.theme === THEME.LIGHT ? THEME.DARK : THEME.LIGHT),
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
keyTest: (event) => event.altKey && event.shiftKey && event.code === CODES.D,
@@ -510,7 +510,7 @@ export const actionToggleEraserTool = register({
activeEmbeddable: null,
activeTool,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) => event.key === KEYS.E,
@@ -549,7 +549,7 @@ export const actionToggleHandTool = register({
activeEmbeddable: null,
activeTool,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/actionClipboard.tsx b/packages/excalidraw/actions/actionClipboard.tsx
index 98361e0b7..c030b8150 100644
--- a/packages/excalidraw/actions/actionClipboard.tsx
+++ b/packages/excalidraw/actions/actionClipboard.tsx
@@ -14,7 +14,7 @@ import { getTextFromElements, isTextElement } from "../element";
import { t } from "../i18n";
import { isFirefox } from "../constants";
import { DuplicateIcon, cutIcon, pngIcon, svgIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionCopy = register({
name: "copy",
@@ -32,7 +32,7 @@ export const actionCopy = register({
await copyToClipboard(elementsToCopy, app.files, event);
} catch (error: any) {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
errorMessage: error.message,
@@ -41,7 +41,7 @@ export const actionCopy = register({
}
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
// don't supply a shortcut since we handle this conditionally via onCopy event
@@ -67,7 +67,7 @@ export const actionPaste = register({
if (isFirefox) {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
errorMessage: t("hints.firefox_clipboard_write"),
@@ -76,7 +76,7 @@ export const actionPaste = register({
}
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
errorMessage: t("errors.asyncPasteFailedOnRead"),
@@ -89,7 +89,7 @@ export const actionPaste = register({
} catch (error: any) {
console.error(error);
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
errorMessage: t("errors.asyncPasteFailedOnParse"),
@@ -98,7 +98,7 @@ export const actionPaste = register({
}
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
// don't supply a shortcut since we handle this conditionally via onCopy event
@@ -125,7 +125,7 @@ export const actionCopyAsSvg = register({
perform: async (elements, appState, _data, app) => {
if (!app.canvas) {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
@@ -167,7 +167,7 @@ export const actionCopyAsSvg = register({
}),
},
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
} catch (error: any) {
console.error(error);
@@ -175,7 +175,7 @@ export const actionCopyAsSvg = register({
appState: {
errorMessage: error.message,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
},
@@ -193,7 +193,7 @@ export const actionCopyAsPng = register({
perform: async (elements, appState, _data, app) => {
if (!app.canvas) {
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
const selectedElements = app.scene.getSelectedElements({
@@ -227,7 +227,7 @@ export const actionCopyAsPng = register({
}),
},
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
} catch (error: any) {
console.error(error);
@@ -236,7 +236,7 @@ export const actionCopyAsPng = register({
...appState,
errorMessage: error.message,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
},
@@ -263,7 +263,7 @@ export const copyText = register({
throw new Error(t("errors.copyToSystemClipboardFailed"));
}
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
predicate: (elements, appState, _, app) => {
diff --git a/packages/excalidraw/actions/actionCropEditor.tsx b/packages/excalidraw/actions/actionCropEditor.tsx
index 09cb20e0a..24b64783b 100644
--- a/packages/excalidraw/actions/actionCropEditor.tsx
+++ b/packages/excalidraw/actions/actionCropEditor.tsx
@@ -1,6 +1,6 @@
import { register } from "./register";
import { cropIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
import { isImageElement } from "../element/typeChecks";
@@ -25,7 +25,7 @@ export const actionToggleCropEditor = register({
isCropping: false,
croppingElementId: selectedElement.id,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
predicate: (elements, appState, _, app) => {
diff --git a/packages/excalidraw/actions/actionDeleteSelected.tsx b/packages/excalidraw/actions/actionDeleteSelected.tsx
index 21555029c..882c8b3f3 100644
--- a/packages/excalidraw/actions/actionDeleteSelected.tsx
+++ b/packages/excalidraw/actions/actionDeleteSelected.tsx
@@ -17,7 +17,7 @@ import {
} from "../element/typeChecks";
import { updateActiveTool } from "../utils";
import { TrashIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
const deleteSelectedElements = (
elements: readonly ExcalidrawElement[],
@@ -189,7 +189,7 @@ export const actionDeleteSelected = register({
...nextAppState,
editingLinearElement: null,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
}
@@ -221,7 +221,7 @@ export const actionDeleteSelected = register({
: [0],
},
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
}
let { elements: nextElements, appState: nextAppState } =
@@ -245,8 +245,8 @@ export const actionDeleteSelected = register({
getNonDeletedElements(elements),
appState,
)
- ? SnapshotAction.CAPTURE
- : SnapshotAction.NONE,
+ ? StoreAction.CAPTURE
+ : StoreAction.NONE,
};
},
keyTest: (event, appState, elements) =>
diff --git a/packages/excalidraw/actions/actionDistribute.tsx b/packages/excalidraw/actions/actionDistribute.tsx
index f64b8afd9..4b4166a7e 100644
--- a/packages/excalidraw/actions/actionDistribute.tsx
+++ b/packages/excalidraw/actions/actionDistribute.tsx
@@ -12,7 +12,7 @@ import { updateFrameMembershipOfSelectedElements } from "../frame";
import { t } from "../i18n";
import { CODES, KEYS } from "../keys";
import { isSomeElementSelected } from "../scene";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import type { AppClassProperties, AppState } from "../types";
import { arrayToMap, getShortcutKey } from "../utils";
import { register } from "./register";
@@ -60,7 +60,7 @@ export const distributeHorizontally = register({
space: "between",
axis: "x",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
@@ -91,7 +91,7 @@ export const distributeVertically = register({
space: "between",
axis: "y",
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/actionDuplicateSelection.tsx b/packages/excalidraw/actions/actionDuplicateSelection.tsx
index 85d435f13..d19bfa59d 100644
--- a/packages/excalidraw/actions/actionDuplicateSelection.tsx
+++ b/packages/excalidraw/actions/actionDuplicateSelection.tsx
@@ -32,7 +32,7 @@ import {
getSelectedElements,
} from "../scene/selection";
import { syncMovedIndices } from "../fractionalIndex";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionDuplicateSelection = register({
name: "duplicateSelection",
@@ -52,7 +52,7 @@ export const actionDuplicateSelection = register({
return {
elements,
appState: newAppState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
} catch {
return false;
@@ -61,7 +61,7 @@ export const actionDuplicateSelection = register({
return {
...duplicateElements(elements, appState),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.D,
diff --git a/packages/excalidraw/actions/actionElementLock.ts b/packages/excalidraw/actions/actionElementLock.ts
index 2b79af47c..5e5a91f5d 100644
--- a/packages/excalidraw/actions/actionElementLock.ts
+++ b/packages/excalidraw/actions/actionElementLock.ts
@@ -4,7 +4,7 @@ import { isFrameLikeElement } from "../element/typeChecks";
import type { ExcalidrawElement } from "../element/types";
import { KEYS } from "../keys";
import { getSelectedElements } from "../scene";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { arrayToMap } from "../utils";
import { register } from "./register";
@@ -67,7 +67,7 @@ export const actionToggleElementLock = register({
? null
: appState.selectedLinearElement,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event, appState, elements, app) => {
@@ -112,7 +112,7 @@ export const actionUnlockAllElements = register({
lockedElements.map((el) => [el.id, true]),
),
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
label: "labels.elementLock.unlockAll",
diff --git a/packages/excalidraw/actions/actionExport.tsx b/packages/excalidraw/actions/actionExport.tsx
index 07468d038..ddfde0c7b 100644
--- a/packages/excalidraw/actions/actionExport.tsx
+++ b/packages/excalidraw/actions/actionExport.tsx
@@ -19,7 +19,7 @@ import { nativeFileSystemSupported } from "../data/filesystem";
import type { Theme } from "../element/types";
import "../components/ToolIcon.scss";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionChangeProjectName = register({
name: "changeProjectName",
@@ -28,7 +28,7 @@ export const actionChangeProjectName = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, name: value },
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ appState, updateData, appProps, data, app }) => (
@@ -48,7 +48,7 @@ export const actionChangeExportScale = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportScale: value },
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ elements: allElements, appState, updateData }) => {
@@ -98,7 +98,7 @@ export const actionChangeExportBackground = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportBackground: value },
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ appState, updateData }) => (
@@ -118,7 +118,7 @@ export const actionChangeExportEmbedScene = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportEmbedScene: value },
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ appState, updateData }) => (
@@ -160,7 +160,7 @@ export const actionSaveToActiveFile = register({
: await saveAsJSON(elements, appState, app.files, app.getName());
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
fileHandle,
@@ -182,7 +182,7 @@ export const actionSaveToActiveFile = register({
} else {
console.warn(error);
}
- return { storeAction: SnapshotAction.NONE };
+ return { storeAction: StoreAction.NONE };
}
},
// CFDO: temporary
@@ -208,7 +208,7 @@ export const actionSaveFileToDisk = register({
app.getName(),
);
return {
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
appState: {
...appState,
openDialog: null,
@@ -222,7 +222,7 @@ export const actionSaveFileToDisk = register({
} else {
console.warn(error);
}
- return { storeAction: SnapshotAction.NONE };
+ return { storeAction: StoreAction.NONE };
}
},
keyTest: (event) =>
@@ -261,7 +261,7 @@ export const actionLoadScene = register({
elements: loadedElements,
appState: loadedAppState,
files,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
} catch (error: any) {
if (error?.name === "AbortError") {
@@ -272,7 +272,7 @@ export const actionLoadScene = register({
elements,
appState: { ...appState, errorMessage: error.message },
files: app.files,
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
},
@@ -286,7 +286,7 @@ export const actionExportWithDarkMode = register({
perform: (_elements, appState, value) => {
return {
appState: { ...appState, exportWithDarkMode: value },
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ appState, updateData }) => (
diff --git a/packages/excalidraw/actions/actionFinalize.tsx b/packages/excalidraw/actions/actionFinalize.tsx
index d2fbc4c8f..0637e304f 100644
--- a/packages/excalidraw/actions/actionFinalize.tsx
+++ b/packages/excalidraw/actions/actionFinalize.tsx
@@ -14,7 +14,7 @@ import {
import { isBindingElement, isLinearElement } from "../element/typeChecks";
import type { AppState } from "../types";
import { resetCursor } from "../cursor";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { pointFrom } from "../../math";
import { isPathALoop } from "../shapes";
@@ -52,7 +52,7 @@ export const actionFinalize = register({
cursorButton: "up",
editingLinearElement: null,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
}
}
@@ -199,7 +199,7 @@ export const actionFinalize = register({
pendingImageElementId: null,
},
// TODO: #7348 we should not capture everything, but if we don't, it leads to incosistencies -> revisit
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event, appState) =>
diff --git a/packages/excalidraw/actions/actionFlip.ts b/packages/excalidraw/actions/actionFlip.ts
index f9776c9a4..34acc01bf 100644
--- a/packages/excalidraw/actions/actionFlip.ts
+++ b/packages/excalidraw/actions/actionFlip.ts
@@ -18,7 +18,7 @@ import {
} from "../element/binding";
import { updateFrameMembershipOfSelectedElements } from "../frame";
import { flipHorizontal, flipVertical } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import {
isArrowElement,
isElbowArrow,
@@ -47,7 +47,7 @@ export const actionFlipHorizontal = register({
app,
),
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) => event.shiftKey && event.code === CODES.H,
@@ -72,7 +72,7 @@ export const actionFlipVertical = register({
app,
),
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/actionFrame.ts b/packages/excalidraw/actions/actionFrame.ts
index b55b5cab7..737ee2d1d 100644
--- a/packages/excalidraw/actions/actionFrame.ts
+++ b/packages/excalidraw/actions/actionFrame.ts
@@ -13,7 +13,7 @@ import { getSelectedElements } from "../scene";
import { newFrameElement } from "../element/newElement";
import { getElementsInGroup } from "../groups";
import { mutateElement } from "../element/mutateElement";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
const isSingleFrameSelected = (
appState: UIAppState,
@@ -49,14 +49,14 @@ export const actionSelectAllElementsInFrame = register({
return acc;
}, {} as Record),
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
}
return {
elements,
appState,
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
predicate: (elements, appState, _, app) =>
@@ -80,14 +80,14 @@ export const actionRemoveAllElementsFromFrame = register({
[selectedElement.id]: true,
},
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
}
return {
elements,
appState,
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
predicate: (elements, appState, _, app) =>
@@ -109,7 +109,7 @@ export const actionupdateFrameRendering = register({
enabled: !appState.frameRendering.enabled,
},
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState: AppState) => appState.frameRendering.enabled,
@@ -139,7 +139,7 @@ export const actionSetFrameAsActiveTool = register({
type: "frame",
}),
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/actionGroup.tsx b/packages/excalidraw/actions/actionGroup.tsx
index ae7aa1db3..f61986f82 100644
--- a/packages/excalidraw/actions/actionGroup.tsx
+++ b/packages/excalidraw/actions/actionGroup.tsx
@@ -34,7 +34,7 @@ import {
replaceAllElementsInFrame,
} from "../frame";
import { syncMovedIndices } from "../fractionalIndex";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
const allElementsInSameGroup = (elements: readonly ExcalidrawElement[]) => {
if (elements.length >= 2) {
@@ -84,7 +84,7 @@ export const actionGroup = register({
);
if (selectedElements.length < 2) {
// nothing to group
- return { appState, elements, storeAction: SnapshotAction.NONE };
+ return { appState, elements, storeAction: StoreAction.NONE };
}
// if everything is already grouped into 1 group, there is nothing to do
const selectedGroupIds = getSelectedGroupIds(appState);
@@ -104,7 +104,7 @@ export const actionGroup = register({
]);
if (combinedSet.size === elementIdsInGroup.size) {
// no incremental ids in the selected ids
- return { appState, elements, storeAction: SnapshotAction.NONE };
+ return { appState, elements, storeAction: StoreAction.NONE };
}
}
@@ -170,7 +170,7 @@ export const actionGroup = register({
),
},
elements: reorderedElements,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
predicate: (elements, appState, _, app) =>
@@ -200,7 +200,7 @@ export const actionUngroup = register({
const elementsMap = arrayToMap(elements);
if (groupIds.length === 0) {
- return { appState, elements, storeAction: SnapshotAction.NONE };
+ return { appState, elements, storeAction: StoreAction.NONE };
}
let nextElements = [...elements];
@@ -273,7 +273,7 @@ export const actionUngroup = register({
return {
appState: { ...appState, ...updateAppState },
elements: nextElements,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/actionHistory.tsx b/packages/excalidraw/actions/actionHistory.tsx
index 5b90a71a8..3b52f7e7d 100644
--- a/packages/excalidraw/actions/actionHistory.tsx
+++ b/packages/excalidraw/actions/actionHistory.tsx
@@ -9,7 +9,7 @@ import { KEYS, matchKey } from "../keys";
import { arrayToMap } from "../utils";
import { isWindows } from "../constants";
import type { SceneElementsMap } from "../element/types";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { useEmitter } from "../hooks/useEmitter";
const executeHistoryAction = (
@@ -29,7 +29,7 @@ const executeHistoryAction = (
const result = updater();
if (!result) {
- return { storeAction: SnapshotAction.NONE };
+ return { storeAction: StoreAction.NONE };
}
const [nextElementsMap, nextAppState] = result;
@@ -38,11 +38,11 @@ const executeHistoryAction = (
return {
appState: nextAppState,
elements: nextElements,
- storeAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
};
}
- return { storeAction: SnapshotAction.NONE };
+ return { storeAction: StoreAction.NONE };
};
type ActionCreator = (history: History) => Action;
diff --git a/packages/excalidraw/actions/actionLinearEditor.tsx b/packages/excalidraw/actions/actionLinearEditor.tsx
index ae3c96231..acde9b1e5 100644
--- a/packages/excalidraw/actions/actionLinearEditor.tsx
+++ b/packages/excalidraw/actions/actionLinearEditor.tsx
@@ -2,7 +2,7 @@ import { DEFAULT_CATEGORIES } from "../components/CommandPalette/CommandPalette"
import { LinearElementEditor } from "../element/linearElementEditor";
import { isElbowArrow, isLinearElement } from "../element/typeChecks";
import type { ExcalidrawLinearElement } from "../element/types";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { register } from "./register";
import { ToolButton } from "../components/ToolButton";
import { t } from "../i18n";
@@ -51,7 +51,7 @@ export const actionToggleLinearEditor = register({
...appState,
editingLinearElement,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ appState, updateData, app }) => {
diff --git a/packages/excalidraw/actions/actionLink.tsx b/packages/excalidraw/actions/actionLink.tsx
index 80dd3def2..ae6197486 100644
--- a/packages/excalidraw/actions/actionLink.tsx
+++ b/packages/excalidraw/actions/actionLink.tsx
@@ -5,7 +5,7 @@ import { isEmbeddableElement } from "../element/typeChecks";
import { t } from "../i18n";
import { KEYS } from "../keys";
import { getSelectedElements } from "../scene";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { getShortcutKey } from "../utils";
import { register } from "./register";
@@ -25,7 +25,7 @@ export const actionLink = register({
showHyperlinkPopup: "editor",
openMenu: null,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
trackEvent: { category: "hyperlink", action: "click" },
diff --git a/packages/excalidraw/actions/actionMenu.tsx b/packages/excalidraw/actions/actionMenu.tsx
index f3d4eb267..84a5d1be4 100644
--- a/packages/excalidraw/actions/actionMenu.tsx
+++ b/packages/excalidraw/actions/actionMenu.tsx
@@ -4,7 +4,7 @@ import { t } from "../i18n";
import { showSelectedShapeActions, getNonDeletedElements } from "../element";
import { register } from "./register";
import { KEYS } from "../keys";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionToggleCanvasMenu = register({
name: "toggleCanvasMenu",
@@ -15,7 +15,7 @@ export const actionToggleCanvasMenu = register({
...appState,
openMenu: appState.openMenu === "canvas" ? null : "canvas",
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
}),
PanelComponent: ({ appState, updateData }) => (
(
event.key === KEYS.QUESTION_MARK,
diff --git a/packages/excalidraw/actions/actionNavigate.tsx b/packages/excalidraw/actions/actionNavigate.tsx
index 141b3bc43..c577e975f 100644
--- a/packages/excalidraw/actions/actionNavigate.tsx
+++ b/packages/excalidraw/actions/actionNavigate.tsx
@@ -7,7 +7,7 @@ import {
microphoneMutedIcon,
} from "../components/icons";
import { t } from "../i18n";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import type { Collaborator } from "../types";
import { register } from "./register";
import clsx from "clsx";
@@ -28,7 +28,7 @@ export const actionGoToCollaborator = register({
...appState,
userToFollow: null,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
@@ -42,7 +42,7 @@ export const actionGoToCollaborator = register({
// Close mobile menu
openMenu: appState.openMenu === "canvas" ? null : appState.openMenu,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
PanelComponent: ({ updateData, data, appState }) => {
diff --git a/packages/excalidraw/actions/actionProperties.tsx b/packages/excalidraw/actions/actionProperties.tsx
index c6d83584e..5c4d7754f 100644
--- a/packages/excalidraw/actions/actionProperties.tsx
+++ b/packages/excalidraw/actions/actionProperties.tsx
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef, useState } from "react";
import type { AppClassProperties, AppState, Primitive } from "../types";
-import type { SnapshotActionType } from "../store";
+import type { StoreActionType } from "../store";
import {
DEFAULT_ELEMENT_BACKGROUND_COLOR_PALETTE,
DEFAULT_ELEMENT_BACKGROUND_PICKS,
@@ -109,7 +109,7 @@ import {
tupleToCoors,
} from "../utils";
import { register } from "./register";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { Fonts, getLineHeight } from "../fonts";
import {
bindLinearElement,
@@ -270,7 +270,7 @@ const changeFontSize = (
? [...newFontSizes][0]
: fallbackValue ?? appState.currentItemFontSize,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
};
@@ -301,8 +301,8 @@ export const actionChangeStrokeColor = register({
...value,
},
storeAction: !!value.currentItemStrokeColor
- ? SnapshotAction.CAPTURE
- : SnapshotAction.NONE,
+ ? StoreAction.CAPTURE
+ : StoreAction.NONE,
};
},
PanelComponent: ({ elements, appState, updateData, appProps }) => (
@@ -347,8 +347,8 @@ export const actionChangeBackgroundColor = register({
...value,
},
storeAction: !!value.currentItemBackgroundColor
- ? SnapshotAction.CAPTURE
- : SnapshotAction.NONE,
+ ? StoreAction.CAPTURE
+ : StoreAction.NONE,
};
},
PanelComponent: ({ elements, appState, updateData, appProps }) => (
@@ -392,7 +392,7 @@ export const actionChangeFillStyle = register({
}),
),
appState: { ...appState, currentItemFillStyle: value },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
@@ -465,7 +465,7 @@ export const actionChangeStrokeWidth = register({
}),
),
appState: { ...appState, currentItemStrokeWidth: value },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@@ -520,7 +520,7 @@ export const actionChangeSloppiness = register({
}),
),
appState: { ...appState, currentItemRoughness: value },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@@ -571,7 +571,7 @@ export const actionChangeStrokeStyle = register({
}),
),
appState: { ...appState, currentItemStrokeStyle: value },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@@ -626,7 +626,7 @@ export const actionChangeOpacity = register({
true,
),
appState: { ...appState, currentItemOpacity: value },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => (
@@ -814,22 +814,22 @@ export const actionChangeFontFamily = register({
...appState,
...nextAppState,
},
- storeAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
};
}
const { currentItemFontFamily, currentHoveredFontFamily } = value;
- let nexStoreAction: SnapshotActionType = SnapshotAction.NONE;
+ let nexStoreAction: StoreActionType = StoreAction.NONE;
let nextFontFamily: FontFamilyValues | undefined;
let skipOnHoverRender = false;
if (currentItemFontFamily) {
nextFontFamily = currentItemFontFamily;
- nexStoreAction = SnapshotAction.CAPTURE;
+ nexStoreAction = StoreAction.CAPTURE;
} else if (currentHoveredFontFamily) {
nextFontFamily = currentHoveredFontFamily;
- nexStoreAction = SnapshotAction.NONE;
+ nexStoreAction = StoreAction.NONE;
const selectedTextElements = getSelectedElements(elements, appState, {
includeBoundTextElement: true,
@@ -1187,7 +1187,7 @@ export const actionChangeTextAlign = register({
...appState,
currentItemTextAlign: value,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => {
@@ -1277,7 +1277,7 @@ export const actionChangeVerticalAlign = register({
appState: {
...appState,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData, app }) => {
@@ -1362,7 +1362,7 @@ export const actionChangeRoundness = register({
...appState,
currentItemRoundness: value,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
@@ -1521,7 +1521,7 @@ export const actionChangeArrowhead = register({
? "currentItemStartArrowhead"
: "currentItemEndArrowhead"]: value.type,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
@@ -1731,7 +1731,7 @@ export const actionChangeArrowType = register({
return {
elements: newElements,
appState: newState,
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
PanelComponent: ({ elements, appState, updateData }) => {
diff --git a/packages/excalidraw/actions/actionSelectAll.ts b/packages/excalidraw/actions/actionSelectAll.ts
index b66e61f58..f8f9b775f 100644
--- a/packages/excalidraw/actions/actionSelectAll.ts
+++ b/packages/excalidraw/actions/actionSelectAll.ts
@@ -6,7 +6,7 @@ import type { ExcalidrawElement } from "../element/types";
import { isLinearElement } from "../element/typeChecks";
import { LinearElementEditor } from "../element/linearElementEditor";
import { selectAllIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionSelectAll = register({
name: "selectAll",
@@ -50,7 +50,7 @@ export const actionSelectAll = register({
? new LinearElementEditor(elements[0])
: null,
},
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) => event[KEYS.CTRL_OR_CMD] && event.key === KEYS.A,
diff --git a/packages/excalidraw/actions/actionStyles.ts b/packages/excalidraw/actions/actionStyles.ts
index 97aef4eb5..1a17bf9de 100644
--- a/packages/excalidraw/actions/actionStyles.ts
+++ b/packages/excalidraw/actions/actionStyles.ts
@@ -23,7 +23,7 @@ import {
import { getSelectedElements } from "../scene";
import type { ExcalidrawTextElement } from "../element/types";
import { paintIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { getLineHeight } from "../fonts";
// `copiedStyles` is exported only for tests.
@@ -53,7 +53,7 @@ export const actionCopyStyles = register({
...appState,
toast: { message: t("toast.copyStyles") },
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
keyTest: (event) =>
@@ -70,7 +70,7 @@ export const actionPasteStyles = register({
const pastedElement = elementsCopied[0];
const boundTextElement = elementsCopied[1];
if (!isExcalidrawElement(pastedElement)) {
- return { elements, storeAction: SnapshotAction.NONE };
+ return { elements, storeAction: StoreAction.NONE };
}
const selectedElements = getSelectedElements(elements, appState, {
@@ -159,7 +159,7 @@ export const actionPasteStyles = register({
}
return element;
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/actionTextAutoResize.ts b/packages/excalidraw/actions/actionTextAutoResize.ts
index f1ec95df7..3093f3090 100644
--- a/packages/excalidraw/actions/actionTextAutoResize.ts
+++ b/packages/excalidraw/actions/actionTextAutoResize.ts
@@ -2,7 +2,7 @@ import { isTextElement } from "../element";
import { newElementWith } from "../element/mutateElement";
import { measureText } from "../element/textElement";
import { getSelectedElements } from "../scene";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import type { AppClassProperties } from "../types";
import { getFontString } from "../utils";
import { register } from "./register";
@@ -42,7 +42,7 @@ export const actionTextAutoResize = register({
}
return element;
}),
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
});
diff --git a/packages/excalidraw/actions/actionToggleGridMode.tsx b/packages/excalidraw/actions/actionToggleGridMode.tsx
index 954ceb828..3ab4c4ff9 100644
--- a/packages/excalidraw/actions/actionToggleGridMode.tsx
+++ b/packages/excalidraw/actions/actionToggleGridMode.tsx
@@ -2,7 +2,7 @@ import { CODES, KEYS } from "../keys";
import { register } from "./register";
import type { AppState } from "../types";
import { gridIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionToggleGridMode = register({
name: "gridMode",
@@ -21,7 +21,7 @@ export const actionToggleGridMode = register({
gridModeEnabled: !this.checked!(appState),
objectsSnapModeEnabled: false,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState: AppState) => appState.gridModeEnabled,
diff --git a/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx b/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx
index 35a195298..e27decf46 100644
--- a/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx
+++ b/packages/excalidraw/actions/actionToggleObjectsSnapMode.tsx
@@ -1,6 +1,6 @@
import { magnetIcon } from "../components/icons";
import { CODES, KEYS } from "../keys";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { register } from "./register";
export const actionToggleObjectsSnapMode = register({
@@ -19,7 +19,7 @@ export const actionToggleObjectsSnapMode = register({
objectsSnapModeEnabled: !this.checked!(appState),
gridModeEnabled: false,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState) => appState.objectsSnapModeEnabled,
diff --git a/packages/excalidraw/actions/actionToggleSearchMenu.ts b/packages/excalidraw/actions/actionToggleSearchMenu.ts
index 630b89fe2..02a58cd2b 100644
--- a/packages/excalidraw/actions/actionToggleSearchMenu.ts
+++ b/packages/excalidraw/actions/actionToggleSearchMenu.ts
@@ -2,7 +2,7 @@ import { KEYS } from "../keys";
import { register } from "./register";
import type { AppState } from "../types";
import { searchIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { CANVAS_SEARCH_TAB, CLASSES, DEFAULT_SIDEBAR } from "../constants";
export const actionToggleSearchMenu = register({
@@ -29,7 +29,7 @@ export const actionToggleSearchMenu = register({
if (searchInput?.matches(":focus")) {
return {
appState: { ...appState, openSidebar: null },
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
}
@@ -44,7 +44,7 @@ export const actionToggleSearchMenu = register({
openSidebar: { name: DEFAULT_SIDEBAR.name, tab: CANVAS_SEARCH_TAB },
openDialog: null,
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState: AppState) => appState.gridModeEnabled,
diff --git a/packages/excalidraw/actions/actionToggleStats.tsx b/packages/excalidraw/actions/actionToggleStats.tsx
index 6911dcdbd..45402e8ad 100644
--- a/packages/excalidraw/actions/actionToggleStats.tsx
+++ b/packages/excalidraw/actions/actionToggleStats.tsx
@@ -1,7 +1,7 @@
import { register } from "./register";
import { CODES, KEYS } from "../keys";
import { abacusIcon } from "../components/icons";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionToggleStats = register({
name: "stats",
@@ -17,7 +17,7 @@ export const actionToggleStats = register({
...appState,
stats: { ...appState.stats, open: !this.checked!(appState) },
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState) => appState.stats.open,
diff --git a/packages/excalidraw/actions/actionToggleViewMode.tsx b/packages/excalidraw/actions/actionToggleViewMode.tsx
index 1d2ebf366..87dbb94ea 100644
--- a/packages/excalidraw/actions/actionToggleViewMode.tsx
+++ b/packages/excalidraw/actions/actionToggleViewMode.tsx
@@ -1,6 +1,6 @@
import { eyeIcon } from "../components/icons";
import { CODES, KEYS } from "../keys";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { register } from "./register";
export const actionToggleViewMode = register({
@@ -19,7 +19,7 @@ export const actionToggleViewMode = register({
...appState,
viewModeEnabled: !this.checked!(appState),
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState) => appState.viewModeEnabled,
diff --git a/packages/excalidraw/actions/actionToggleZenMode.tsx b/packages/excalidraw/actions/actionToggleZenMode.tsx
index c1280b779..86261443f 100644
--- a/packages/excalidraw/actions/actionToggleZenMode.tsx
+++ b/packages/excalidraw/actions/actionToggleZenMode.tsx
@@ -1,6 +1,6 @@
import { coffeeIcon } from "../components/icons";
import { CODES, KEYS } from "../keys";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
import { register } from "./register";
export const actionToggleZenMode = register({
@@ -19,7 +19,7 @@ export const actionToggleZenMode = register({
...appState,
zenModeEnabled: !this.checked!(appState),
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
checked: (appState) => appState.zenModeEnabled,
diff --git a/packages/excalidraw/actions/actionZindex.tsx b/packages/excalidraw/actions/actionZindex.tsx
index 5fe1c859c..261b4ab78 100644
--- a/packages/excalidraw/actions/actionZindex.tsx
+++ b/packages/excalidraw/actions/actionZindex.tsx
@@ -15,7 +15,7 @@ import {
SendToBackIcon,
} from "../components/icons";
import { isDarwin } from "../constants";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
export const actionSendBackward = register({
name: "sendBackward",
@@ -27,7 +27,7 @@ export const actionSendBackward = register({
return {
elements: moveOneLeft(elements, appState),
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyPriority: 40,
@@ -57,7 +57,7 @@ export const actionBringForward = register({
return {
elements: moveOneRight(elements, appState),
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyPriority: 40,
@@ -87,7 +87,7 @@ export const actionSendToBack = register({
return {
elements: moveAllLeft(elements, appState),
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
@@ -125,7 +125,7 @@ export const actionBringToFront = register({
return {
elements: moveAllRight(elements, appState),
appState,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
};
},
keyTest: (event) =>
diff --git a/packages/excalidraw/actions/types.ts b/packages/excalidraw/actions/types.ts
index d694d5751..1627b2fca 100644
--- a/packages/excalidraw/actions/types.ts
+++ b/packages/excalidraw/actions/types.ts
@@ -10,7 +10,7 @@ import type {
BinaryFiles,
UIAppState,
} from "../types";
-import type { SnapshotActionType } from "../store";
+import type { StoreActionType } from "../store";
export type ActionSource =
| "ui"
@@ -25,7 +25,7 @@ export type ActionResult =
elements?: readonly ExcalidrawElement[] | null;
appState?: Partial | null;
files?: BinaryFiles | null;
- storeAction: SnapshotActionType;
+ storeAction: StoreActionType;
replaceFiles?: boolean;
}
| false;
diff --git a/packages/excalidraw/components/App.tsx b/packages/excalidraw/components/App.tsx
index eaf0eede9..da395822a 100644
--- a/packages/excalidraw/components/App.tsx
+++ b/packages/excalidraw/components/App.tsx
@@ -419,7 +419,7 @@ import { COLOR_PALETTE } from "../colors";
import { ElementCanvasButton } from "./MagicButton";
import { MagicIcon, copyIcon, fullscreenIcon } from "./icons";
import FollowMode from "./FollowMode/FollowMode";
-import { Store, SnapshotAction } from "../store";
+import { Store, StoreAction } from "../store";
import { AnimationFrameHandler } from "../animation-frame-handler";
import { AnimatedTrail } from "../animated-trail";
import { LaserTrails } from "../laser-trails";
@@ -2093,12 +2093,12 @@ class App extends React.Component {
if (shouldUpdateStrokeColor) {
this.syncActionResult({
appState: { ...this.state, currentItemStrokeColor: color },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
} else {
this.syncActionResult({
appState: { ...this.state, currentItemBackgroundColor: color },
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
}
} else {
@@ -2112,7 +2112,7 @@ class App extends React.Component {
}
return el;
}),
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
}
},
@@ -2334,7 +2334,7 @@ class App extends React.Component {
this.resetHistory();
this.syncActionResult({
...scene,
- storeAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// clear the shape and image cache so that any images in initialData
@@ -3869,45 +3869,48 @@ class App extends React.Component {
elements?: SceneData["elements"];
appState?: Pick | null;
collaborators?: SceneData["collaborators"];
- /** @default SnapshotAction.NONE */
- snapshotAction?: SceneData["snapshotAction"];
+ /** @default StoreAction.NONE */
+ storeAction?: SceneData["storeAction"];
}) => {
- // flush all pending updates (if any) most of the time it's no-op
+ // flush all pending updates (if any), most of the time it should be a no-op
flushSync(() => {});
// flush all incoming updates immediately, so that they couldn't be batched with other updates, having different `storeAction`
flushSync(() => {
- const nextElements = syncInvalidIndices(sceneData.elements ?? []);
+ const { elements, appState, collaborators, storeAction } = sceneData;
+ const nextElements = elements
+ ? syncInvalidIndices(elements)
+ : undefined;
- if (sceneData.snapshotAction) {
+ if (storeAction) {
const prevCommittedAppState = this.store.snapshot.appState;
const prevCommittedElements = this.store.snapshot.elements;
- const nextCommittedAppState = sceneData.appState
- ? Object.assign({}, prevCommittedAppState, sceneData.appState) // new instance, with partial appstate applied to previously captured one, including hidden prop inside `prevCommittedAppState`
+ const nextCommittedAppState = appState
+ ? Object.assign({}, prevCommittedAppState, appState) // new instance, with partial appstate applied to previously captured one, including hidden prop inside `prevCommittedAppState`
: prevCommittedAppState;
- const nextCommittedElements = sceneData.elements
+ const nextCommittedElements = elements
? this.store.filterUncomittedElements(
this.scene.getElementsMapIncludingDeleted(), // Only used to detect uncomitted local elements
- arrayToMap(nextElements), // We expect all (already reconciled) elements
+ arrayToMap(nextElements ?? []), // We expect all (already reconciled) elements
)
: prevCommittedElements;
- this.store.scheduleAction(sceneData.snapshotAction);
+ this.store.scheduleAction(storeAction);
this.store.commit(nextCommittedElements, nextCommittedAppState);
}
- if (sceneData.appState) {
- this.setState(sceneData.appState);
+ if (appState) {
+ this.setState(appState);
}
- if (sceneData.elements) {
+ if (nextElements) {
this.scene.replaceAllElements(nextElements);
}
- if (sceneData.collaborators) {
- this.setState({ collaborators: sceneData.collaborators });
+ if (collaborators) {
+ this.setState({ collaborators });
}
});
},
@@ -4571,7 +4574,7 @@ class App extends React.Component {
if (!event.altKey) {
if (this.flowChartNavigator.isExploring) {
this.flowChartNavigator.clear();
- this.syncActionResult({ storeAction: SnapshotAction.CAPTURE });
+ this.syncActionResult({ storeAction: StoreAction.CAPTURE });
}
}
@@ -4618,7 +4621,7 @@ class App extends React.Component {
}
this.flowChartCreator.clear();
- this.syncActionResult({ storeAction: SnapshotAction.CAPTURE });
+ this.syncActionResult({ storeAction: StoreAction.CAPTURE });
}
}
});
@@ -6347,10 +6350,10 @@ class App extends React.Component {
this.state,
),
},
- snapshotAction:
+ storeAction:
this.state.openDialog?.name === "elementLinkSelector"
- ? SnapshotAction.NONE
- : SnapshotAction.UPDATE,
+ ? StoreAction.NONE
+ : StoreAction.UPDATE,
});
return;
}
@@ -9002,7 +9005,7 @@ class App extends React.Component {
appState: {
newElement: null,
},
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
return;
@@ -9172,7 +9175,7 @@ class App extends React.Component {
elements: this.scene
.getElementsIncludingDeleted()
.filter((el) => el.id !== resizingElement.id),
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
}
@@ -10137,7 +10140,7 @@ class App extends React.Component {
isLoading: false,
},
replaceFiles: true,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
return;
} catch (error: any) {
@@ -10255,7 +10258,7 @@ class App extends React.Component {
// restore the fractional indices by mutating elements
syncInvalidIndices(elements.concat(ret.data.elements));
// update the store snapshot for old elements, otherwise we would end up with duplicated fractional indices on undo
- this.store.scheduleAction(SnapshotAction.UPDATE);
+ this.store.scheduleAction(StoreAction.UPDATE);
this.store.commit(arrayToMap(elements), this.state);
this.setState({ isLoading: true });
@@ -10266,7 +10269,7 @@ class App extends React.Component {
isLoading: false,
},
replaceFiles: true,
- storeAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
} else if (ret.type === MIME_TYPES.excalidrawlib) {
await this.library
diff --git a/packages/excalidraw/components/Stats/DragInput.tsx b/packages/excalidraw/components/Stats/DragInput.tsx
index 64ee3934e..77cc942cf 100644
--- a/packages/excalidraw/components/Stats/DragInput.tsx
+++ b/packages/excalidraw/components/Stats/DragInput.tsx
@@ -8,7 +8,7 @@ import { useApp } from "../App";
import { InlineIcon } from "../InlineIcon";
import type { StatsInputProperty } from "./utils";
import { SMALLEST_DELTA } from "./utils";
-import { SnapshotAction } from "../../store";
+import { StoreAction } from "../../store";
import type Scene from "../../scene/Scene";
import "./DragInput.scss";
@@ -132,7 +132,7 @@ const StatsDragInput = <
originalAppState: appState,
setInputValue: (value) => setInputValue(String(value)),
});
- app.syncActionResult({ storeAction: SnapshotAction.CAPTURE });
+ app.syncActionResult({ storeAction: StoreAction.CAPTURE });
}
};
@@ -276,7 +276,7 @@ const StatsDragInput = <
false,
);
- app.syncActionResult({ storeAction: SnapshotAction.CAPTURE });
+ app.syncActionResult({ storeAction: StoreAction.CAPTURE });
lastPointer = null;
accumulatedChange = 0;
diff --git a/packages/excalidraw/delta.ts b/packages/excalidraw/delta.ts
index d0773ae5a..980906d9e 100644
--- a/packages/excalidraw/delta.ts
+++ b/packages/excalidraw/delta.ts
@@ -1271,7 +1271,7 @@ export class ElementsDelta implements DeltaContainer {
});
}
- // CFDO II: this looks wrong
+ // CFDO: this looks wrong
if (isImageElement(element)) {
const _delta = delta as Delta>;
// we want to override `crop` only if modified so that we don't reset
diff --git a/packages/excalidraw/element/embeddable.ts b/packages/excalidraw/element/embeddable.ts
index c7bac2c64..0948d4b52 100644
--- a/packages/excalidraw/element/embeddable.ts
+++ b/packages/excalidraw/element/embeddable.ts
@@ -16,7 +16,7 @@ import type {
IframeData,
} from "./types";
import type { MarkRequired } from "../utility-types";
-import { SnapshotAction } from "../store";
+import { StoreAction } from "../store";
type IframeDataWithSandbox = MarkRequired;
@@ -344,7 +344,7 @@ export const actionSetEmbeddableAsActiveTool = register({
type: "embeddable",
}),
},
- storeAction: SnapshotAction.NONE,
+ storeAction: StoreAction.NONE,
};
},
});
diff --git a/packages/excalidraw/history.ts b/packages/excalidraw/history.ts
index 68523c0d3..bc204d574 100644
--- a/packages/excalidraw/history.ts
+++ b/packages/excalidraw/history.ts
@@ -106,6 +106,7 @@ export class History {
[nextElements, nextAppState, containsVisibleChange] =
this.store.applyDeltaTo(historyEntry, nextElements, nextAppState, {
triggerIncrement: true,
+ updateSnapshot: true,
});
prevSnapshot = this.store.snapshot;
diff --git a/packages/excalidraw/index.tsx b/packages/excalidraw/index.tsx
index 461f70f36..7014fab26 100644
--- a/packages/excalidraw/index.tsx
+++ b/packages/excalidraw/index.tsx
@@ -259,7 +259,7 @@ export {
bumpVersion,
} from "./element/mutateElement";
-export { SnapshotAction } from "./store";
+export { StoreAction } from "./store";
export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library";
diff --git a/packages/excalidraw/scene/Scene.ts b/packages/excalidraw/scene/Scene.ts
index 99bb9e1e4..f51a006de 100644
--- a/packages/excalidraw/scene/Scene.ts
+++ b/packages/excalidraw/scene/Scene.ts
@@ -296,6 +296,7 @@ class Scene {
validateIndicesThrottled(_nextElements);
+ // CFDO: if technically this leads to modifying the indices, it should update the snapshot immediately (as it shall be an non-undoable change)
this.elements = syncInvalidIndices(_nextElements);
this.elementsMap.clear();
this.elements.forEach((element) => {
diff --git a/packages/excalidraw/store.ts b/packages/excalidraw/store.ts
index 7e1c3b69e..f0bc82d54 100644
--- a/packages/excalidraw/store.ts
+++ b/packages/excalidraw/store.ts
@@ -8,11 +8,13 @@ import { deepCopyElement } from "./element/newElement";
import type { AppState, ObservedAppState } from "./types";
import type { DTO, ValueOf } from "./utility-types";
import type {
+ ExcalidrawElement,
OrderedExcalidrawElement,
SceneElementsMap,
} from "./element/types";
+import { arrayToMap, assertNever } from "./utils";
import { hashElementsVersion } from "./element";
-import { assertNever } from "./utils";
+import { syncMovedIndices } from "./fractionalIndex";
// hidden non-enumerable property for runtime checks
const hiddenObservedAppStateProp = "__observedAppState";
@@ -43,7 +45,7 @@ const isObservedAppState = (
!!Reflect.get(appState, hiddenObservedAppStateProp);
// CFDO: consider adding a "remote" action, which should perform update but never be emitted (so that it we don't have to filter it when pushing it into sync api)
-export const SnapshotAction = {
+export const StoreAction = {
/**
* Immediately undoable.
*
@@ -68,7 +70,7 @@ export const SnapshotAction = {
* Use for updates which should not be captured as deltas immediately, such as
* exceptions which are part of some async multi-step proces.
*
- * These updates will be captured with the next `SnapshotAction.CAPTURE`,
+ * These updates will be captured with the next `StoreAction.CAPTURE`,
* triggered either by the next `updateScene` or internally by the editor.
*
* These updates will _eventually_ make it to the local undo / redo stacks.
@@ -78,7 +80,7 @@ export const SnapshotAction = {
NONE: "NONE",
} as const;
-export type SnapshotActionType = ValueOf;
+export type StoreActionType = ValueOf;
/**
* Store which captures the observed changes and emits them as `StoreIncrement` events.
@@ -98,9 +100,9 @@ export class Store {
this._snapshot = snapshot;
}
- private scheduledActions: Set = new Set();
+ private scheduledActions: Set = new Set();
- public scheduleAction(action: SnapshotActionType) {
+ public scheduleAction(action: StoreActionType) {
this.scheduledActions.add(action);
this.satisfiesScheduledActionsInvariant();
}
@@ -110,26 +112,27 @@ export class Store {
*/
// TODO: Suspicious that this is called so many places. Seems error-prone.
public scheduleCapture() {
- this.scheduleAction(SnapshotAction.CAPTURE);
+ this.scheduleAction(StoreAction.CAPTURE);
}
private get scheduledAction() {
// Capture has a precedence over update, since it also performs snapshot update
- if (this.scheduledActions.has(SnapshotAction.CAPTURE)) {
- return SnapshotAction.CAPTURE;
+ if (this.scheduledActions.has(StoreAction.CAPTURE)) {
+ return StoreAction.CAPTURE;
}
// Update has a precedence over none, since it also emits an (ephemeral) increment
- if (this.scheduledActions.has(SnapshotAction.UPDATE)) {
- return SnapshotAction.UPDATE;
+ if (this.scheduledActions.has(StoreAction.UPDATE)) {
+ return StoreAction.UPDATE;
}
+ // CFDO: maybe it should be explicitly set so that we don't clone on every single component update
// Emit ephemeral increment, don't update the snapshot
- return SnapshotAction.NONE;
+ return StoreAction.NONE;
}
/**
- * Performs the incoming `SnapshotAction` and emits the corresponding `StoreIncrement`.
+ * Performs the incoming `StoreAction` and emits the corresponding `StoreIncrement`.
* Emits `DurableStoreIncrement` when action is "capture", emits `EphemeralStoreIncrement` otherwise.
*
* @emits StoreIncrement
@@ -142,13 +145,14 @@ export class Store {
const { scheduledAction } = this;
switch (scheduledAction) {
- case SnapshotAction.CAPTURE:
+ case StoreAction.CAPTURE:
this.snapshot = this.captureDurableIncrement(elements, appState);
break;
- case SnapshotAction.UPDATE:
+ case StoreAction.UPDATE:
this.snapshot = this.emitEphemeralIncrement(elements);
break;
- case SnapshotAction.NONE:
+ case StoreAction.NONE:
+ // ÇFDO: consider perf. optimisation without creating a snapshot if it is not updated in the end, it shall not be needed (more complex though)
this.emitEphemeralIncrement(elements);
return;
default:
@@ -171,7 +175,9 @@ export class Store {
appState: AppState | ObservedAppState | undefined,
) {
const prevSnapshot = this.snapshot;
- const nextSnapshot = this.snapshot.maybeClone(elements, appState);
+ const nextSnapshot = this.snapshot.maybeClone(elements, appState, {
+ shouldIgnoreCache: true,
+ });
// Optimisation, don't continue if nothing has changed
if (prevSnapshot === nextSnapshot) {
@@ -229,14 +235,16 @@ export class Store {
* This is necessary in updates in which we receive reconciled elements, already containing elements which were not yet captured by the local store (i.e. collab).
*/
public filterUncomittedElements(
- prevElements: Map,
- nextElements: Map,
- ) {
+ prevElements: Map,
+ nextElements: Map,
+ ): Map {
+ const movedElements = new Map();
+
for (const [id, prevElement] of prevElements.entries()) {
const nextElement = nextElements.get(id);
if (!nextElement) {
- // Nothing to care about here, elements were forcefully deleted
+ // Nothing to care about here, element was forcefully deleted
continue;
}
@@ -249,10 +257,18 @@ export class Store {
} else if (elementSnapshot.version < prevElement.version) {
// Element was already commited, but the snapshot version is lower than current local version
nextElements.set(id, elementSnapshot);
+ // Mark the element as potentially moved, as it could have
+ movedElements.set(id, elementSnapshot);
}
}
- return nextElements;
+ // Make sure to sync only potentially invalid indices for all elements restored from the snapshot
+ const syncedElements = syncMovedIndices(
+ Array.from(nextElements.values()),
+ movedElements,
+ );
+
+ return arrayToMap(syncedElements);
}
/**
@@ -266,8 +282,10 @@ export class Store {
appState: AppState,
options: {
triggerIncrement: boolean;
+ updateSnapshot: boolean;
} = {
triggerIncrement: false,
+ updateSnapshot: false,
},
): [SceneElementsMap, AppState, boolean] {
const [nextElements, elementsContainVisibleChange] = delta.elements.applyTo(
@@ -282,7 +300,9 @@ export class Store {
elementsContainVisibleChange || appStateContainsVisibleChange;
const prevSnapshot = this.snapshot;
- const nextSnapshot = this.snapshot.maybeClone(nextElements, nextAppState);
+ const nextSnapshot = this.snapshot.maybeClone(nextElements, nextAppState, {
+ shouldIgnoreCache: true,
+ });
if (options.triggerIncrement) {
const change = StoreChange.create(prevSnapshot, nextSnapshot);
@@ -290,7 +310,12 @@ export class Store {
this.onStoreIncrementEmitter.trigger(increment);
}
- this.snapshot = nextSnapshot;
+ // CFDO: maybe I should not update the snapshot here so that it always syncs ephemeral change after durable change,
+ // so that clients exchange the latest element versions between each other,
+ // meaning if it will be ignored on other clients, other clients would initiate a relay with current version instead of doing nothing
+ if (options.updateSnapshot) {
+ this.snapshot = nextSnapshot;
+ }
return [nextElements, nextAppState, appliedVisibleChanges];
}
@@ -307,7 +332,7 @@ export class Store {
if (
!(
this.scheduledActions.size >= 0 &&
- this.scheduledActions.size <= Object.keys(SnapshotAction).length
+ this.scheduledActions.size <= Object.keys(StoreAction).length
)
) {
const message = `There can be at most three store actions scheduled at the same time, but there are "${this.scheduledActions.size}".`;
@@ -441,9 +466,7 @@ export class StoreDelta {
* Inverse store delta, creates new instance of `StoreDelta`.
*/
public static inverse(delta: StoreDelta): StoreDelta {
- return this.create(delta.elements.inverse(), delta.appState.inverse(), {
- id: delta.id,
- });
+ return this.create(delta.elements.inverse(), delta.appState.inverse());
}
/**
@@ -538,8 +561,16 @@ export class StoreSnapshot {
public maybeClone(
elements: Map | undefined,
appState: AppState | ObservedAppState | undefined,
+ options: {
+ shouldIgnoreCache: boolean;
+ } = {
+ shouldIgnoreCache: false,
+ },
) {
- const nextElementsSnapshot = this.maybeCreateElementsSnapshot(elements);
+ const nextElementsSnapshot = this.maybeCreateElementsSnapshot(
+ elements,
+ options,
+ );
const nextAppStateSnapshot = this.maybeCreateAppStateSnapshot(appState);
let didElementsChange = false;
@@ -597,12 +628,17 @@ export class StoreSnapshot {
private maybeCreateElementsSnapshot(
elements: Map | undefined,
+ options: {
+ shouldIgnoreCache: boolean;
+ } = {
+ shouldIgnoreCache: false,
+ },
) {
if (!elements) {
return this.elements;
}
- const changedElements = this.detectChangedElements(elements);
+ const changedElements = this.detectChangedElements(elements, options);
if (!changedElements?.size) {
return this.elements;
@@ -619,6 +655,11 @@ export class StoreSnapshot {
*/
private detectChangedElements(
nextElements: Map,
+ options: {
+ shouldIgnoreCache: boolean;
+ } = {
+ shouldIgnoreCache: false,
+ },
) {
if (this.elements === nextElements) {
return;
@@ -653,10 +694,18 @@ export class StoreSnapshot {
return;
}
+ // if we wouldn't ignore a cache, durable increment would be skipped
+ // in case there was an ephemeral increment emitter just before
+ // with the same changed elements
+ if (options.shouldIgnoreCache) {
+ return changedElements;
+ }
+
// due to snapshot containing only durable changes,
// we might have already processed these elements in a previous run,
// hence additionally check whether the hash of the elements has changed
// since if it didn't, we don't need to process them again
+ // otherwise we would have ephemeral increments even for component updates unrelated to elements
const changedElementsHash = hashElementsVersion(
Array.from(changedElements.values()),
);
diff --git a/packages/excalidraw/sync/client.ts b/packages/excalidraw/sync/client.ts
index 68de1d4a6..2d393dd97 100644
--- a/packages/excalidraw/sync/client.ts
+++ b/packages/excalidraw/sync/client.ts
@@ -10,10 +10,10 @@ import {
type MetadataRepository,
type DeltasRepository,
} from "./queue";
-import { SnapshotAction, StoreDelta } from "../store";
+import { StoreAction, StoreDelta } from "../store";
import type { StoreChange } from "../store";
import type { ExcalidrawImperativeAPI } from "../types";
-import type { SceneElementsMap } from "../element/types";
+import type { ExcalidrawElement, SceneElementsMap } from "../element/types";
import type { CLIENT_MESSAGE_RAW, SERVER_DELTA, CHANGE } from "./protocol";
import { debounce } from "../utils";
import { randomId } from "../random";
@@ -38,7 +38,7 @@ class SocketClient {
private isOffline = true;
private socket: ReconnectingWebSocket | null = null;
- private get isDisconnected() {
+ public get isDisconnected() {
return !this.socket;
}
@@ -204,6 +204,11 @@ export class SyncClient {
private readonly metadata: MetadataRepository;
private readonly client: SocketClient;
+ private relayedElementsVersionsCache = new Map<
+ string,
+ ExcalidrawElement["version"]
+ >();
+
// #region ACKNOWLEDGED DELTAS & METADATA
// CFDO: shouldn't be stateful, only request / response
private readonly acknowledgedDeltasMap: Map =
@@ -264,11 +269,12 @@ export class SyncClient {
// #region PUBLIC API METHODS
public connect() {
- return this.client.connect();
+ this.client.connect();
}
public disconnect() {
- return this.client.disconnect();
+ this.client.disconnect();
+ this.relayedElementsVersionsCache.clear();
}
public pull(sinceVersion?: number): void {
@@ -298,6 +304,32 @@ export class SyncClient {
// CFDO: should be throttled! 60 fps for live scenes, 10s or so for single player
public relay(change: StoreChange): void {
+ if (this.client.isDisconnected) {
+ // don't reconnect if we're explicitly disconnected
+ // otherwise versioning slider would trigger sync on every slider step
+ return;
+ }
+
+ let shouldRelay = false;
+
+ for (const [id, element] of Object.entries(change.elements)) {
+ const cachedElementVersion = this.relayedElementsVersionsCache.get(id);
+
+ if (!cachedElementVersion || cachedElementVersion < element.version) {
+ this.relayedElementsVersionsCache.set(id, element.version);
+
+ if (!shouldRelay) {
+ // it's enough that a single element is not cached or is outdated in cache
+ // to relay the whole change, otherwise we skip the relay as we've already received this change
+ shouldRelay = true;
+ }
+ }
+ }
+
+ if (!shouldRelay) {
+ return;
+ }
+
this.client.send({
type: "relay",
payload: { ...change },
@@ -357,12 +389,13 @@ export class SyncClient {
existingElement.version < relayedElement.version // updated element
) {
nextElements.set(id, relayedElement);
+ this.relayedElementsVersionsCache.set(id, relayedElement.version);
}
}
this.api.updateScene({
elements: Array.from(nextElements.values()),
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
} catch (e) {
console.error("Failed to apply relayed change:", e);
@@ -426,16 +459,22 @@ export class SyncClient {
delta,
nextElements,
appState,
+ {
+ triggerIncrement: false,
+ updateSnapshot: true,
+ },
);
prevSnapshot = this.api.store.snapshot;
}
- // CFDO: I still need to filter out uncomitted elements
- // I still need to update snapshot with the new elements
+ // CFDO: might need to restore first due to potentially stale delta versions
this.api.updateScene({
elements: Array.from(nextElements.values()),
- snapshotAction: SnapshotAction.NONE,
+ // even though the snapshot should be up-to-date already,
+ // still some more updates might be triggered,
+ // i.e. as a result from syncing invalid indices
+ storeAction: StoreAction.UPDATE,
});
this.lastAcknowledgedVersion = nextAcknowledgedVersion;
diff --git a/packages/excalidraw/tests/history.test.tsx b/packages/excalidraw/tests/history.test.tsx
index ccdfb25ec..dca7fe8b3 100644
--- a/packages/excalidraw/tests/history.test.tsx
+++ b/packages/excalidraw/tests/history.test.tsx
@@ -42,7 +42,7 @@ import {
import { vi } from "vitest";
import { queryByText } from "@testing-library/react";
import { AppStateDelta, ElementsDelta } from "../delta";
-import { SnapshotAction, StoreDelta } from "../store";
+import { StoreAction, StoreDelta } from "../store";
import type { LocalPoint, Radians } from "../../math";
import { pointFrom } from "../../math";
import type { AppState } from "../types.js";
@@ -216,7 +216,7 @@ describe("history", () => {
API.updateScene({
elements: [rect1, rect2],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
expect(API.getUndoStack().length).toBe(1);
@@ -228,7 +228,7 @@ describe("history", () => {
API.updateScene({
elements: [rect1, rect2],
- snapshotAction: SnapshotAction.CAPTURE, // even though the flag is on, same elements are passed, nothing to commit
+ storeAction: StoreAction.CAPTURE, // even though the flag is on, same elements are passed, nothing to commit
});
expect(API.getUndoStack().length).toBe(1);
expect(API.getRedoStack().length).toBe(0);
@@ -596,7 +596,7 @@ describe("history", () => {
appState: {
name: "New name",
},
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
expect(API.getUndoStack().length).toBe(1);
@@ -607,7 +607,7 @@ describe("history", () => {
appState: {
viewBackgroundColor: "#000",
},
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(0);
@@ -620,7 +620,7 @@ describe("history", () => {
name: "New name",
viewBackgroundColor: "#000",
},
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
expect(API.getUndoStack().length).toBe(2);
expect(API.getRedoStack().length).toBe(0);
@@ -1327,7 +1327,7 @@ describe("history", () => {
API.updateScene({
elements: [rect1, text, rect2],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// bind text1 to rect1
@@ -1899,7 +1899,7 @@ describe("history", () => {
strokeColor: blue,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -1937,7 +1937,7 @@ describe("history", () => {
strokeColor: yellow,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -1985,7 +1985,7 @@ describe("history", () => {
backgroundColor: yellow,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// At this point our entry gets updated from `red` -> `blue` into `red` -> `yellow`
@@ -2001,7 +2001,7 @@ describe("history", () => {
backgroundColor: violet,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// At this point our (inversed) entry gets updated from `red` -> `yellow` into `violet` -> `yellow`
@@ -2046,7 +2046,7 @@ describe("history", () => {
API.updateScene({
elements: [rect, diamond],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Connect the arrow
@@ -2095,7 +2095,7 @@ describe("history", () => {
} as FixedPointBinding,
},
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -2110,7 +2110,7 @@ describe("history", () => {
}
: el,
),
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2134,7 +2134,7 @@ describe("history", () => {
// Initialize scene
API.updateScene({
elements: [rect1, rect2],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
@@ -2143,7 +2143,7 @@ describe("history", () => {
newElementWith(h.elements[0], { groupIds: ["A"] }),
newElementWith(h.elements[1], { groupIds: ["A"] }),
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
const rect3 = API.createElement({ type: "rectangle", groupIds: ["B"] });
@@ -2157,7 +2157,7 @@ describe("history", () => {
rect3,
rect4,
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2203,7 +2203,7 @@ describe("history", () => {
] as LocalPoint[],
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo(); // undo `actionFinalize`
@@ -2298,7 +2298,7 @@ describe("history", () => {
isDeleted: false, // undeletion might happen due to concurrency between clients
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
expect(API.getSelectedElements()).toEqual([]);
@@ -2375,7 +2375,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
expect(h.elements).toEqual([
@@ -2437,7 +2437,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2513,7 +2513,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2552,7 +2552,7 @@ describe("history", () => {
isDeleted: false,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.redo();
@@ -2598,7 +2598,7 @@ describe("history", () => {
// Simulate remote update
API.updateScene({
elements: [rect1, rect2],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.withModifierKeys({ ctrl: true }, () => {
@@ -2608,7 +2608,7 @@ describe("history", () => {
// Simulate remote update
API.updateScene({
elements: [h.elements[0], h.elements[1], rect3, rect4],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.withModifierKeys({ ctrl: true }, () => {
@@ -2629,7 +2629,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2654,7 +2654,7 @@ describe("history", () => {
isDeleted: false,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.redo();
@@ -2665,7 +2665,7 @@ describe("history", () => {
// Simulate remote update
API.updateScene({
elements: [h.elements[0], h.elements[1], rect3, rect4],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.redo();
@@ -2711,7 +2711,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2732,7 +2732,7 @@ describe("history", () => {
}),
h.elements[1],
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2775,7 +2775,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2818,7 +2818,7 @@ describe("history", () => {
h.elements[0],
h.elements[1],
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
expect(API.getUndoStack().length).toBe(2);
@@ -2857,7 +2857,7 @@ describe("history", () => {
h.elements[0],
h.elements[1],
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
expect(API.getUndoStack().length).toBe(2);
@@ -2908,7 +2908,7 @@ describe("history", () => {
h.elements[0], // rect2
h.elements[1], // rect1
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2938,7 +2938,7 @@ describe("history", () => {
h.elements[0], // rect3
h.elements[2], // rect1
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -2968,7 +2968,7 @@ describe("history", () => {
// Simulate remote update
API.updateScene({
elements: [...h.elements, rect],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
mouse.moveTo(60, 60);
@@ -3020,7 +3020,7 @@ describe("history", () => {
// // Simulate remote update
API.updateScene({
elements: [...h.elements, rect3],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
mouse.moveTo(100, 100);
@@ -3110,7 +3110,7 @@ describe("history", () => {
// Simulate remote update
API.updateScene({
elements: [...h.elements, rect3],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
mouse.moveTo(100, 100);
@@ -3287,7 +3287,7 @@ describe("history", () => {
// Initialize the scene
API.updateScene({
elements: [container, text],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
@@ -3300,7 +3300,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -3331,7 +3331,7 @@ describe("history", () => {
x: h.elements[1].x + 10,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3374,7 +3374,7 @@ describe("history", () => {
// Initialize the scene
API.updateScene({
elements: [container, text],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
@@ -3387,7 +3387,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -3421,7 +3421,7 @@ describe("history", () => {
remoteText,
h.elements[1],
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3477,7 +3477,7 @@ describe("history", () => {
// Initialize the scene
API.updateScene({
elements: [container, text],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
@@ -3490,7 +3490,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -3527,7 +3527,7 @@ describe("history", () => {
containerId: remoteContainer.id,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3585,7 +3585,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [container],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -3596,7 +3596,7 @@ describe("history", () => {
}),
newElementWith(text, { containerId: container.id }),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3646,7 +3646,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [text],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -3657,7 +3657,7 @@ describe("history", () => {
}),
newElementWith(text, { containerId: container.id }),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3706,7 +3706,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [container],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -3719,7 +3719,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -3756,7 +3756,7 @@ describe("history", () => {
// rebinding the container with a new text element!
remoteText,
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3813,7 +3813,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [text],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -3826,7 +3826,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.undo();
@@ -3863,7 +3863,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3919,7 +3919,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [container],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -3933,7 +3933,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -3976,7 +3976,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [text],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -3990,7 +3990,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -4033,7 +4033,7 @@ describe("history", () => {
// Initialize the scene
API.updateScene({
elements: [container],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
@@ -4045,7 +4045,7 @@ describe("history", () => {
angle: 90 as Radians,
}),
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -4058,7 +4058,7 @@ describe("history", () => {
}),
newElementWith(text, { containerId: container.id }),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
expect(h.elements).toEqual([
@@ -4151,7 +4151,7 @@ describe("history", () => {
// Initialize the scene
API.updateScene({
elements: [text],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
@@ -4163,7 +4163,7 @@ describe("history", () => {
angle: 90 as Radians,
}),
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -4178,7 +4178,7 @@ describe("history", () => {
containerId: container.id,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
expect(API.getUndoStack().length).toBe(0);
@@ -4269,7 +4269,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [rect1, rect2],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
mouse.reset();
@@ -4358,7 +4358,7 @@ describe("history", () => {
x: h.elements[1].x + 50,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -4502,7 +4502,7 @@ describe("history", () => {
}),
remoteContainer,
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -4609,7 +4609,7 @@ describe("history", () => {
boundElements: [{ id: arrow.id, type: "arrow" }],
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -4686,7 +4686,7 @@ describe("history", () => {
// Simulate local update
API.updateScene({
elements: [arrow],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate remote update
@@ -4713,7 +4713,7 @@ describe("history", () => {
boundElements: [{ id: arrow.id, type: "arrow" }],
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
runTwice(() => {
@@ -4845,7 +4845,7 @@ describe("history", () => {
newElementWith(h.elements[1], { x: 500, y: -500 }),
h.elements[2],
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.redo();
@@ -4917,13 +4917,13 @@ describe("history", () => {
// Initialize the scene
API.updateScene({
elements: [frame],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
// Simulate local update
API.updateScene({
elements: [rect, h.elements[0]],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
// Simulate local update
@@ -4934,7 +4934,7 @@ describe("history", () => {
}),
h.elements[1],
],
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
Keyboard.undo();
@@ -4978,7 +4978,7 @@ describe("history", () => {
isDeleted: true,
}),
],
- snapshotAction: SnapshotAction.UPDATE,
+ storeAction: StoreAction.UPDATE,
});
Keyboard.redo();
diff --git a/packages/excalidraw/tests/packages/events.test.tsx b/packages/excalidraw/tests/packages/events.test.tsx
index 2eb0e0489..34a1cc062 100644
--- a/packages/excalidraw/tests/packages/events.test.tsx
+++ b/packages/excalidraw/tests/packages/events.test.tsx
@@ -1,6 +1,6 @@
import React from "react";
import { vi } from "vitest";
-import { Excalidraw, SnapshotAction } from "../../index";
+import { Excalidraw, StoreAction } from "../../index";
import type { ExcalidrawImperativeAPI } from "../../types";
import { resolvablePromise } from "../../utils";
import { render } from "../test-utils";
@@ -31,7 +31,7 @@ describe("event callbacks", () => {
excalidrawAPI.onChange(onChange);
API.updateScene({
appState: { viewBackgroundColor: "red" },
- snapshotAction: SnapshotAction.CAPTURE,
+ storeAction: StoreAction.CAPTURE,
});
expect(onChange).toHaveBeenCalledWith(
// elements
diff --git a/packages/excalidraw/types.ts b/packages/excalidraw/types.ts
index 89261a2e2..e12799924 100644
--- a/packages/excalidraw/types.ts
+++ b/packages/excalidraw/types.ts
@@ -43,7 +43,7 @@ import type { Merge, MaybePromise, ValueOf, MakeBrand } from "./utility-types";
import type {
DurableStoreIncrement,
EphemeralStoreIncrement,
- SnapshotActionType,
+ StoreActionType as StoreActionType,
} from "./store";
export type SocketId = string & { _brand: "SocketId" };
@@ -578,7 +578,7 @@ export type SceneData = {
elements?: ImportedDataState["elements"];
appState?: ImportedDataState["appState"];
collaborators?: Map;
- snapshotAction?: SnapshotActionType;
+ storeAction?: StoreActionType;
};
export enum UserIdleState {