actionHistory.tsx 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596
  1. import { Action, ActionResult } from "./types";
  2. import React from "react";
  3. import { undo, redo } from "../components/icons";
  4. import { ToolButton } from "../components/ToolButton";
  5. import { t } from "../i18n";
  6. import { SceneHistory, HistoryEntry } from "../history";
  7. import { ExcalidrawElement } from "../element/types";
  8. import { AppState } from "../types";
  9. import { KEYS } from "../keys";
  10. import { getElementMap } from "../element";
  11. import { newElementWith } from "../element/mutateElement";
  12. const writeData = (
  13. prevElements: readonly ExcalidrawElement[],
  14. appState: AppState,
  15. updater: () => HistoryEntry | null,
  16. ): ActionResult => {
  17. const commitToHistory = false;
  18. if (
  19. !appState.multiElement &&
  20. !appState.resizingElement &&
  21. !appState.editingElement &&
  22. !appState.draggingElement
  23. ) {
  24. const data = updater();
  25. if (data === null) {
  26. return { commitToHistory };
  27. }
  28. const prevElementMap = getElementMap(prevElements);
  29. const nextElements = data.elements;
  30. const nextElementMap = getElementMap(nextElements);
  31. const elements = nextElements
  32. .map((nextElement) =>
  33. newElementWith(
  34. prevElementMap[nextElement.id] || nextElement,
  35. nextElement,
  36. ),
  37. )
  38. .concat(
  39. prevElements
  40. .filter(
  41. (prevElement) => !nextElementMap.hasOwnProperty(prevElement.id),
  42. )
  43. .map((prevElement) =>
  44. newElementWith(prevElement, { isDeleted: true }),
  45. ),
  46. );
  47. return {
  48. elements,
  49. appState: { ...appState, ...data.appState },
  50. commitToHistory,
  51. syncHistory: true,
  52. };
  53. }
  54. return { commitToHistory };
  55. };
  56. const testUndo = (shift: boolean) => (event: KeyboardEvent) =>
  57. event[KEYS.CTRL_OR_CMD] && /z/i.test(event.key) && event.shiftKey === shift;
  58. type ActionCreator = (history: SceneHistory) => Action;
  59. export const createUndoAction: ActionCreator = (history) => ({
  60. name: "undo",
  61. perform: (elements, appState) =>
  62. writeData(elements, appState, () => history.undoOnce()),
  63. keyTest: testUndo(false),
  64. PanelComponent: ({ updateData }) => (
  65. <ToolButton
  66. type="button"
  67. icon={undo}
  68. aria-label={t("buttons.undo")}
  69. onClick={updateData}
  70. />
  71. ),
  72. commitToHistory: () => false,
  73. });
  74. export const createRedoAction: ActionCreator = (history) => ({
  75. name: "redo",
  76. perform: (elements, appState) =>
  77. writeData(elements, appState, () => history.redoOnce()),
  78. keyTest: testUndo(true),
  79. PanelComponent: ({ updateData }) => (
  80. <ToolButton
  81. type="button"
  82. icon={redo}
  83. aria-label={t("buttons.redo")}
  84. onClick={updateData}
  85. />
  86. ),
  87. commitToHistory: () => false,
  88. });