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 filespull/205/head
parent
054669cfef
commit
d0365933a9
@ -0,0 +1,80 @@
|
||||
import { ExcalidrawElement } from "./element/types";
|
||||
import { generateDraw } from "./element";
|
||||
|
||||
class SceneHistory {
|
||||
private recording: boolean = true;
|
||||
private stateHistory: string[] = [];
|
||||
private 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();
|
||||
}
|
||||
|
||||
clearRedoStack() {
|
||||
this.redoStack.splice(0, this.redoStack.length);
|
||||
}
|
||||
|
||||
redoOnce(elements: ExcalidrawElement[]) {
|
||||
const currentEntry = this.generateCurrentEntry(elements);
|
||||
const entryToRestore = this.redoStack.pop();
|
||||
if (entryToRestore !== undefined) {
|
||||
this.restoreEntry(elements, entryToRestore);
|
||||
this.stateHistory.push(currentEntry);
|
||||
}
|
||||
}
|
||||
|
||||
undoOnce(elements: ExcalidrawElement[]) {
|
||||
const currentEntry = this.generateCurrentEntry(elements);
|
||||
let entryToRestore = this.stateHistory.pop();
|
||||
|
||||
// If nothing was changed since last, take the previous one
|
||||
if (currentEntry === entryToRestore) {
|
||||
entryToRestore = this.stateHistory.pop();
|
||||
}
|
||||
if (entryToRestore !== undefined) {
|
||||
this.restoreEntry(elements, entryToRestore);
|
||||
this.redoStack.push(currentEntry);
|
||||
}
|
||||
}
|
||||
|
||||
isRecording() {
|
||||
return this.recording;
|
||||
}
|
||||
|
||||
skipRecording() {
|
||||
this.recording = false;
|
||||
}
|
||||
|
||||
resumeRecording() {
|
||||
this.recording = true;
|
||||
}
|
||||
}
|
||||
|
||||
export const createHistory: () => { history: SceneHistory } = () => {
|
||||
const history = new SceneHistory();
|
||||
return { history };
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
/**
|
||||
* https://stackoverflow.com/a/3368118
|
||||
* Draws a rounded rectangle using the current state of the canvas.
|
||||
* @param {CanvasRenderingContext2D} context
|
||||
* @param {Number} x The top left x coordinate
|
||||
* @param {Number} y The top left y coordinate
|
||||
* @param {Number} width The width of the rectangle
|
||||
* @param {Number} height The height of the rectangle
|
||||
* @param {Number} radius The corner radius
|
||||
*/
|
||||
export function roundRect(
|
||||
context: CanvasRenderingContext2D,
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
radius: number
|
||||
) {
|
||||
context.beginPath();
|
||||
context.moveTo(x + radius, y);
|
||||
context.lineTo(x + width - radius, y);
|
||||
context.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||
context.lineTo(x + width, y + height - radius);
|
||||
context.quadraticCurveTo(
|
||||
x + width,
|
||||
y + height,
|
||||
x + width - radius,
|
||||
y + height
|
||||
);
|
||||
context.lineTo(x + radius, y + height);
|
||||
context.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||
context.lineTo(x, y + radius);
|
||||
context.quadraticCurveTo(x, y, x + radius, y);
|
||||
context.closePath();
|
||||
context.fill();
|
||||
context.stroke();
|
||||
}
|
Loading…
Reference in New Issue