actionBoundText.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. import { VERTICAL_ALIGN } from "../constants";
  2. import { getNonDeletedElements, isTextElement } from "../element";
  3. import { mutateElement } from "../element/mutateElement";
  4. import {
  5. getBoundTextElement,
  6. measureText,
  7. redrawTextBoundingBox,
  8. } from "../element/textElement";
  9. import {
  10. getOriginalContainerHeightFromCache,
  11. resetOriginalContainerCache,
  12. } from "../element/textWysiwyg";
  13. import {
  14. hasBoundTextElement,
  15. isTextBindableContainer,
  16. } from "../element/typeChecks";
  17. import {
  18. ExcalidrawTextContainer,
  19. ExcalidrawTextElement,
  20. } from "../element/types";
  21. import { getSelectedElements } from "../scene";
  22. import { getFontString } from "../utils";
  23. import { register } from "./register";
  24. export const actionUnbindText = register({
  25. name: "unbindText",
  26. contextItemLabel: "labels.unbindText",
  27. trackEvent: { category: "element" },
  28. predicate: (elements, appState) => {
  29. const selectedElements = getSelectedElements(elements, appState);
  30. return selectedElements.some((element) => hasBoundTextElement(element));
  31. },
  32. perform: (elements, appState) => {
  33. const selectedElements = getSelectedElements(
  34. getNonDeletedElements(elements),
  35. appState,
  36. );
  37. selectedElements.forEach((element) => {
  38. const boundTextElement = getBoundTextElement(element);
  39. if (boundTextElement) {
  40. const { width, height } = measureText(
  41. boundTextElement.originalText,
  42. getFontString(boundTextElement),
  43. );
  44. const originalContainerHeight = getOriginalContainerHeightFromCache(
  45. element.id,
  46. );
  47. resetOriginalContainerCache(element.id);
  48. mutateElement(boundTextElement as ExcalidrawTextElement, {
  49. containerId: null,
  50. width,
  51. height,
  52. text: boundTextElement.originalText,
  53. });
  54. mutateElement(element, {
  55. boundElements: element.boundElements?.filter(
  56. (ele) => ele.id !== boundTextElement.id,
  57. ),
  58. height: originalContainerHeight
  59. ? originalContainerHeight
  60. : element.height,
  61. });
  62. }
  63. });
  64. return {
  65. elements,
  66. appState,
  67. commitToHistory: true,
  68. };
  69. },
  70. });
  71. export const actionBindText = register({
  72. name: "bindText",
  73. contextItemLabel: "labels.bindText",
  74. trackEvent: { category: "element" },
  75. predicate: (elements, appState) => {
  76. const selectedElements = getSelectedElements(elements, appState);
  77. if (selectedElements.length === 2) {
  78. const textElement =
  79. isTextElement(selectedElements[0]) ||
  80. isTextElement(selectedElements[1]);
  81. let bindingContainer;
  82. if (isTextBindableContainer(selectedElements[0])) {
  83. bindingContainer = selectedElements[0];
  84. } else if (isTextBindableContainer(selectedElements[1])) {
  85. bindingContainer = selectedElements[1];
  86. }
  87. if (
  88. textElement &&
  89. bindingContainer &&
  90. getBoundTextElement(bindingContainer) === null
  91. ) {
  92. return true;
  93. }
  94. }
  95. return false;
  96. },
  97. perform: (elements, appState) => {
  98. const selectedElements = getSelectedElements(
  99. getNonDeletedElements(elements),
  100. appState,
  101. );
  102. let textElement: ExcalidrawTextElement;
  103. let container: ExcalidrawTextContainer;
  104. if (
  105. isTextElement(selectedElements[0]) &&
  106. isTextBindableContainer(selectedElements[1])
  107. ) {
  108. textElement = selectedElements[0];
  109. container = selectedElements[1];
  110. } else {
  111. textElement = selectedElements[1] as ExcalidrawTextElement;
  112. container = selectedElements[0] as ExcalidrawTextContainer;
  113. }
  114. mutateElement(textElement, {
  115. containerId: container.id,
  116. verticalAlign: VERTICAL_ALIGN.MIDDLE,
  117. });
  118. mutateElement(container, {
  119. boundElements: (container.boundElements || []).concat({
  120. type: "text",
  121. id: textElement.id,
  122. }),
  123. });
  124. redrawTextBoundingBox(textElement, container);
  125. const updatedElements = elements.slice();
  126. const textElementIndex = updatedElements.findIndex(
  127. (ele) => ele.id === textElement.id,
  128. );
  129. updatedElements.splice(textElementIndex, 1);
  130. const containerIndex = updatedElements.findIndex(
  131. (ele) => ele.id === container.id,
  132. );
  133. updatedElements.splice(containerIndex + 1, 0, textElement);
  134. return {
  135. elements: updatedElements,
  136. appState: { ...appState, selectedElementIds: { [container.id]: true } },
  137. commitToHistory: true,
  138. };
  139. },
  140. });