newElement.ts 3.8 KB

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