mutateElement.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. import { ExcalidrawElement } from "./types";
  2. import { invalidateShapeForElement } from "../renderer/renderElement";
  3. import Scene from "../scene/Scene";
  4. import { getSizeFromPoints } from "../points";
  5. import { randomInteger } from "../random";
  6. import { Point } from "../types";
  7. import { getUpdatedTimestamp } from "../utils";
  8. type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
  9. Partial<TElement>,
  10. "id" | "version" | "versionNonce"
  11. >;
  12. // This function tracks updates of text elements for the purposes for collaboration.
  13. // The version is used to compare updates when more than one user is working in
  14. // the same drawing. Note: this will trigger the component to update. Make sure you
  15. // are calling it either from a React event handler or within unstable_batchedUpdates().
  16. export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
  17. element: TElement,
  18. updates: ElementUpdate<TElement>,
  19. informMutation = true,
  20. ): TElement => {
  21. let didChange = false;
  22. // casting to any because can't use `in` operator
  23. // (see https://github.com/microsoft/TypeScript/issues/21732)
  24. const { points, fileId } = updates as any;
  25. if (typeof points !== "undefined") {
  26. updates = { ...getSizeFromPoints(points), ...updates };
  27. }
  28. for (const key in updates) {
  29. const value = (updates as any)[key];
  30. if (typeof value !== "undefined") {
  31. if (
  32. (element as any)[key] === value &&
  33. // if object, always update because its attrs could have changed
  34. // (except for specific keys we handle below)
  35. (typeof value !== "object" ||
  36. value === null ||
  37. key === "groupIds" ||
  38. key === "scale")
  39. ) {
  40. continue;
  41. }
  42. if (key === "scale") {
  43. const prevScale = (element as any)[key];
  44. const nextScale = value;
  45. if (prevScale[0] === nextScale[0] && prevScale[1] === nextScale[1]) {
  46. continue;
  47. }
  48. } else if (key === "points") {
  49. const prevPoints = (element as any)[key];
  50. const nextPoints = value;
  51. if (prevPoints.length === nextPoints.length) {
  52. let didChangePoints = false;
  53. let index = prevPoints.length;
  54. while (--index) {
  55. const prevPoint: Point = prevPoints[index];
  56. const nextPoint: Point = nextPoints[index];
  57. if (
  58. prevPoint[0] !== nextPoint[0] ||
  59. prevPoint[1] !== nextPoint[1]
  60. ) {
  61. didChangePoints = true;
  62. break;
  63. }
  64. }
  65. if (!didChangePoints) {
  66. continue;
  67. }
  68. }
  69. }
  70. (element as any)[key] = value;
  71. didChange = true;
  72. }
  73. }
  74. if (!didChange) {
  75. return element;
  76. }
  77. if (
  78. typeof updates.height !== "undefined" ||
  79. typeof updates.width !== "undefined" ||
  80. typeof fileId != "undefined" ||
  81. typeof points !== "undefined"
  82. ) {
  83. invalidateShapeForElement(element);
  84. }
  85. element.version++;
  86. element.versionNonce = randomInteger();
  87. element.updated = getUpdatedTimestamp();
  88. if (informMutation) {
  89. Scene.getScene(element)?.informMutation();
  90. }
  91. return element;
  92. };
  93. export const newElementWith = <TElement extends ExcalidrawElement>(
  94. element: TElement,
  95. updates: ElementUpdate<TElement>,
  96. ): TElement => {
  97. let didChange = false;
  98. for (const key in updates) {
  99. const value = (updates as any)[key];
  100. if (typeof value !== "undefined") {
  101. if (
  102. (element as any)[key] === value &&
  103. // if object, always update because its attrs could have changed
  104. (typeof value !== "object" || value === null)
  105. ) {
  106. continue;
  107. }
  108. didChange = true;
  109. }
  110. }
  111. if (!didChange) {
  112. return element;
  113. }
  114. return {
  115. ...element,
  116. ...updates,
  117. updated: getUpdatedTimestamp(),
  118. version: element.version + 1,
  119. versionNonce: randomInteger(),
  120. };
  121. };
  122. /**
  123. * Mutates element, bumping `version`, `versionNonce`, and `updated`.
  124. *
  125. * NOTE: does not trigger re-render.
  126. */
  127. export const bumpVersion = (
  128. element: Mutable<ExcalidrawElement>,
  129. version?: ExcalidrawElement["version"],
  130. ) => {
  131. element.version = (version ?? element.version) + 1;
  132. element.versionNonce = randomInteger();
  133. element.updated = getUpdatedTimestamp();
  134. return element;
  135. };