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 {