diff --git a/src/actions/actionHistory.tsx b/src/actions/actionHistory.tsx index c0971c2e..6f7536df 100644 --- a/src/actions/actionHistory.tsx +++ b/src/actions/actionHistory.tsx @@ -3,7 +3,7 @@ import React from "react"; import { undo, redo } from "../components/icons"; import { ToolButton } from "../components/ToolButton"; import { t } from "../i18n"; -import { SceneHistory, HistoryEntry } from "../history"; +import History, { HistoryEntry } from "../history"; import { ExcalidrawElement } from "../element/types"; import { AppState } from "../types"; import { isWindows, KEYS } from "../keys"; @@ -59,7 +59,7 @@ const writeData = ( return { commitToHistory }; }; -type ActionCreator = (history: SceneHistory) => Action; +type ActionCreator = (history: History) => Action; export const createUndoAction: ActionCreator = (history) => ({ name: "undo", diff --git a/src/components/App.tsx b/src/components/App.tsx index 56c67a1a..7cc50cb0 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -137,7 +137,7 @@ import { isSelectedViaGroup, selectGroupsForSelectedElements, } from "../groups"; -import { createHistory, SceneHistory } from "../history"; +import History from "../history"; import { defaultLang, getLanguage, languages, setLanguage, t } from "../i18n"; import { CODES, @@ -203,8 +203,6 @@ const ExcalidrawContainerContext = React.createContext( export const useExcalidrawContainer = () => useContext(ExcalidrawContainerContext); -const { history } = createHistory(); - let didTapTwice: boolean = false; let tappedTwiceTimer = 0; let cursorX = 0; @@ -321,6 +319,7 @@ class App extends React.Component { public library: Library; public libraryItemsFromStorage: LibraryItems | undefined; private id: string; + private history: History; constructor(props: AppProps) { super(props); @@ -379,7 +378,7 @@ class App extends React.Component { } this.scene = new Scene(); this.library = new Library(this); - + this.history = new History(); this.actionManager = new ActionManager( this.syncActionResult, () => this.state, @@ -388,8 +387,8 @@ class App extends React.Component { ); this.actionManager.registerAll(actions); - this.actionManager.registerAction(createUndoAction(history)); - this.actionManager.registerAction(createRedoAction(history)); + this.actionManager.registerAction(createUndoAction(this.history)); + this.actionManager.registerAction(createRedoAction(this.history)); } private renderCanvas() { @@ -564,13 +563,13 @@ class App extends React.Component { }); this.scene.replaceAllElements(actionResult.elements); if (actionResult.commitToHistory) { - history.resumeRecording(); + this.history.resumeRecording(); } } if (actionResult.appState || editingElement) { if (actionResult.commitToHistory) { - history.resumeRecording(); + this.history.resumeRecording(); } let viewModeEnabled = actionResult?.appState?.viewModeEnabled || false; @@ -614,7 +613,7 @@ class App extends React.Component { }, () => { if (actionResult.syncHistory) { - history.setCurrentState( + this.history.setCurrentState( this.state, this.scene.getElementsIncludingDeleted(), ); @@ -689,7 +688,7 @@ class App extends React.Component { }; private resetHistory = () => { - history.clear(); + this.history.clear(); }; /** @@ -822,6 +821,10 @@ class App extends React.Component { configurable: true, value: this, }, + history: { + configurable: true, + value: this.history, + }, }); } @@ -1123,7 +1126,7 @@ class App extends React.Component { this.setState({ scrolledOutside }); } - history.record(this.state, this.scene.getElementsIncludingDeleted()); + this.history.record(this.state, this.scene.getElementsIncludingDeleted()); // Do not notify consumers if we're still loading the scene. Among other // potential issues, this fixes a case where the tab isn't focused during @@ -1332,7 +1335,7 @@ class App extends React.Component { ); this.scene.replaceAllElements(nextElements); - history.resumeRecording(); + this.history.resumeRecording(); this.setState( selectGroupsForSelectedElements( { @@ -1378,7 +1381,7 @@ class App extends React.Component { element, ]); this.setState({ selectedElementIds: { [element.id]: true } }); - history.resumeRecording(); + this.history.resumeRecording(); } // Collaboration @@ -1452,7 +1455,7 @@ class App extends React.Component { public updateScene = withBatchedUpdates((sceneData: SceneData) => { if (sceneData.commitToHistory) { - history.resumeRecording(); + this.history.resumeRecording(); } // currently we only support syncing background color @@ -1595,7 +1598,7 @@ class App extends React.Component { !this.state.editingLinearElement || this.state.editingLinearElement.elementId !== selectedElements[0].id ) { - history.resumeRecording(); + this.history.resumeRecording(); this.setState({ editingLinearElement: new LinearElementEditor( selectedElements[0], @@ -1789,7 +1792,7 @@ class App extends React.Component { fixBindingsAfterDeletion(this.scene.getElements(), [element]); } if (!isDeleted || isExistingElement) { - history.resumeRecording(); + this.history.resumeRecording(); } this.setState({ @@ -1970,7 +1973,7 @@ class App extends React.Component { !this.state.editingLinearElement || this.state.editingLinearElement.elementId !== selectedElements[0].id ) { - history.resumeRecording(); + this.history.resumeRecording(); this.setState({ editingLinearElement: new LinearElementEditor( selectedElements[0], @@ -2687,7 +2690,7 @@ class App extends React.Component { event, this.state, (appState) => this.setState(appState), - history, + this.history, pointerDownState.origin, ); if (ret.hitElement) { @@ -3379,7 +3382,7 @@ class App extends React.Component { if (isLinearElement(draggingElement)) { if (draggingElement!.points.length > 1) { - history.resumeRecording(); + this.history.resumeRecording(); } const pointerCoords = viewportCoordsToSceneCoords( childEvent, @@ -3463,7 +3466,7 @@ class App extends React.Component { } if (resizingElement) { - history.resumeRecording(); + this.history.resumeRecording(); } if (resizingElement && isInvisiblySmallElement(resizingElement)) { @@ -3577,7 +3580,7 @@ class App extends React.Component { elementType !== "selection" || isSomeElementSelected(this.scene.getElements(), this.state) ) { - history.resumeRecording(); + this.history.resumeRecording(); } if (pointerDownState.drag.hasOccurred || isResizing || isRotating) { @@ -4269,8 +4272,8 @@ declare global { elements: readonly ExcalidrawElement[]; state: AppState; setState: React.Component["setState"]; - history: SceneHistory; app: InstanceType; + history: History; }; } } @@ -4291,10 +4294,6 @@ if ( return this.app.scene.replaceAllElements(elements); }, }, - history: { - configurable: true, - get: () => history, - }, }); } export default App; diff --git a/src/element/linearElementEditor.ts b/src/element/linearElementEditor.ts index 50eb9308..c4f38da4 100644 --- a/src/element/linearElementEditor.ts +++ b/src/element/linearElementEditor.ts @@ -10,7 +10,7 @@ import { getElementAbsoluteCoords } from "."; import { getElementPointsCoords } from "./bounds"; import { Point, AppState } from "../types"; import { mutateElement } from "./mutateElement"; -import { SceneHistory } from "../history"; +import History from "../history"; import Scene from "../scene/Scene"; import { @@ -167,7 +167,7 @@ export class LinearElementEditor { event: React.PointerEvent, appState: AppState, setState: React.Component["setState"], - history: SceneHistory, + history: History, scenePointer: { x: number; y: number }, ): { didAddPoint: boolean; diff --git a/src/history.ts b/src/history.ts index d0b29a25..bbe054d5 100644 --- a/src/history.ts +++ b/src/history.ts @@ -28,7 +28,7 @@ const clearAppStatePropertiesForHistory = (appState: AppState) => { }; }; -export class SceneHistory { +class History { private elementCache = new Map>(); private recording: boolean = true; private stateHistory: DehydratedHistoryEntry[] = []; @@ -260,7 +260,4 @@ export class SceneHistory { } } -export const createHistory: () => { history: SceneHistory } = () => { - const history = new SceneHistory(); - return { history }; -}; +export default History; diff --git a/src/packages/excalidraw/CHANGELOG.md b/src/packages/excalidraw/CHANGELOG.md index 64559af8..0bed00b9 100644 --- a/src/packages/excalidraw/CHANGELOG.md +++ b/src/packages/excalidraw/CHANGELOG.md @@ -38,6 +38,8 @@ Please add the latest change on the top under the correct section. ### Fixes +- Make history local to a given Excalidraw instance. This fixes a case where history was getting shared when you have multiple Excalidraw components on the same page [#3481](https://github.com/excalidraw/excalidraw/pull/3481). + - Use active Excalidraw component when editing text. This fixes a case where text editing was not working when you have multiple Excalidraw components on the same page [#3478](https://github.com/excalidraw/excalidraw/pull/3478). - When switching theme, apply it only to the active Excalidraw component. This fixes a case where the theme was getting applied to the first Excalidraw component if you had multiple Excalidraw components on the same page [#3446](https://github.com/excalidraw/excalidraw/pull/3446) diff --git a/src/tests/__snapshots__/contextmenu.test.tsx.snap b/src/tests/__snapshots__/contextmenu.test.tsx.snap index d5158b49..e2d3f3b6 100644 --- a/src/tests/__snapshots__/contextmenu.test.tsx.snap +++ b/src/tests/__snapshots__/contextmenu.test.tsx.snap @@ -900,40 +900,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -1096,40 +1062,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -1354,40 +1286,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -1647,40 +1545,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -1995,40 +1859,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -3058,40 +2888,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -3404,40 +3200,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -3819,40 +3581,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -4114,40 +3842,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 449462985, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": -10, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 3e8dcd9b..dd64a7ec 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -2958,40 +2958,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 1278240551, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": 100, - "y": 100, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, @@ -5022,40 +4988,6 @@ Object { }, "elements": Array [], }, - Object { - "appState": Object { - "editingGroupId": null, - "editingLinearElement": null, - "name": "Untitled-201933152653", - "selectedElementIds": Object {}, - "viewBackgroundColor": "#ffffff", - }, - "elements": Array [ - Object { - "angle": 0, - "backgroundColor": "transparent", - "boundElementIds": null, - "fillStyle": "hachure", - "groupIds": Array [], - "height": 0, - "id": "id0", - "isDeleted": false, - "opacity": 100, - "roughness": 1, - "seed": 337897, - "strokeColor": "#000000", - "strokeSharpness": "sharp", - "strokeStyle": "solid", - "strokeWidth": 1, - "type": "rectangle", - "version": 1, - "versionNonce": 0, - "width": 0, - "x": 0, - "y": 0, - }, - ], - }, Object { "appState": Object { "editingGroupId": null, diff --git a/src/tests/contextmenu.test.tsx b/src/tests/contextmenu.test.tsx index 343ffbcc..198d20a6 100644 --- a/src/tests/contextmenu.test.tsx +++ b/src/tests/contextmenu.test.tsx @@ -62,7 +62,6 @@ describe("contextMenu element", () => { beforeEach(async () => { localStorage.clear(); renderScene.mockClear(); - h.history.clear(); reseed(7); setDateTimeForTests("201933152653"); diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 3aca8c38..7af86005 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -57,7 +57,6 @@ beforeEach(async () => { localStorage.clear(); renderScene.mockClear(); - h.history.clear(); reseed(7); setDateTimeForTests("201933152653");