newElement.ts 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. import {
  2. ExcalidrawElement,
  3. ExcalidrawTextElement,
  4. ExcalidrawLinearElement,
  5. ExcalidrawGenericElement,
  6. NonDeleted,
  7. TextAlign,
  8. } from "../element/types";
  9. import { measureText } from "../utils";
  10. import { randomInteger, randomId } from "../random";
  11. import { newElementWith } from "./mutateElement";
  12. type ElementConstructorOpts = {
  13. x: ExcalidrawGenericElement["x"];
  14. y: ExcalidrawGenericElement["y"];
  15. strokeColor: ExcalidrawGenericElement["strokeColor"];
  16. backgroundColor: ExcalidrawGenericElement["backgroundColor"];
  17. fillStyle: ExcalidrawGenericElement["fillStyle"];
  18. strokeWidth: ExcalidrawGenericElement["strokeWidth"];
  19. strokeStyle: ExcalidrawGenericElement["strokeStyle"];
  20. roughness: ExcalidrawGenericElement["roughness"];
  21. opacity: ExcalidrawGenericElement["opacity"];
  22. width?: ExcalidrawGenericElement["width"];
  23. height?: ExcalidrawGenericElement["height"];
  24. angle?: ExcalidrawGenericElement["angle"];
  25. };
  26. const _newElementBase = <T extends ExcalidrawElement>(
  27. type: T["type"],
  28. {
  29. x,
  30. y,
  31. strokeColor,
  32. backgroundColor,
  33. fillStyle,
  34. strokeWidth,
  35. strokeStyle,
  36. roughness,
  37. opacity,
  38. width = 0,
  39. height = 0,
  40. angle = 0,
  41. ...rest
  42. }: ElementConstructorOpts & Partial<ExcalidrawGenericElement>,
  43. ) => ({
  44. id: rest.id || randomId(),
  45. type,
  46. x,
  47. y,
  48. width,
  49. height,
  50. angle,
  51. strokeColor,
  52. backgroundColor,
  53. fillStyle,
  54. strokeWidth,
  55. strokeStyle,
  56. roughness,
  57. opacity,
  58. seed: rest.seed ?? randomInteger(),
  59. version: rest.version || 1,
  60. versionNonce: rest.versionNonce ?? 0,
  61. isDeleted: false as false,
  62. });
  63. export const newElement = (
  64. opts: {
  65. type: ExcalidrawGenericElement["type"];
  66. } & ElementConstructorOpts,
  67. ): NonDeleted<ExcalidrawGenericElement> =>
  68. _newElementBase<ExcalidrawGenericElement>(opts.type, opts);
  69. export const newTextElement = (
  70. opts: {
  71. text: string;
  72. font: string;
  73. textAlign: TextAlign;
  74. } & ElementConstructorOpts,
  75. ): NonDeleted<ExcalidrawTextElement> => {
  76. const metrics = measureText(opts.text, opts.font);
  77. const textElement = newElementWith(
  78. {
  79. ..._newElementBase<ExcalidrawTextElement>("text", opts),
  80. text: opts.text,
  81. font: opts.font,
  82. textAlign: opts.textAlign,
  83. // Center the text
  84. x: opts.x - metrics.width / 2,
  85. y: opts.y - metrics.height / 2,
  86. width: metrics.width,
  87. height: metrics.height,
  88. baseline: metrics.baseline,
  89. },
  90. {},
  91. );
  92. return textElement;
  93. };
  94. export const newLinearElement = (
  95. opts: {
  96. type: ExcalidrawLinearElement["type"];
  97. lastCommittedPoint?: ExcalidrawLinearElement["lastCommittedPoint"];
  98. } & ElementConstructorOpts,
  99. ): NonDeleted<ExcalidrawLinearElement> => {
  100. return {
  101. ..._newElementBase<ExcalidrawLinearElement>(opts.type, opts),
  102. points: [],
  103. lastCommittedPoint: opts.lastCommittedPoint || null,
  104. };
  105. };
  106. // Simplified deep clone for the purpose of cloning ExcalidrawElement only
  107. // (doesn't clone Date, RegExp, Map, Set, Typed arrays etc.)
  108. //
  109. // Adapted from https://github.com/lukeed/klona
  110. export const deepCopyElement = (val: any, depth: number = 0) => {
  111. if (val == null || typeof val !== "object") {
  112. return val;
  113. }
  114. if (Object.prototype.toString.call(val) === "[object Object]") {
  115. const tmp =
  116. typeof val.constructor === "function"
  117. ? Object.create(Object.getPrototypeOf(val))
  118. : {};
  119. for (const key in val) {
  120. if (val.hasOwnProperty(key)) {
  121. // don't copy top-level shape property, which we want to regenerate
  122. if (depth === 0 && (key === "shape" || key === "canvas")) {
  123. continue;
  124. }
  125. tmp[key] = deepCopyElement(val[key], depth + 1);
  126. }
  127. }
  128. return tmp;
  129. }
  130. if (Array.isArray(val)) {
  131. let k = val.length;
  132. const arr = new Array(k);
  133. while (k--) {
  134. arr[k] = deepCopyElement(val[k], depth + 1);
  135. }
  136. return arr;
  137. }
  138. return val;
  139. };
  140. export const duplicateElement = <TElement extends Mutable<ExcalidrawElement>>(
  141. element: TElement,
  142. overrides?: Partial<TElement>,
  143. ): TElement => {
  144. let copy: TElement = deepCopyElement(element);
  145. copy.id = randomId();
  146. copy.seed = randomInteger();
  147. if (overrides) {
  148. copy = Object.assign(copy, overrides);
  149. }
  150. return copy;
  151. };