mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
Cache received changes, ignore snapshot cache for durable changes, revert StoreAction, history fix, indices fix
This commit is contained in:
parent
310a9ae4e0
commit
7e0f5b6369
50 changed files with 437 additions and 338 deletions
|
@ -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,
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 }) => (
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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<ExcalidrawElement["id"], true>),
|
||||
},
|
||||
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) =>
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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 }) => (
|
||||
<ToolButton
|
||||
|
@ -37,7 +37,7 @@ export const actionToggleEditMenu = register({
|
|||
...appState,
|
||||
openMenu: appState.openMenu === "shape" ? null : "shape",
|
||||
},
|
||||
storeAction: SnapshotAction.NONE,
|
||||
storeAction: StoreAction.NONE,
|
||||
}),
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<ToolButton
|
||||
|
@ -74,7 +74,7 @@ export const actionShortcuts = register({
|
|||
name: "help",
|
||||
},
|
||||
},
|
||||
storeAction: SnapshotAction.NONE,
|
||||
storeAction: StoreAction.NONE,
|
||||
};
|
||||
},
|
||||
keyTest: (event) => event.key === KEYS.QUESTION_MARK,
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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) =>
|
||||
|
|
|
@ -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<AppState> | null;
|
||||
files?: BinaryFiles | null;
|
||||
storeAction: SnapshotActionType;
|
||||
storeAction: StoreActionType;
|
||||
replaceFiles?: boolean;
|
||||
}
|
||||
| false;
|
||||
|
|
|
@ -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<AppProps, AppState> {
|
|||
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<AppProps, AppState> {
|
|||
}
|
||||
return el;
|
||||
}),
|
||||
snapshotAction: SnapshotAction.CAPTURE,
|
||||
storeAction: StoreAction.CAPTURE,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
@ -2334,7 +2334,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
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<AppProps, AppState> {
|
|||
elements?: SceneData["elements"];
|
||||
appState?: Pick<AppState, K> | 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<AppProps, AppState> {
|
|||
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<AppProps, AppState> {
|
|||
}
|
||||
|
||||
this.flowChartCreator.clear();
|
||||
this.syncActionResult({ storeAction: SnapshotAction.CAPTURE });
|
||||
this.syncActionResult({ storeAction: StoreAction.CAPTURE });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -6347,10 +6350,10 @@ class App extends React.Component<AppProps, AppState> {
|
|||
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<AppProps, AppState> {
|
|||
appState: {
|
||||
newElement: null,
|
||||
},
|
||||
snapshotAction: SnapshotAction.UPDATE,
|
||||
storeAction: StoreAction.UPDATE,
|
||||
});
|
||||
|
||||
return;
|
||||
|
@ -9172,7 +9175,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
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<AppProps, AppState> {
|
|||
isLoading: false,
|
||||
},
|
||||
replaceFiles: true,
|
||||
storeAction: SnapshotAction.CAPTURE,
|
||||
storeAction: StoreAction.CAPTURE,
|
||||
});
|
||||
return;
|
||||
} catch (error: any) {
|
||||
|
@ -10255,7 +10258,7 @@ class App extends React.Component<AppProps, AppState> {
|
|||
// 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<AppProps, AppState> {
|
|||
isLoading: false,
|
||||
},
|
||||
replaceFiles: true,
|
||||
storeAction: SnapshotAction.CAPTURE,
|
||||
storeAction: StoreAction.CAPTURE,
|
||||
});
|
||||
} else if (ret.type === MIME_TYPES.excalidrawlib) {
|
||||
await this.library
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -1271,7 +1271,7 @@ export class ElementsDelta implements DeltaContainer<SceneElementsMap> {
|
|||
});
|
||||
}
|
||||
|
||||
// CFDO II: this looks wrong
|
||||
// CFDO: this looks wrong
|
||||
if (isImageElement(element)) {
|
||||
const _delta = delta as Delta<ElementPartial<ExcalidrawImageElement>>;
|
||||
// we want to override `crop` only if modified so that we don't reset
|
||||
|
|
|
@ -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<IframeData, "sandbox">;
|
||||
|
||||
|
@ -344,7 +344,7 @@ export const actionSetEmbeddableAsActiveTool = register({
|
|||
type: "embeddable",
|
||||
}),
|
||||
},
|
||||
storeAction: SnapshotAction.NONE,
|
||||
storeAction: StoreAction.NONE,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -106,6 +106,7 @@ export class History {
|
|||
[nextElements, nextAppState, containsVisibleChange] =
|
||||
this.store.applyDeltaTo(historyEntry, nextElements, nextAppState, {
|
||||
triggerIncrement: true,
|
||||
updateSnapshot: true,
|
||||
});
|
||||
|
||||
prevSnapshot = this.store.snapshot;
|
||||
|
|
|
@ -259,7 +259,7 @@ export {
|
|||
bumpVersion,
|
||||
} from "./element/mutateElement";
|
||||
|
||||
export { SnapshotAction } from "./store";
|
||||
export { StoreAction } from "./store";
|
||||
|
||||
export { parseLibraryTokensFromUrl, useHandleLibrary } from "./data/library";
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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<typeof SnapshotAction>;
|
||||
export type StoreActionType = ValueOf<typeof StoreAction>;
|
||||
|
||||
/**
|
||||
* 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<SnapshotActionType> = new Set();
|
||||
private scheduledActions: Set<StoreActionType> = 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<string, OrderedExcalidrawElement>,
|
||||
nextElements: Map<string, OrderedExcalidrawElement>,
|
||||
) {
|
||||
prevElements: Map<string, ExcalidrawElement>,
|
||||
nextElements: Map<string, ExcalidrawElement>,
|
||||
): Map<string, OrderedExcalidrawElement> {
|
||||
const movedElements = new Map<string, ExcalidrawElement>();
|
||||
|
||||
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<string, OrderedExcalidrawElement> | 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<string, OrderedExcalidrawElement> | 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<string, OrderedExcalidrawElement>,
|
||||
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()),
|
||||
);
|
||||
|
|
|
@ -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<string, AcknowledgedDelta> =
|
||||
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<SocketId, Collaborator>;
|
||||
snapshotAction?: SnapshotActionType;
|
||||
storeAction?: StoreActionType;
|
||||
};
|
||||
|
||||
export enum UserIdleState {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue