newElement.ts 3.8 KB

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