VirtualizedTree.tsx 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import {TreeLike} from 'sentry/utils/profiling/hooks/useVirtualizedTree/useVirtualizedTree';
  2. import {VirtualizedTreeNode} from './VirtualizedTreeNode';
  3. export class VirtualizedTree<T extends TreeLike> {
  4. roots: VirtualizedTreeNode<T>[] = [];
  5. flattened: VirtualizedTreeNode<T>[] = [];
  6. constructor(roots: VirtualizedTreeNode<T>[], flattenedList?: VirtualizedTreeNode<T>[]) {
  7. this.roots = roots;
  8. this.flattened = flattenedList || VirtualizedTree.toExpandedList(this.roots);
  9. }
  10. // Rebuilds the tree
  11. static fromRoots<T extends TreeLike>(
  12. items: T[],
  13. expanded?: boolean,
  14. skipFn: (n: VirtualizedTreeNode<T>) => boolean = () => false,
  15. // If we are selecting a sub-root of the tree and the user
  16. // has previously expended some of the children, we use this
  17. // to carry-them over and preserver their state.
  18. expandedNodes?: Set<T>
  19. ): VirtualizedTree<T> {
  20. const roots: VirtualizedTreeNode<T>[] = [];
  21. function toTreeNode(
  22. node: T,
  23. parent: VirtualizedTreeNode<T> | null,
  24. collection: VirtualizedTreeNode<T>[] | null,
  25. depth: number
  26. ) {
  27. const shouldUseExpandedSet = expandedNodes && expandedNodes.size > 0;
  28. const treeNode = new VirtualizedTreeNode<T>(
  29. node,
  30. parent,
  31. depth,
  32. shouldUseExpandedSet ? expandedNodes.has(node) : expanded
  33. );
  34. // We cannot skip root nodes, so we check that the parent is not null.
  35. // If the node should be skipped, then we don't add it to the tree and descend
  36. // into its children without incrementing the depth.
  37. if (parent && skipFn(treeNode)) {
  38. for (let i = 0; i < node.children.length; i++) {
  39. toTreeNode(node.children[i] as T, treeNode, parent.children, depth);
  40. }
  41. return;
  42. }
  43. if (collection) {
  44. collection.push(treeNode);
  45. }
  46. for (let i = 0; i < node.children.length; i++) {
  47. toTreeNode(node.children[i] as T, treeNode, treeNode.children, depth + 1);
  48. }
  49. }
  50. for (let i = 0; i < items.length; i++) {
  51. toTreeNode(items[i], null, roots, 0);
  52. }
  53. return new VirtualizedTree<T>(roots, undefined);
  54. }
  55. // Returns a list of nodes that are visible in the tree.
  56. static toExpandedList<T extends TreeLike>(
  57. nodes: VirtualizedTreeNode<T>[]
  58. ): VirtualizedTreeNode<T>[] {
  59. const list: VirtualizedTreeNode<T>[] = [];
  60. function visit(node: VirtualizedTreeNode<T>): void {
  61. list.push(node);
  62. if (!node.expanded) {
  63. return;
  64. }
  65. for (let i = 0; i < node.children.length; i++) {
  66. visit(node.children[i]);
  67. }
  68. }
  69. for (let i = 0; i < nodes.length; i++) {
  70. visit(nodes[i]);
  71. }
  72. return list;
  73. }
  74. expandNode(
  75. node: VirtualizedTreeNode<T>,
  76. value: boolean,
  77. opts?: {expandChildren: boolean}
  78. ) {
  79. // Because node.setExpanded handles toggling the node and all it's children, we still need to update the
  80. // flattened list. To do that w/o having to rebuild the entire tree, we can just remove the node and add them
  81. const removedOrAddedNodes = node.setExpanded(value, opts);
  82. // If toggling the node resulted in no changes to the actual tree, do nothing
  83. if (!removedOrAddedNodes.length) {
  84. return removedOrAddedNodes;
  85. }
  86. // If a node was expanded, we need to add all of its children to the flattened list.
  87. if (node.expanded) {
  88. this.flattened.splice(this.flattened.indexOf(node) + 1, 0, ...removedOrAddedNodes);
  89. } else {
  90. // If a node was collapsed, we need to remove all of its children from the flattened list.
  91. this.flattened.splice(this.flattened.indexOf(node) + 1, removedOrAddedNodes.length);
  92. }
  93. return removedOrAddedNodes;
  94. }
  95. // Sorts the entire tree and rebuilds the flattened list.
  96. sort(sortFn: (a: VirtualizedTreeNode<T>, b: VirtualizedTreeNode<T>) => number) {
  97. if (!this.roots.length) {
  98. return;
  99. }
  100. function visit(node: VirtualizedTreeNode<T>) {
  101. const sortedChildren = node.children.sort(sortFn);
  102. for (let i = 0; i < sortedChildren.length; i++) {
  103. visit(sortedChildren[i]);
  104. }
  105. }
  106. const sortedRoots = this.roots.sort(sortFn);
  107. for (let i = 0; i < sortedRoots.length; i++) {
  108. visit(sortedRoots[i]);
  109. }
  110. this.flattened = VirtualizedTree.toExpandedList(this.roots);
  111. }
  112. getAllExpandedNodes(previouslyExpandedNodes: Set<T>): Set<T> {
  113. const expandedNodes = new Set<T>([...previouslyExpandedNodes]);
  114. function visit(node: VirtualizedTreeNode<T>) {
  115. if (node.expanded) {
  116. expandedNodes.add(node.node);
  117. }
  118. for (let i = 0; i < node.children.length; i++) {
  119. visit(node.children[i]);
  120. }
  121. }
  122. for (let i = 0; i < this.roots.length; i++) {
  123. visit(this.roots[i]);
  124. }
  125. return expandedNodes;
  126. }
  127. }