Extract history (#213)

* Extract History into its own module

* Encapsulate undo and redo actions within history

* Encapsulate clearing redo stack within History

* Add private access modifiers to scene history class member variables

* Remove duplicate files
This commit is contained in:
Gasim Gasimzada 2020-01-06 21:58:48 +04:00 committed by GitHub
parent 054669cfef
commit d0365933a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 90 deletions

View file

@ -33,43 +33,15 @@ import { EditableText } from "./components/EditableText";
import { ButtonSelect } from "./components/ButtonSelect";
import { ColorPicker } from "./components/ColorPicker";
import { SHAPES, findShapeByKey, shapesShortcutKeys } from "./shapes";
import { createHistory } from "./history";
import "./styles.scss";
const { elements } = createScene();
const { history } = createHistory();
const DEFAULT_PROJECT_NAME = `excalidraw-${getDateTime()}`;
let skipHistory = false;
const stateHistory: string[] = [];
const redoStack: string[] = [];
function generateHistoryCurrentEntry() {
return JSON.stringify(
elements.map(element => ({ ...element, isSelected: false }))
);
}
function pushHistoryEntry(newEntry: string) {
if (
stateHistory.length > 0 &&
stateHistory[stateHistory.length - 1] === newEntry
) {
// If the last entry is the same as this one, ignore it
return;
}
stateHistory.push(newEntry);
}
function restoreHistoryEntry(entry: string) {
const newElements = JSON.parse(entry);
elements.splice(0, elements.length);
newElements.forEach((newElement: ExcalidrawElement) => {
generateDraw(newElement);
elements.push(newElement);
});
// When restoring, we shouldn't add an history entry otherwise we'll be stuck with it and can't go back
skipHistory = true;
}
const CANVAS_WINDOW_OFFSET_LEFT = 250;
const CANVAS_WINDOW_OFFSET_TOP = 0;
@ -231,25 +203,12 @@ class App extends React.Component<{}, AppState> {
} else if (shapesShortcutKeys.includes(event.key.toLowerCase())) {
this.setState({ elementType: findShapeByKey(event.key) });
} else if (event.metaKey && event.code === "KeyZ") {
const currentEntry = generateHistoryCurrentEntry();
if (event.shiftKey) {
// Redo action
const entryToRestore = redoStack.pop();
if (entryToRestore !== undefined) {
restoreHistoryEntry(entryToRestore);
stateHistory.push(currentEntry);
}
history.redoOnce(elements);
} else {
// undo action
let lastEntry = stateHistory.pop();
// If nothing was changed since last, take the previous one
if (currentEntry === lastEntry) {
lastEntry = stateHistory.pop();
}
if (lastEntry !== undefined) {
restoreHistoryEntry(lastEntry);
redoStack.push(currentEntry);
}
history.undoOnce(elements);
}
this.forceUpdate();
event.preventDefault();
@ -798,7 +757,7 @@ class App extends React.Component<{}, AppState> {
lastX = x;
lastY = y;
// We don't want to save history when resizing an element
skipHistory = true;
history.skipRecording();
this.forceUpdate();
return;
}
@ -818,7 +777,7 @@ class App extends React.Component<{}, AppState> {
lastX = x;
lastY = y;
// We don't want to save history when dragging an element to initially size it
skipHistory = true;
history.skipRecording();
this.forceUpdate();
return;
}
@ -850,7 +809,7 @@ class App extends React.Component<{}, AppState> {
setSelection(elements, draggingElement);
}
// We don't want to save history when moving an element
skipHistory = true;
history.skipRecording();
this.forceUpdate();
};
@ -892,7 +851,7 @@ class App extends React.Component<{}, AppState> {
window.addEventListener("mouseup", onMouseUp);
// We don't want to save history on mouseDown, only on mouseUp when it's fully configured
skipHistory = true;
history.skipRecording();
this.forceUpdate();
}}
onDoubleClick={e => {
@ -952,11 +911,11 @@ class App extends React.Component<{}, AppState> {
viewBackgroundColor: this.state.viewBackgroundColor
});
saveToLocalStorage(elements, this.state);
if (!skipHistory) {
pushHistoryEntry(generateHistoryCurrentEntry());
redoStack.splice(0, redoStack.length);
if (history.isRecording()) {
history.pushEntry(history.generateCurrentEntry(elements));
history.clearRedoStack();
}
skipHistory = false;
history.resumeRecording();
}
}