history.ts 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. import { ExcalidrawElement } from "./element/types";
  2. class SceneHistory {
  3. private recording: boolean = true;
  4. private stateHistory: string[] = [];
  5. private redoStack: string[] = [];
  6. generateCurrentEntry(elements: readonly ExcalidrawElement[]) {
  7. return JSON.stringify(
  8. elements.map(({ shape, ...element }) => ({
  9. ...element,
  10. isSelected: false
  11. }))
  12. );
  13. }
  14. pushEntry(newEntry: string) {
  15. if (
  16. this.stateHistory.length > 0 &&
  17. this.stateHistory[this.stateHistory.length - 1] === newEntry
  18. ) {
  19. // If the last entry is the same as this one, ignore it
  20. return;
  21. }
  22. this.stateHistory.push(newEntry);
  23. // As a new entry was pushed, we invalidate the redo stack
  24. this.clearRedoStack();
  25. }
  26. restoreEntry(entry: string) {
  27. try {
  28. return JSON.parse(entry);
  29. } catch {
  30. return null;
  31. }
  32. }
  33. clearRedoStack() {
  34. this.redoStack.splice(0, this.redoStack.length);
  35. }
  36. redoOnce() {
  37. if (this.redoStack.length === 0) {
  38. return null;
  39. }
  40. const entryToRestore = this.redoStack.pop();
  41. if (entryToRestore !== undefined) {
  42. this.stateHistory.push(entryToRestore);
  43. return this.restoreEntry(entryToRestore);
  44. }
  45. return null;
  46. }
  47. undoOnce() {
  48. if (this.stateHistory.length === 0) {
  49. return null;
  50. }
  51. const currentEntry = this.stateHistory.pop();
  52. const entryToRestore = this.stateHistory[this.stateHistory.length - 1];
  53. if (currentEntry !== undefined) {
  54. this.redoStack.push(currentEntry);
  55. return this.restoreEntry(entryToRestore);
  56. }
  57. return null;
  58. }
  59. isRecording() {
  60. return this.recording;
  61. }
  62. skipRecording() {
  63. this.recording = false;
  64. }
  65. resumeRecording() {
  66. this.recording = true;
  67. }
  68. }
  69. export const createHistory: () => { history: SceneHistory } = () => {
  70. const history = new SceneHistory();
  71. return { history };
  72. };