From 01a922bf20c581155bbd4511d830c6883bc7d509 Mon Sep 17 00:00:00 2001 From: Gasim Gasimzada Date: Mon, 6 Jan 2020 21:32:07 +0400 Subject: [PATCH] Extract History into its own module --- src/history.ts | 53 +++++++++++++++++++++++++++++++++++++++++ src/index.tsx | 64 ++++++++++++++------------------------------------ 2 files changed, 71 insertions(+), 46 deletions(-) create mode 100644 src/history.ts diff --git a/src/history.ts b/src/history.ts new file mode 100644 index 000000000..2f829b078 --- /dev/null +++ b/src/history.ts @@ -0,0 +1,53 @@ +import { ExcalidrawElement } from "./element/types"; +import { generateDraw } from "./element"; + +class SceneHistory { + recording: boolean = true; + stateHistory: string[] = []; + redoStack: string[] = []; + + generateCurrentEntry(elements: ExcalidrawElement[]) { + return JSON.stringify( + elements.map(element => ({ ...element, isSelected: false })) + ); + } + + pushEntry(newEntry: string) { + if ( + this.stateHistory.length > 0 && + this.stateHistory[this.stateHistory.length - 1] === newEntry + ) { + // If the last entry is the same as this one, ignore it + return; + } + this.stateHistory.push(newEntry); + } + + restoreEntry(elements: ExcalidrawElement[], 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 + this.skipRecording(); + } + + isRecording() { + return this.recording; + } + + skipRecording() { + this.recording = false; + } + + resumeRecording() { + this.recording = true; + } +} + +export const createHistory: () => { history: SceneHistory } = () => { + const history = new SceneHistory(); + return { history }; +}; diff --git a/src/index.tsx b/src/index.tsx index 5f1056d22..eb711352f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -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,24 +203,24 @@ 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(); + const currentEntry = history.generateCurrentEntry(elements); if (event.shiftKey) { // Redo action - const entryToRestore = redoStack.pop(); + const entryToRestore = history.redoStack.pop(); if (entryToRestore !== undefined) { - restoreHistoryEntry(entryToRestore); - stateHistory.push(currentEntry); + history.restoreEntry(elements, entryToRestore); + history.stateHistory.push(currentEntry); } } else { // undo action - let lastEntry = stateHistory.pop(); + let lastEntry = history.stateHistory.pop(); // If nothing was changed since last, take the previous one if (currentEntry === lastEntry) { - lastEntry = stateHistory.pop(); + lastEntry = history.stateHistory.pop(); } if (lastEntry !== undefined) { - restoreHistoryEntry(lastEntry); - redoStack.push(currentEntry); + history.restoreEntry(elements, lastEntry); + history.redoStack.push(currentEntry); } } this.forceUpdate(); @@ -798,7 +770,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 +790,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 +822,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 +864,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 +924,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.redoStack.splice(0, history.redoStack.length); } - skipHistory = false; + history.resumeRecording(); } }