Prechádzať zdrojové kódy

fix: make history local to a given excalidraw instance (#3481)

* fix: make history local to a given excalidraw instance

* changelog

* Update src/packages/excalidraw/CHANGELOG.md
Aakansha Doshi 4 rokov pred
rodič
commit
d3106495b2

+ 2 - 2
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",

+ 25 - 26
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<HTMLDivElement | null>(
 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<AppProps, AppState> {
   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<AppProps, AppState> {
     }
     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<AppProps, AppState> {
     );
     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<AppProps, AppState> {
         });
         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<AppProps, AppState> {
           },
           () => {
             if (actionResult.syncHistory) {
-              history.setCurrentState(
+              this.history.setCurrentState(
                 this.state,
                 this.scene.getElementsIncludingDeleted(),
               );
@@ -689,7 +688,7 @@ class App extends React.Component<AppProps, AppState> {
   };
 
   private resetHistory = () => {
-    history.clear();
+    this.history.clear();
   };
 
   /**
@@ -822,6 +821,10 @@ class App extends React.Component<AppProps, AppState> {
           configurable: true,
           value: this,
         },
+        history: {
+          configurable: true,
+          value: this.history,
+        },
       });
     }
 
@@ -1123,7 +1126,7 @@ class App extends React.Component<AppProps, AppState> {
       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<AppProps, AppState> {
     );
 
     this.scene.replaceAllElements(nextElements);
-    history.resumeRecording();
+    this.history.resumeRecording();
     this.setState(
       selectGroupsForSelectedElements(
         {
@@ -1378,7 +1381,7 @@ class App extends React.Component<AppProps, AppState> {
       element,
     ]);
     this.setState({ selectedElementIds: { [element.id]: true } });
-    history.resumeRecording();
+    this.history.resumeRecording();
   }
 
   // Collaboration
@@ -1452,7 +1455,7 @@ class App extends React.Component<AppProps, AppState> {
 
   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<AppProps, AppState> {
             !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<AppProps, AppState> {
           fixBindingsAfterDeletion(this.scene.getElements(), [element]);
         }
         if (!isDeleted || isExistingElement) {
-          history.resumeRecording();
+          this.history.resumeRecording();
         }
 
         this.setState({
@@ -1970,7 +1973,7 @@ class App extends React.Component<AppProps, AppState> {
         !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<AppProps, AppState> {
             event,
             this.state,
             (appState) => this.setState(appState),
-            history,
+            this.history,
             pointerDownState.origin,
           );
           if (ret.hitElement) {
@@ -3379,7 +3382,7 @@ class App extends React.Component<AppProps, AppState> {
 
       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<AppProps, AppState> {
       }
 
       if (resizingElement) {
-        history.resumeRecording();
+        this.history.resumeRecording();
       }
 
       if (resizingElement && isInvisiblySmallElement(resizingElement)) {
@@ -3577,7 +3580,7 @@ class App extends React.Component<AppProps, AppState> {
         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<any, AppState>["setState"];
-      history: SceneHistory;
       app: InstanceType<typeof App>;
+      history: History;
     };
   }
 }
@@ -4291,10 +4294,6 @@ if (
         return this.app.scene.replaceAllElements(elements);
       },
     },
-    history: {
-      configurable: true,
-      get: () => history,
-    },
   });
 }
 export default App;

+ 2 - 2
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<HTMLCanvasElement>,
     appState: AppState,
     setState: React.Component<any, AppState>["setState"],
-    history: SceneHistory,
+    history: History,
     scenePointer: { x: number; y: number },
   ): {
     didAddPoint: boolean;

+ 2 - 5
src/history.ts

@@ -28,7 +28,7 @@ const clearAppStatePropertiesForHistory = (appState: AppState) => {
   };
 };
 
-export class SceneHistory {
+class History {
   private elementCache = new Map<string, Map<number, ExcalidrawElement>>();
   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;

+ 2 - 0
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)
 

+ 0 - 306
src/tests/__snapshots__/contextmenu.test.tsx.snap

@@ -905,40 +905,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -1101,40 +1067,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -1359,40 +1291,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -1652,40 +1550,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -2000,40 +1864,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -3063,40 +2893,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -3409,40 +3205,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -3824,40 +3586,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -4119,40 +3847,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },

+ 0 - 68
src/tests/__snapshots__/regressionTests.test.tsx.snap

@@ -2963,40 +2963,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },
@@ -5027,40 +4993,6 @@ 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,
-        "editingLinearElement": null,
-        "name": "Untitled-201933152653",
         "selectedElementIds": Object {
           "id0": true,
         },

+ 0 - 1
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");
 

+ 0 - 1
src/tests/regressionTests.test.tsx

@@ -57,7 +57,6 @@ beforeEach(async () => {
 
   localStorage.clear();
   renderScene.mockClear();
-  h.history.clear();
   reseed(7);
   setDateTimeForTests("201933152653");