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
This commit is contained in:
Aakansha Doshi 2021-04-24 18:21:02 +05:30 committed by GitHub
parent 891ac82447
commit d3106495b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 33 additions and 411 deletions

View File

@ -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",

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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)

View File

@ -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,

View File

@ -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,

View File

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

View File

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