actionDeleteSelected.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { isSomeElementSelected } from "../scene";
  2. import { KEYS } from "../keys";
  3. import { ToolButton } from "../components/ToolButton";
  4. import { trash } from "../components/icons";
  5. import { t } from "../i18n";
  6. import { register } from "./register";
  7. import { getNonDeletedElements } from "../element";
  8. import { ExcalidrawElement } from "../element/types";
  9. import { AppState } from "../types";
  10. import { newElementWith } from "../element/mutateElement";
  11. import { getElementsInGroup } from "../groups";
  12. import { LinearElementEditor } from "../element/linearElementEditor";
  13. import { fixBindingsAfterDeletion } from "../element/binding";
  14. import { isBoundToContainer } from "../element/typeChecks";
  15. const deleteSelectedElements = (
  16. elements: readonly ExcalidrawElement[],
  17. appState: AppState,
  18. ) => {
  19. return {
  20. elements: elements.map((el) => {
  21. if (appState.selectedElementIds[el.id]) {
  22. return newElementWith(el, { isDeleted: true });
  23. }
  24. if (
  25. isBoundToContainer(el) &&
  26. appState.selectedElementIds[el.containerId]
  27. ) {
  28. return newElementWith(el, { isDeleted: true });
  29. }
  30. return el;
  31. }),
  32. appState: {
  33. ...appState,
  34. selectedElementIds: {},
  35. },
  36. };
  37. };
  38. const handleGroupEditingState = (
  39. appState: AppState,
  40. elements: readonly ExcalidrawElement[],
  41. ): AppState => {
  42. if (appState.editingGroupId) {
  43. const siblingElements = getElementsInGroup(
  44. getNonDeletedElements(elements),
  45. appState.editingGroupId!,
  46. );
  47. if (siblingElements.length) {
  48. return {
  49. ...appState,
  50. selectedElementIds: { [siblingElements[0].id]: true },
  51. };
  52. }
  53. }
  54. return appState;
  55. };
  56. export const actionDeleteSelected = register({
  57. name: "deleteSelectedElements",
  58. perform: (elements, appState) => {
  59. if (appState.editingLinearElement) {
  60. const {
  61. elementId,
  62. selectedPointsIndices,
  63. startBindingElement,
  64. endBindingElement,
  65. } = appState.editingLinearElement;
  66. const element = LinearElementEditor.getElement(elementId);
  67. if (!element) {
  68. return false;
  69. }
  70. if (
  71. // case: no point selected → delete whole element
  72. selectedPointsIndices == null ||
  73. // case: deleting last remaining point
  74. element.points.length < 2
  75. ) {
  76. const nextElements = elements.filter((el) => el.id !== element.id);
  77. const nextAppState = handleGroupEditingState(appState, nextElements);
  78. return {
  79. elements: nextElements,
  80. appState: {
  81. ...nextAppState,
  82. editingLinearElement: null,
  83. },
  84. commitToHistory: false,
  85. };
  86. }
  87. // We cannot do this inside `movePoint` because it is also called
  88. // when deleting the uncommitted point (which hasn't caused any binding)
  89. const binding = {
  90. startBindingElement: selectedPointsIndices?.includes(0)
  91. ? null
  92. : startBindingElement,
  93. endBindingElement: selectedPointsIndices?.includes(
  94. element.points.length - 1,
  95. )
  96. ? null
  97. : endBindingElement,
  98. };
  99. LinearElementEditor.deletePoints(element, selectedPointsIndices);
  100. return {
  101. elements,
  102. appState: {
  103. ...appState,
  104. editingLinearElement: {
  105. ...appState.editingLinearElement,
  106. ...binding,
  107. selectedPointsIndices:
  108. selectedPointsIndices?.[0] > 0
  109. ? [selectedPointsIndices[0] - 1]
  110. : [0],
  111. },
  112. },
  113. commitToHistory: true,
  114. };
  115. }
  116. let { elements: nextElements, appState: nextAppState } =
  117. deleteSelectedElements(elements, appState);
  118. fixBindingsAfterDeletion(
  119. nextElements,
  120. elements.filter(({ id }) => appState.selectedElementIds[id]),
  121. );
  122. nextAppState = handleGroupEditingState(nextAppState, nextElements);
  123. return {
  124. elements: nextElements,
  125. appState: {
  126. ...nextAppState,
  127. elementType: "selection",
  128. multiElement: null,
  129. },
  130. commitToHistory: isSomeElementSelected(
  131. getNonDeletedElements(elements),
  132. appState,
  133. ),
  134. };
  135. },
  136. contextItemLabel: "labels.delete",
  137. keyTest: (event) => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
  138. PanelComponent: ({ elements, appState, updateData }) => (
  139. <ToolButton
  140. type="button"
  141. icon={trash}
  142. title={t("labels.delete")}
  143. aria-label={t("labels.delete")}
  144. onClick={() => updateData(null)}
  145. visible={isSomeElementSelected(getNonDeletedElements(elements), appState)}
  146. />
  147. ),
  148. });