mutateElement.ts 4.2 KB

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