mutateElement.ts 2.8 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  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. type ElementUpdate<TElement extends ExcalidrawElement> = Omit<
  8. Partial<TElement>,
  9. "id" | "version" | "versionNonce"
  10. >;
  11. // This function tracks updates of text elements for the purposes for collaboration.
  12. // The version is used to compare updates when more than one user is working in
  13. // the same drawing. Note: this will trigger the component to update. Make sure you
  14. // are calling it either from a React event handler or within unstable_batchedUpdates().
  15. export const mutateElement = <TElement extends Mutable<ExcalidrawElement>>(
  16. element: TElement,
  17. updates: ElementUpdate<TElement>,
  18. ) => {
  19. let didChange = false;
  20. // casting to any because can't use `in` operator
  21. // (see https://github.com/microsoft/TypeScript/issues/21732)
  22. const { points } = updates as any;
  23. if (typeof points !== "undefined") {
  24. updates = { ...getSizeFromPoints(points), ...updates };
  25. }
  26. for (const key in updates) {
  27. const value = (updates as any)[key];
  28. if (typeof value !== "undefined") {
  29. if (
  30. (element as any)[key] === value &&
  31. // if object, always update in case its deep prop was mutated
  32. (typeof value !== "object" || value === null || key === "groupIds")
  33. ) {
  34. continue;
  35. }
  36. if (key === "points") {
  37. const prevPoints = (element as any)[key];
  38. const nextPoints = value;
  39. if (prevPoints.length === nextPoints.length) {
  40. let didChangePoints = false;
  41. let index = prevPoints.length;
  42. while (--index) {
  43. const prevPoint: Point = prevPoints[index];
  44. const nextPoint: Point = nextPoints[index];
  45. if (
  46. prevPoint[0] !== nextPoint[0] ||
  47. prevPoint[1] !== nextPoint[1]
  48. ) {
  49. didChangePoints = true;
  50. break;
  51. }
  52. }
  53. if (!didChangePoints) {
  54. continue;
  55. }
  56. }
  57. }
  58. (element as any)[key] = value;
  59. didChange = true;
  60. }
  61. }
  62. if (!didChange) {
  63. return;
  64. }
  65. if (
  66. typeof updates.height !== "undefined" ||
  67. typeof updates.width !== "undefined" ||
  68. typeof points !== "undefined"
  69. ) {
  70. invalidateShapeForElement(element);
  71. }
  72. element.version++;
  73. element.versionNonce = randomInteger();
  74. Scene.getScene(element)?.informMutation();
  75. };
  76. export const newElementWith = <TElement extends ExcalidrawElement>(
  77. element: TElement,
  78. updates: ElementUpdate<TElement>,
  79. ): TElement => ({
  80. ...element,
  81. ...updates,
  82. version: element.version + 1,
  83. versionNonce: randomInteger(),
  84. });