flow-virtual-tree.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. import { type Disposable, Emitter } from '@flowgram.ai/utils';
  2. /**
  3. * 存储节点的 tree 结构信息
  4. * 策略是 "重修改轻查询",即修改时候做的事情更多,查询都通过指针来操作
  5. */
  6. export class FlowVirtualTree<T extends { id: string }> implements Disposable {
  7. protected onTreeChangeEmitter = new Emitter<void>();
  8. /**
  9. * tree 结构变化时候触发
  10. */
  11. onTreeChange = this.onTreeChangeEmitter.event;
  12. protected map: Map<T, FlowVirtualTree.NodeInfo<T>> = new Map();
  13. constructor(readonly root: T) {}
  14. dispose() {
  15. this.map.clear();
  16. this.onTreeChangeEmitter.dispose();
  17. }
  18. getInfo(node: T): FlowVirtualTree.NodeInfo<T> {
  19. let res: FlowVirtualTree.NodeInfo<T> | undefined = this.map.get(node);
  20. if (!res) {
  21. res = { children: [] };
  22. this.map.set(node, res);
  23. }
  24. return res;
  25. }
  26. clear(): void {
  27. this.map.clear();
  28. }
  29. cloneMap(): Map<T, FlowVirtualTree.NodeInfo<T>> {
  30. const newMap: Map<T, FlowVirtualTree.NodeInfo<T>> = new Map();
  31. for (const [key, value] of this.map) {
  32. newMap.set(key, {
  33. ...value,
  34. children: value.children.slice(),
  35. });
  36. }
  37. return newMap;
  38. }
  39. clone(): FlowVirtualTree<T> {
  40. const newTree = new FlowVirtualTree<T>(this.root);
  41. newTree.map = this.cloneMap();
  42. return newTree;
  43. }
  44. remove(node: T, withChildren = true): void {
  45. this.removeParent(node);
  46. if (withChildren) {
  47. this._removeChildren(node);
  48. }
  49. this.map.delete(node);
  50. this.fireTreeChange();
  51. }
  52. addChild(parent: T, child: T, index?: number): T {
  53. const parentInfo = this.getInfo(parent);
  54. const childInfo = this.getInfo(child);
  55. if (childInfo.parent) {
  56. if (childInfo.parent === parent) return child;
  57. if (childInfo.parent !== parent) {
  58. this.removeParent(child);
  59. }
  60. }
  61. const len = parentInfo.children.length;
  62. const idx = typeof index === 'undefined' ? len - 1 : index - 1;
  63. const lastChild = parentInfo.children[idx];
  64. const nextChild = parentInfo.children[idx + 1];
  65. if (lastChild) this.getInfo(lastChild).next = child;
  66. if (nextChild) this.getInfo(nextChild).pre = child;
  67. childInfo.pre = lastChild;
  68. childInfo.next = nextChild;
  69. parentInfo.children.splice(idx + 1, 0, child);
  70. childInfo.parent = parent;
  71. this.fireTreeChange();
  72. return child;
  73. }
  74. moveChilds(parent: T, childs: T[], index?: number): T[] {
  75. const parentInfo = this.getInfo(parent);
  76. const len = parentInfo.children.length;
  77. let childIndex: number = index ?? len;
  78. childs.forEach((child) => {
  79. const childInfo = this.getInfo(child);
  80. if (childInfo.parent) {
  81. this.removeParent(child);
  82. }
  83. });
  84. childs.forEach((child) => {
  85. const childInfo = this.getInfo(child);
  86. let lastChild: T | undefined = parentInfo.children[childIndex - 1];
  87. let nextChild: T | undefined = parentInfo.children[childIndex];
  88. if (lastChild) this.getInfo(lastChild).next = child;
  89. if (nextChild) this.getInfo(nextChild).pre = child;
  90. childInfo.pre = lastChild;
  91. childInfo.next = nextChild;
  92. parentInfo.children.splice(childIndex, 0, child);
  93. childInfo.parent = parent;
  94. childIndex++;
  95. });
  96. this.fireTreeChange();
  97. return childs;
  98. }
  99. getById(id: string): T | undefined {
  100. for (const node of this.map.keys()) {
  101. if (node.id === id) return node;
  102. }
  103. }
  104. /**
  105. * 插入节点到后边
  106. * @param before
  107. * @param after
  108. */
  109. insertAfter(before: T, after: T) {
  110. const beforeInfo = this.getInfo(before);
  111. const afterInfo = this.getInfo(after);
  112. this.removeParent(after);
  113. if (beforeInfo.parent) {
  114. const parentInfo = this.getInfo(beforeInfo.parent);
  115. parentInfo.children.splice(parentInfo.children.indexOf(before) + 1, 0, after);
  116. const { next } = beforeInfo;
  117. if (next) {
  118. this.getInfo(next).pre = after;
  119. }
  120. afterInfo.next = next;
  121. beforeInfo.next = after;
  122. afterInfo.pre = before;
  123. afterInfo.parent = beforeInfo.parent;
  124. }
  125. this.fireTreeChange();
  126. }
  127. removeParent(node: T): void {
  128. const info = this.getInfo(node);
  129. if (!info.parent) return;
  130. const parentInfo = this.getInfo(info.parent);
  131. const index = parentInfo.children.indexOf(node);
  132. parentInfo.children.splice(index, 1);
  133. const { pre, next } = info;
  134. if (pre) this.getInfo(pre).next = next;
  135. if (next) this.getInfo(next).pre = pre;
  136. this.fireTreeChange();
  137. }
  138. private _removeChildren(node: T): void {
  139. // 删除子节点
  140. const children = this.getChildren(node);
  141. if (children.length > 0) {
  142. children.forEach((child) => {
  143. this._removeChildren(child);
  144. this.map.delete(child);
  145. });
  146. }
  147. }
  148. getParent(node: T): T | undefined {
  149. return this.getInfo(node).parent;
  150. }
  151. getPre(node: T): T | undefined {
  152. return this.getInfo(node).pre;
  153. }
  154. getNext(node: T): T | undefined {
  155. return this.getInfo(node).next;
  156. }
  157. getChildren(node: T): T[] {
  158. return this.getInfo(node).children;
  159. }
  160. traverse(
  161. fn: (node: T, depth: number, index: number) => boolean | void,
  162. node = this.root,
  163. depth = 0,
  164. index = 0
  165. ): boolean | void {
  166. const breaked = fn(node, depth, index);
  167. if (breaked) return true;
  168. const info = this.getInfo(node);
  169. const shouldBreak = info.children.find((child, i) => this.traverse(fn, child, depth + 1, i));
  170. if (shouldBreak) return true;
  171. }
  172. /**
  173. * 通知文档树结构更新
  174. */
  175. fireTreeChange(): void {
  176. this.onTreeChangeEmitter.fire();
  177. }
  178. get size(): number {
  179. return this.map.size;
  180. }
  181. toString(): string {
  182. const ret: string[] = [];
  183. this.traverse((node, depth) => {
  184. if (depth === 0) {
  185. ret.push(node.id);
  186. } else {
  187. ret.push(`|${new Array(depth).fill('--').join('')} ${node.id}`);
  188. }
  189. });
  190. return `${ret.join('\n')}`;
  191. }
  192. }
  193. export namespace FlowVirtualTree {
  194. export interface NodeInfo<T> {
  195. parent?: T;
  196. next?: T;
  197. pre?: T;
  198. children: T[];
  199. }
  200. }