actionFinalize.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import { KEYS } from "../keys";
  2. import { isInvisiblySmallElement } from "../element";
  3. import { resetCursor } from "../utils";
  4. import React from "react";
  5. import { ToolButton } from "../components/ToolButton";
  6. import { done } from "../components/icons";
  7. import { t } from "../i18n";
  8. import { register } from "./register";
  9. import { mutateElement } from "../element/mutateElement";
  10. import { isPathALoop } from "../math";
  11. import { LinearElementEditor } from "../element/linearElementEditor";
  12. import Scene from "../scene/Scene";
  13. import {
  14. maybeBindLinearElement,
  15. bindOrUnbindLinearElement,
  16. } from "../element/binding";
  17. import { isBindingElement } from "../element/typeChecks";
  18. export const actionFinalize = register({
  19. name: "finalize",
  20. perform: (elements, appState) => {
  21. if (appState.editingLinearElement) {
  22. const {
  23. elementId,
  24. startBindingElement,
  25. endBindingElement,
  26. } = appState.editingLinearElement;
  27. const element = LinearElementEditor.getElement(elementId);
  28. if (element) {
  29. if (isBindingElement(element)) {
  30. bindOrUnbindLinearElement(
  31. element,
  32. startBindingElement,
  33. endBindingElement,
  34. );
  35. }
  36. return {
  37. elements:
  38. element.points.length < 2 || isInvisiblySmallElement(element)
  39. ? elements.filter((el) => el.id !== element.id)
  40. : undefined,
  41. appState: {
  42. ...appState,
  43. editingLinearElement: null,
  44. },
  45. commitToHistory: true,
  46. };
  47. }
  48. }
  49. let newElements = elements;
  50. if (window.document.activeElement instanceof HTMLElement) {
  51. window.document.activeElement.blur();
  52. }
  53. const multiPointElement = appState.multiElement
  54. ? appState.multiElement
  55. : appState.editingElement?.type === "draw"
  56. ? appState.editingElement
  57. : null;
  58. if (multiPointElement) {
  59. // pen and mouse have hover
  60. if (
  61. multiPointElement.type !== "draw" &&
  62. appState.lastPointerDownWith !== "touch"
  63. ) {
  64. const { points, lastCommittedPoint } = multiPointElement;
  65. if (
  66. !lastCommittedPoint ||
  67. points[points.length - 1] !== lastCommittedPoint
  68. ) {
  69. mutateElement(multiPointElement, {
  70. points: multiPointElement.points.slice(0, -1),
  71. });
  72. }
  73. }
  74. if (isInvisiblySmallElement(multiPointElement)) {
  75. newElements = newElements.slice(0, -1);
  76. }
  77. // If the multi point line closes the loop,
  78. // set the last point to first point.
  79. // This ensures that loop remains closed at different scales.
  80. const isLoop = isPathALoop(multiPointElement.points);
  81. if (
  82. multiPointElement.type === "line" ||
  83. multiPointElement.type === "draw"
  84. ) {
  85. if (isLoop) {
  86. const linePoints = multiPointElement.points;
  87. const firstPoint = linePoints[0];
  88. mutateElement(multiPointElement, {
  89. points: linePoints.map((point, index) =>
  90. index === linePoints.length - 1
  91. ? ([firstPoint[0], firstPoint[1]] as const)
  92. : point,
  93. ),
  94. });
  95. }
  96. }
  97. if (
  98. isBindingElement(multiPointElement) &&
  99. !isLoop &&
  100. multiPointElement.points.length > 1
  101. ) {
  102. const [x, y] = LinearElementEditor.getPointAtIndexGlobalCoordinates(
  103. multiPointElement,
  104. -1,
  105. );
  106. maybeBindLinearElement(
  107. multiPointElement,
  108. appState,
  109. Scene.getScene(multiPointElement)!,
  110. { x, y },
  111. );
  112. }
  113. if (!appState.elementLocked && appState.elementType !== "draw") {
  114. appState.selectedElementIds[multiPointElement.id] = true;
  115. }
  116. }
  117. if (
  118. (!appState.elementLocked && appState.elementType !== "draw") ||
  119. !multiPointElement
  120. ) {
  121. resetCursor();
  122. }
  123. return {
  124. elements: newElements,
  125. appState: {
  126. ...appState,
  127. elementType:
  128. (appState.elementLocked || appState.elementType === "draw") &&
  129. multiPointElement
  130. ? appState.elementType
  131. : "selection",
  132. draggingElement: null,
  133. multiElement: null,
  134. editingElement: null,
  135. startBoundElement: null,
  136. suggestedBindings: [],
  137. selectedElementIds:
  138. multiPointElement &&
  139. !appState.elementLocked &&
  140. appState.elementType !== "draw"
  141. ? {
  142. ...appState.selectedElementIds,
  143. [multiPointElement.id]: true,
  144. }
  145. : appState.selectedElementIds,
  146. },
  147. commitToHistory: appState.elementType === "draw",
  148. };
  149. },
  150. keyTest: (event, appState) =>
  151. (event.key === KEYS.ESCAPE &&
  152. (appState.editingLinearElement !== null ||
  153. (!appState.draggingElement && appState.multiElement === null))) ||
  154. ((event.key === KEYS.ESCAPE || event.key === KEYS.ENTER) &&
  155. appState.multiElement !== null),
  156. PanelComponent: ({ appState, updateData }) => (
  157. <ToolButton
  158. type="button"
  159. icon={done}
  160. title={t("buttons.done")}
  161. aria-label={t("buttons.done")}
  162. onClick={updateData}
  163. visible={appState.multiElement != null}
  164. />
  165. ),
  166. });