history.ts 2.0 KB

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