actionBoundText.tsx 4.1 KB

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