actionBoundText.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148
  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, baseline } = 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. baseline,
  53. text: boundTextElement.originalText,
  54. });
  55. mutateElement(element, {
  56. boundElements: element.boundElements?.filter(
  57. (ele) => ele.id !== boundTextElement.id,
  58. ),
  59. height: originalContainerHeight
  60. ? originalContainerHeight
  61. : element.height,
  62. });
  63. }
  64. });
  65. return {
  66. elements,
  67. appState,
  68. commitToHistory: true,
  69. };
  70. },
  71. });
  72. export const actionBindText = register({
  73. name: "bindText",
  74. contextItemLabel: "labels.bindText",
  75. trackEvent: { category: "element" },
  76. predicate: (elements, appState) => {
  77. const selectedElements = getSelectedElements(elements, appState);
  78. if (selectedElements.length === 2) {
  79. const textElement =
  80. isTextElement(selectedElements[0]) ||
  81. isTextElement(selectedElements[1]);
  82. let bindingContainer;
  83. if (isTextBindableContainer(selectedElements[0])) {
  84. bindingContainer = selectedElements[0];
  85. } else if (isTextBindableContainer(selectedElements[1])) {
  86. bindingContainer = selectedElements[1];
  87. }
  88. if (
  89. textElement &&
  90. bindingContainer &&
  91. getBoundTextElement(bindingContainer) === null
  92. ) {
  93. return true;
  94. }
  95. }
  96. return false;
  97. },
  98. perform: (elements, appState) => {
  99. const selectedElements = getSelectedElements(
  100. getNonDeletedElements(elements),
  101. appState,
  102. );
  103. let textElement: ExcalidrawTextElement;
  104. let container: ExcalidrawTextContainer;
  105. if (
  106. isTextElement(selectedElements[0]) &&
  107. isTextBindableContainer(selectedElements[1])
  108. ) {
  109. textElement = selectedElements[0];
  110. container = selectedElements[1];
  111. } else {
  112. textElement = selectedElements[1] as ExcalidrawTextElement;
  113. container = selectedElements[0] as ExcalidrawTextContainer;
  114. }
  115. mutateElement(textElement, {
  116. containerId: container.id,
  117. verticalAlign: VERTICAL_ALIGN.MIDDLE,
  118. });
  119. mutateElement(container, {
  120. boundElements: (container.boundElements || []).concat({
  121. type: "text",
  122. id: textElement.id,
  123. }),
  124. });
  125. redrawTextBoundingBox(textElement, container);
  126. const updatedElements = elements.slice();
  127. const textElementIndex = updatedElements.findIndex(
  128. (ele) => ele.id === textElement.id,
  129. );
  130. updatedElements.splice(textElementIndex, 1);
  131. const containerIndex = updatedElements.findIndex(
  132. (ele) => ele.id === container.id,
  133. );
  134. updatedElements.splice(containerIndex + 1, 0, textElement);
  135. return {
  136. elements: updatedElements,
  137. appState: { ...appState, selectedElementIds: { [container.id]: true } },
  138. commitToHistory: true,
  139. };
  140. },
  141. });