sortElements.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { arrayToMapWithIndex } from "../utils";
  2. import { ExcalidrawElement } from "./types";
  3. const normalizeGroupElementOrder = (elements: readonly ExcalidrawElement[]) => {
  4. const origElements: ExcalidrawElement[] = elements.slice();
  5. const sortedElements = new Set<ExcalidrawElement>();
  6. const orderInnerGroups = (
  7. elements: readonly ExcalidrawElement[],
  8. ): ExcalidrawElement[] => {
  9. const firstGroupSig = elements[0]?.groupIds?.join("");
  10. const aGroup: ExcalidrawElement[] = [elements[0]];
  11. const bGroup: ExcalidrawElement[] = [];
  12. for (const element of elements.slice(1)) {
  13. if (element.groupIds?.join("") === firstGroupSig) {
  14. aGroup.push(element);
  15. } else {
  16. bGroup.push(element);
  17. }
  18. }
  19. return bGroup.length ? [...aGroup, ...orderInnerGroups(bGroup)] : aGroup;
  20. };
  21. const groupHandledElements = new Map<string, true>();
  22. origElements.forEach((element, idx) => {
  23. if (groupHandledElements.has(element.id)) {
  24. return;
  25. }
  26. if (element.groupIds?.length) {
  27. const topGroup = element.groupIds[element.groupIds.length - 1];
  28. const groupElements = origElements.slice(idx).filter((element) => {
  29. const ret = element?.groupIds?.some((id) => id === topGroup);
  30. if (ret) {
  31. groupHandledElements.set(element!.id, true);
  32. }
  33. return ret;
  34. });
  35. for (const elem of orderInnerGroups(groupElements)) {
  36. sortedElements.add(elem);
  37. }
  38. } else {
  39. sortedElements.add(element);
  40. }
  41. });
  42. // if there's a bug which resulted in losing some of the elements, return
  43. // original instead as that's better than losing data
  44. if (sortedElements.size !== elements.length) {
  45. console.error("normalizeGroupElementOrder: lost some elements... bailing!");
  46. return elements;
  47. }
  48. return [...sortedElements];
  49. };
  50. /**
  51. * In theory, when we have text elements bound to a container, they
  52. * should be right after the container element in the elements array.
  53. * However, this is not guaranteed due to old and potential future bugs.
  54. *
  55. * This function sorts containers and their bound texts together. It prefers
  56. * original z-index of container (i.e. it moves bound text elements after
  57. * containers).
  58. */
  59. const normalizeBoundElementsOrder = (
  60. elements: readonly ExcalidrawElement[],
  61. ) => {
  62. const elementsMap = arrayToMapWithIndex(elements);
  63. const origElements: (ExcalidrawElement | null)[] = elements.slice();
  64. const sortedElements = new Set<ExcalidrawElement>();
  65. origElements.forEach((element, idx) => {
  66. if (!element) {
  67. return;
  68. }
  69. if (element.boundElements?.length) {
  70. sortedElements.add(element);
  71. origElements[idx] = null;
  72. element.boundElements.forEach((boundElement) => {
  73. const child = elementsMap.get(boundElement.id);
  74. if (child && boundElement.type === "text") {
  75. sortedElements.add(child[0]);
  76. origElements[child[1]] = null;
  77. }
  78. });
  79. } else if (element.type === "text" && element.containerId) {
  80. const parent = elementsMap.get(element.containerId);
  81. if (!parent?.[0].boundElements?.find((x) => x.id === element.id)) {
  82. sortedElements.add(element);
  83. origElements[idx] = null;
  84. // if element has a container and container lists it, skip this element
  85. // as it'll be taken care of by the container
  86. }
  87. } else {
  88. sortedElements.add(element);
  89. origElements[idx] = null;
  90. }
  91. });
  92. // if there's a bug which resulted in losing some of the elements, return
  93. // original instead as that's better than losing data
  94. if (sortedElements.size !== elements.length) {
  95. console.error(
  96. "normalizeBoundElementsOrder: lost some elements... bailing!",
  97. );
  98. return elements;
  99. }
  100. return [...sortedElements];
  101. };
  102. export const normalizeElementOrder = (
  103. elements: readonly ExcalidrawElement[],
  104. ) => {
  105. // console.time();
  106. const ret = normalizeBoundElementsOrder(normalizeGroupElementOrder(elements));
  107. // console.timeEnd();
  108. return ret;
  109. };