actionStyles.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import {
  2. isTextElement,
  3. isExcalidrawElement,
  4. redrawTextBoundingBox,
  5. } from "../element";
  6. import { CODES, KEYS } from "../keys";
  7. import { t } from "../i18n";
  8. import { register } from "./register";
  9. import { newElementWith } from "../element/mutateElement";
  10. import {
  11. DEFAULT_FONT_SIZE,
  12. DEFAULT_FONT_FAMILY,
  13. DEFAULT_TEXT_ALIGN,
  14. } from "../constants";
  15. import { getBoundTextElement } from "../element/textElement";
  16. import {
  17. hasBoundTextElement,
  18. canApplyRoundnessTypeToElement,
  19. getDefaultRoundnessTypeForElement,
  20. } from "../element/typeChecks";
  21. import { getSelectedElements } from "../scene";
  22. // `copiedStyles` is exported only for tests.
  23. export let copiedStyles: string = "{}";
  24. export const actionCopyStyles = register({
  25. name: "copyStyles",
  26. trackEvent: { category: "element" },
  27. perform: (elements, appState) => {
  28. const elementsCopied = [];
  29. const element = elements.find((el) => appState.selectedElementIds[el.id]);
  30. elementsCopied.push(element);
  31. if (element && hasBoundTextElement(element)) {
  32. const boundTextElement = getBoundTextElement(element);
  33. elementsCopied.push(boundTextElement);
  34. }
  35. if (element) {
  36. copiedStyles = JSON.stringify(elementsCopied);
  37. }
  38. return {
  39. appState: {
  40. ...appState,
  41. toast: { message: t("toast.copyStyles") },
  42. },
  43. commitToHistory: false,
  44. };
  45. },
  46. contextItemLabel: "labels.copyStyles",
  47. keyTest: (event) =>
  48. event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.C,
  49. });
  50. export const actionPasteStyles = register({
  51. name: "pasteStyles",
  52. trackEvent: { category: "element" },
  53. perform: (elements, appState) => {
  54. const elementsCopied = JSON.parse(copiedStyles);
  55. const pastedElement = elementsCopied[0];
  56. const boundTextElement = elementsCopied[1];
  57. if (!isExcalidrawElement(pastedElement)) {
  58. return { elements, commitToHistory: false };
  59. }
  60. const selectedElements = getSelectedElements(elements, appState, true);
  61. const selectedElementIds = selectedElements.map((element) => element.id);
  62. return {
  63. elements: elements.map((element) => {
  64. if (selectedElementIds.includes(element.id)) {
  65. let elementStylesToCopyFrom = pastedElement;
  66. if (isTextElement(element) && element.containerId) {
  67. elementStylesToCopyFrom = boundTextElement;
  68. }
  69. if (!elementStylesToCopyFrom) {
  70. return element;
  71. }
  72. let newElement = newElementWith(element, {
  73. backgroundColor: elementStylesToCopyFrom?.backgroundColor,
  74. strokeWidth: elementStylesToCopyFrom?.strokeWidth,
  75. strokeColor: elementStylesToCopyFrom?.strokeColor,
  76. strokeStyle: elementStylesToCopyFrom?.strokeStyle,
  77. fillStyle: elementStylesToCopyFrom?.fillStyle,
  78. opacity: elementStylesToCopyFrom?.opacity,
  79. roughness: elementStylesToCopyFrom?.roughness,
  80. roundness: elementStylesToCopyFrom.roundness
  81. ? canApplyRoundnessTypeToElement(
  82. elementStylesToCopyFrom.roundness.type,
  83. element,
  84. )
  85. ? elementStylesToCopyFrom.roundness
  86. : getDefaultRoundnessTypeForElement(element)
  87. : null,
  88. });
  89. if (isTextElement(newElement)) {
  90. newElement = newElementWith(newElement, {
  91. fontSize: elementStylesToCopyFrom?.fontSize || DEFAULT_FONT_SIZE,
  92. fontFamily:
  93. elementStylesToCopyFrom?.fontFamily || DEFAULT_FONT_FAMILY,
  94. textAlign:
  95. elementStylesToCopyFrom?.textAlign || DEFAULT_TEXT_ALIGN,
  96. });
  97. let container = null;
  98. if (newElement.containerId) {
  99. container =
  100. selectedElements.find(
  101. (element) =>
  102. isTextElement(newElement) &&
  103. element.id === newElement.containerId,
  104. ) || null;
  105. }
  106. redrawTextBoundingBox(newElement, container);
  107. }
  108. if (newElement.type === "arrow") {
  109. newElement = newElementWith(newElement, {
  110. startArrowhead: elementStylesToCopyFrom.startArrowhead,
  111. endArrowhead: elementStylesToCopyFrom.endArrowhead,
  112. });
  113. }
  114. return newElement;
  115. }
  116. return element;
  117. }),
  118. commitToHistory: true,
  119. };
  120. },
  121. contextItemLabel: "labels.pasteStyles",
  122. keyTest: (event) =>
  123. event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === CODES.V,
  124. });