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