flow-document.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. import { omit } from 'lodash';
  2. import { inject, injectable, multiInject, optional, postConstruct } from 'inversify';
  3. import { type Disposable, Emitter } from '@flowgram.ai/utils';
  4. import { type EntityData, type EntityDataRegistry, EntityManager } from '@flowgram.ai/core';
  5. import {
  6. AddNodeData,
  7. DEFAULT_FLOW_NODE_META,
  8. type FlowDocumentJSON,
  9. FlowLayout,
  10. FlowLayoutDefault,
  11. FlowNodeBaseType,
  12. type FlowNodeJSON,
  13. FlowNodeRegistry,
  14. FlowNodeType,
  15. } from './typings';
  16. import { FlowVirtualTree } from './flow-virtual-tree';
  17. import { FlowRenderTree } from './flow-render-tree';
  18. import {
  19. ConstantKeys,
  20. FlowDocumentOptions,
  21. FlowDocumentOptionsDefault,
  22. } from './flow-document-options';
  23. import { FlowDocumentContribution } from './flow-document-contribution';
  24. import { FlowDocumentConfig } from './flow-document-config';
  25. import { FlowDocumentTransformerEntity, FlowNodeEntity, FlowRendererStateEntity } from './entities';
  26. export type FlowDocumentProvider = () => FlowDocument;
  27. export const FlowDocumentProvider = Symbol('FlowDocumentProvider');
  28. /**
  29. * 流程整个文档数据
  30. */
  31. @injectable()
  32. export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
  33. @inject(EntityManager) protected entityManager: EntityManager;
  34. @inject(FlowDocumentConfig) readonly config: FlowDocumentConfig;
  35. /**
  36. * 流程画布配置项
  37. */
  38. @inject(FlowDocumentOptions) @optional() public options: FlowDocumentOptions;
  39. @multiInject(FlowDocumentContribution)
  40. @optional()
  41. protected contributions: FlowDocumentContribution[] = [];
  42. protected registers = new Map<FlowNodeType, FlowNodeRegistry>();
  43. private nodeRegistryCache = new Map<string, any>();
  44. protected nodeDataRegistries: EntityDataRegistry[] = [];
  45. protected layouts: FlowLayout[] = [];
  46. protected currentLayoutKey: string = '';
  47. protected onNodeUpdateEmitter = new Emitter<{
  48. node: FlowNodeEntity;
  49. data: FlowNodeJSON;
  50. }>();
  51. protected onNodeCreateEmitter = new Emitter<{
  52. node: FlowNodeEntity;
  53. data: FlowNodeJSON;
  54. }>();
  55. protected onNodeDisposeEmitter = new Emitter<{
  56. node: FlowNodeEntity;
  57. }>();
  58. protected onLayoutChangeEmitter = new Emitter<FlowLayout>();
  59. readonly onNodeUpdate = this.onNodeUpdateEmitter.event;
  60. readonly onNodeCreate = this.onNodeCreateEmitter.event;
  61. readonly onNodeDispose = this.onNodeDisposeEmitter.event;
  62. readonly onLayoutChange = this.onLayoutChangeEmitter.event;
  63. root: FlowNodeEntity;
  64. /**
  65. * 原始的 tree 结构
  66. */
  67. originTree: FlowVirtualTree<FlowNodeEntity>;
  68. transformer: FlowDocumentTransformerEntity;
  69. /**
  70. * 渲染相关的全局轧辊台
  71. */
  72. renderState: FlowRendererStateEntity;
  73. /**
  74. * 渲染后的 tree 结构
  75. */
  76. renderTree: FlowRenderTree<FlowNodeEntity>;
  77. @postConstruct()
  78. init(): void {
  79. if (!this.options) this.options = FlowDocumentOptionsDefault;
  80. this.currentLayoutKey = this.options.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT;
  81. this.contributions.forEach((contrib) => contrib.registerDocument?.(this));
  82. this.root = this.addNode({ id: 'root', type: FlowNodeBaseType.ROOT });
  83. this.originTree = new FlowVirtualTree<FlowNodeEntity>(this.root);
  84. this.transformer = this.entityManager.createEntity<FlowDocumentTransformerEntity>(
  85. FlowDocumentTransformerEntity,
  86. { document: this }
  87. );
  88. this.renderState =
  89. this.entityManager.createEntity<FlowRendererStateEntity>(FlowRendererStateEntity);
  90. this.renderTree = new FlowRenderTree<FlowNodeEntity>(this.root, this.originTree, this);
  91. // 布局第一次加载时候触发一次
  92. this.layout.reload?.();
  93. }
  94. /**
  95. * 从数据初始化 O(n)
  96. * @param json
  97. */
  98. /**
  99. * 加载数据,可以被重载
  100. * @param json 文档数据更新
  101. * @param fireRender 是否要触发渲染,默认 true
  102. */
  103. fromJSON(json: FlowDocumentJSON | any, fireRender = true): void {
  104. // 清空 tree 数据 重新计算
  105. this.originTree.clear();
  106. this.renderTree.clear();
  107. // 暂停触发画布更新
  108. this.entityManager.changeEntityLocked = true;
  109. // 添加前的节点
  110. const oldNodes = this.entityManager.getEntities<FlowNodeEntity>(FlowNodeEntity);
  111. // 添加后的节点
  112. const newNodes: FlowNodeEntity[] = [this.root];
  113. this.addBlocksAsChildren(this.root, json.nodes || [], newNodes);
  114. // 删除无效的节点
  115. oldNodes.forEach((node) => {
  116. if (!newNodes.includes(node)) {
  117. node.dispose();
  118. }
  119. });
  120. this.entityManager.changeEntityLocked = false;
  121. this.transformer.loading = false;
  122. if (fireRender) this.fireRender();
  123. }
  124. get layout(): FlowLayout {
  125. const layout = this.layouts.find((layout) => layout.name == this.currentLayoutKey);
  126. if (!layout) {
  127. throw new Error(`Unknown flow layout: ${this.currentLayoutKey}`);
  128. }
  129. return layout;
  130. }
  131. async load(): Promise<void> {
  132. await Promise.all(this.contributions.map((c) => c.loadDocument?.(this)));
  133. }
  134. get loading(): boolean {
  135. return this.transformer.loading;
  136. }
  137. /**
  138. * 触发 render
  139. */
  140. fireRender(): void {
  141. if (this.transformer.isTreeDirty()) {
  142. this.entityManager.fireEntityChanged(FlowNodeEntity.type);
  143. this.entityManager.fireEntityChanged(FlowDocumentTransformerEntity.type);
  144. }
  145. }
  146. /**
  147. * 从指定节点的下一个节点新增
  148. * @param fromNode
  149. * @param json
  150. */
  151. addFromNode(fromNode: FlowNodeEntity | string, json: FlowNodeJSON): FlowNodeEntity {
  152. const node = typeof fromNode === 'string' ? this.getNode(fromNode)! : fromNode;
  153. this.entityManager.changeEntityLocked = true;
  154. const { parent } = node;
  155. const result = this.addNode({
  156. ...json,
  157. parent,
  158. // originParent,
  159. });
  160. this.originTree.insertAfter(node, result);
  161. this.entityManager.changeEntityLocked = false;
  162. this.entityManager.fireEntityChanged(FlowNodeEntity.type);
  163. return result;
  164. }
  165. removeNode(node: FlowNodeEntity | string) {
  166. if (typeof node === 'string') {
  167. this.getNode(node)?.dispose();
  168. } else {
  169. node.dispose();
  170. }
  171. }
  172. /**
  173. * 添加节点,如果节点已经存在则不会重复创建
  174. * @param data
  175. * @param addedNodes
  176. */
  177. addNode(
  178. data: AddNodeData,
  179. addedNodes?: FlowNodeEntity[],
  180. ignoreCreateEvent?: boolean
  181. ): FlowNodeEntity {
  182. const { id, type = 'block', originParent, parent, meta, hidden, index } = data;
  183. let node = this.getNode(id);
  184. let isNew = false;
  185. const register = this.getNodeRegistry(type, data.originParent);
  186. // node 类型变化则全部删除重新来
  187. if (node && node.flowNodeType !== data.type) {
  188. node.dispose();
  189. node = undefined;
  190. }
  191. if (!node) {
  192. const { dataRegistries } = register;
  193. node = this.entityManager.createEntity<FlowNodeEntity>(FlowNodeEntity, {
  194. id,
  195. document: this,
  196. flowNodeType: type,
  197. originParent,
  198. meta,
  199. });
  200. const datas = dataRegistries
  201. ? this.nodeDataRegistries.concat(...dataRegistries)
  202. : this.nodeDataRegistries;
  203. node.addInitializeData(datas);
  204. node.onDispose(() => this.onNodeDisposeEmitter.fire({ node: node! }));
  205. if (this.options.fromNodeJSON) {
  206. this.options.fromNodeJSON(node, data);
  207. }
  208. isNew = true;
  209. }
  210. // 初始化数据重制
  211. node.initData({
  212. originParent,
  213. parent,
  214. meta,
  215. hidden,
  216. index,
  217. });
  218. // 开始节点加到 root 里边
  219. if (node.isStart) {
  220. this.root.addChild(node);
  221. }
  222. this.onNodeUpdateEmitter.fire({ node, data });
  223. addedNodes?.push(node);
  224. // 自定义创建逻辑
  225. if (register.onCreate) {
  226. const extendNodes = register.onCreate(node, data);
  227. if (extendNodes && addedNodes) {
  228. addedNodes.push(...extendNodes);
  229. }
  230. } else if (data.blocks && data.blocks.length > 0) {
  231. // 兼容老的写法
  232. if (!data.blocks[0].type) {
  233. this.addInlineBlocks(node, data.blocks, addedNodes);
  234. } else {
  235. this.addBlocksAsChildren(node, data.blocks as FlowNodeJSON[], addedNodes);
  236. }
  237. }
  238. if (isNew && !ignoreCreateEvent) {
  239. this.onNodeCreateEmitter.fire({
  240. node,
  241. data,
  242. });
  243. }
  244. return node;
  245. }
  246. addBlocksAsChildren(
  247. parent: FlowNodeEntity,
  248. blocks: FlowNodeJSON[],
  249. addedNodes?: FlowNodeEntity[]
  250. ): void {
  251. for (const block of blocks) {
  252. this.addNode(
  253. {
  254. ...block,
  255. parent,
  256. },
  257. addedNodes
  258. );
  259. }
  260. }
  261. /**
  262. * block 格式:
  263. * node: (最原始的 id)
  264. * blockIcon
  265. * inlineBlocks
  266. * block
  267. * blockOrderIcon
  268. * block
  269. * blockOrderIcon
  270. * @param node
  271. * @param blocks
  272. * @param addedNodes
  273. */
  274. addInlineBlocks(
  275. node: FlowNodeEntity,
  276. blocks: FlowNodeJSON[],
  277. addedNodes: FlowNodeEntity[] = []
  278. ): FlowNodeEntity[] {
  279. // 块列表开始节点,用来展示块的按钮
  280. const blockIconNode = this.addNode({
  281. id: `$blockIcon$${node.id}`,
  282. type: FlowNodeBaseType.BLOCK_ICON,
  283. originParent: node,
  284. parent: node,
  285. });
  286. addedNodes.push(blockIconNode);
  287. // inlineblocks 为空则不创建
  288. if (blocks.length > 0) {
  289. // 水平布局
  290. const inlineBlocksNode = this.addNode({
  291. id: `$inlineBlocks$${node.id}`,
  292. type: FlowNodeBaseType.INLINE_BLOCKS,
  293. originParent: node,
  294. parent: node,
  295. });
  296. addedNodes.push(inlineBlocksNode);
  297. blocks.forEach((blockData) => {
  298. this.addBlock(node, blockData, addedNodes);
  299. });
  300. }
  301. return addedNodes;
  302. }
  303. /**
  304. * 添加单个 block
  305. * @param target
  306. * @param blockData
  307. * @param addedNodes
  308. * @param parent 默认去找 $inlineBlocks$
  309. */
  310. addBlock(
  311. target: FlowNodeEntity | string,
  312. blockData: FlowNodeJSON,
  313. addedNodes?: FlowNodeEntity[],
  314. parent?: FlowNodeEntity,
  315. index?: number
  316. ): FlowNodeEntity {
  317. const node: FlowNodeEntity = typeof target === 'string' ? this.getNode(target)! : target;
  318. const { onBlockChildCreate } = node.getNodeRegistry();
  319. if (onBlockChildCreate) {
  320. return onBlockChildCreate(node, blockData, addedNodes);
  321. }
  322. parent = parent || this.getNode(`$inlineBlocks$${node.id}`);
  323. // 块节点会生成一个空的 Block 节点用来切割 Block
  324. const block = this.addNode({
  325. ...omit(blockData, 'blocks'),
  326. type: blockData.type || FlowNodeBaseType.BLOCK,
  327. originParent: node,
  328. parent,
  329. index,
  330. });
  331. if (blockData.meta?.defaultCollapsed) {
  332. block.collapsed = true;
  333. }
  334. // 块开始节点
  335. const blockOrderIcon = this.addNode({
  336. id: `$blockOrderIcon$${blockData.id}`,
  337. type: FlowNodeBaseType.BLOCK_ORDER_ICON,
  338. originParent: node,
  339. meta: blockData.meta,
  340. data: blockData.data,
  341. parent: block,
  342. });
  343. addedNodes?.push(block, blockOrderIcon);
  344. if (blockData.blocks) {
  345. this.addBlocksAsChildren(block, blockData.blocks as FlowNodeJSON[], addedNodes);
  346. }
  347. return block;
  348. }
  349. /**
  350. * 根据 id 获取节点
  351. * @param id
  352. */
  353. getNode(id: string): FlowNodeEntity | undefined {
  354. if (!id) return undefined;
  355. return this.entityManager.getEntityById<FlowNodeEntity>(id);
  356. }
  357. /**
  358. * 注册节点
  359. * @param registries
  360. */
  361. registerFlowNodes<T extends FlowNodeRegistry<any>>(...registries: T[]): void {
  362. registries.forEach((newRegistry) => {
  363. if (!newRegistry) {
  364. throw new Error('[FlowDocument] registerFlowNodes parameters get undefined registry.');
  365. }
  366. const preRegistry = this.registers.get(newRegistry.type);
  367. this.registers.set(newRegistry.type, {
  368. ...preRegistry,
  369. ...newRegistry,
  370. meta: {
  371. ...preRegistry?.meta,
  372. ...newRegistry?.meta,
  373. },
  374. });
  375. });
  376. }
  377. /**
  378. * 导出数据,可以重载
  379. */
  380. toJSON(): T | any {
  381. return {
  382. nodes: this.root.toJSON().blocks,
  383. };
  384. }
  385. /**
  386. * @deprecated
  387. * use `getNodeRegistry` instead
  388. */
  389. getNodeRegister<T extends FlowNodeRegistry = FlowNodeRegistry>(
  390. type: FlowNodeType,
  391. originParent?: FlowNodeEntity
  392. ): T {
  393. return this.getNodeRegistry<T>(type, originParent);
  394. }
  395. getNodeRegistry<T extends FlowNodeRegistry = FlowNodeRegistry>(
  396. type: FlowNodeType,
  397. originParent?: FlowNodeEntity
  398. ): T {
  399. const typeKey = `${type}_${originParent?.flowNodeType || ''}`;
  400. if (this.nodeRegistryCache.has(typeKey)) {
  401. return this.nodeRegistryCache.get(typeKey) as T;
  402. }
  403. const customDefaultRegistry = this.options.getNodeDefaultRegistry?.(type);
  404. let register = this.registers.get(type) || { type };
  405. const extendRegisters: FlowNodeRegistry[] = [];
  406. // 继承重载
  407. if (register.extend && this.registers.has(register.extend)) {
  408. register = FlowNodeRegistry.merge(
  409. this.getNodeRegistry(register.extend),
  410. register,
  411. register.type
  412. );
  413. }
  414. // 父节点覆盖
  415. if (originParent) {
  416. const extendRegister = this.getNodeRegistry(
  417. originParent.flowNodeType
  418. ).extendChildRegistries?.find((r) => r.type === type);
  419. if (extendRegister) {
  420. if (extendRegister.extend && this.registers.has(extendRegister.extend)) {
  421. extendRegisters.push(this.registers.get(extendRegister.extend)!);
  422. }
  423. extendRegisters.push(extendRegister);
  424. }
  425. }
  426. register = FlowNodeRegistry.extend(register, extendRegisters);
  427. const defaultNodeMeta = DEFAULT_FLOW_NODE_META(type, this);
  428. defaultNodeMeta.spacing =
  429. this.options?.constants?.[ConstantKeys.NODE_SPACING] || defaultNodeMeta.spacing;
  430. const res = {
  431. ...customDefaultRegistry,
  432. ...register,
  433. meta: {
  434. ...defaultNodeMeta,
  435. ...customDefaultRegistry?.meta,
  436. ...register.meta,
  437. },
  438. } as T;
  439. this.nodeRegistryCache.set(typeKey, res);
  440. return res;
  441. }
  442. /**
  443. * 节点注入数据
  444. * @param nodeDatas
  445. */
  446. registerNodeDatas(...nodeDatas: EntityDataRegistry[]): void {
  447. this.nodeDataRegistries.push(...nodeDatas);
  448. }
  449. /**
  450. * traverse all nodes, O(n)
  451. * R
  452. * |
  453. * +---1
  454. * | |
  455. * | +---1.1
  456. * | |
  457. * | +---1.2
  458. * | |
  459. * | +---1.3
  460. * | | |
  461. * | | +---1.3.1
  462. * | | |
  463. * | | +---1.3.2
  464. * | |
  465. * | +---1.4
  466. * |
  467. * +---2
  468. * |
  469. * +---2.1
  470. *
  471. * sort: [1, 1.1, 1.2, 1.3, 1.3.1, 1.3.2, 1.4, 2, 2.1]
  472. * @param fn
  473. * @param node
  474. * @param depth
  475. * @return isBreak
  476. */
  477. traverse(
  478. fn: (node: FlowNodeEntity, depth: number, index: number) => boolean | void,
  479. node = this.root,
  480. depth = 0
  481. ): boolean | void {
  482. return this.originTree.traverse(fn, node, depth);
  483. }
  484. get size(): number {
  485. return this.getAllNodes().length;
  486. }
  487. hasNode(nodeId: string): boolean {
  488. return !!this.entityManager.getEntityById(nodeId);
  489. }
  490. getAllNodes(): FlowNodeEntity[] {
  491. return this.entityManager.getEntities(FlowNodeEntity);
  492. }
  493. toString(): string {
  494. return this.originTree.toString();
  495. }
  496. /**
  497. * 返回需要渲染的数据
  498. */
  499. getRenderDatas<T extends EntityData>(
  500. dataRegistry: EntityDataRegistry<T>,
  501. containHiddenNodes = true
  502. ): T[] {
  503. const result: T[] = [];
  504. this.renderTree.traverse((node) => {
  505. if (!containHiddenNodes && node.hidden) return;
  506. result.push(node.getData<T>(dataRegistry)!);
  507. });
  508. return result;
  509. }
  510. /**
  511. * 移动节点
  512. * @param param0
  513. * @returns
  514. */
  515. moveNodes({
  516. dropNodeId,
  517. sortNodeIds,
  518. inside = false,
  519. }: {
  520. dropNodeId: string;
  521. sortNodeIds: string[];
  522. inside?: boolean;
  523. }) {
  524. const dropEntity = this.getNode(dropNodeId);
  525. if (!dropEntity) {
  526. return;
  527. }
  528. const sortNodes = sortNodeIds.map((id) => this.getNode(id)!);
  529. // 按照顺序一个个移动到目标节点下
  530. this.entityManager.changeEntityLocked = true;
  531. for (const node of sortNodes.reverse()) {
  532. if (inside) {
  533. this.originTree.addChild(dropEntity, node, 0);
  534. } else {
  535. this.originTree.insertAfter(dropEntity, node);
  536. }
  537. }
  538. this.entityManager.changeEntityLocked = false;
  539. this.fireRender();
  540. }
  541. /**
  542. * 移动子节点
  543. * @param param0
  544. * @returns
  545. */
  546. moveChildNodes({
  547. toParentId,
  548. toIndex,
  549. nodeIds,
  550. }: {
  551. toParentId: string;
  552. nodeIds: string[];
  553. toIndex: number;
  554. }) {
  555. if (nodeIds.length === 0) {
  556. return;
  557. }
  558. const toParent = this.getNode(toParentId);
  559. if (!toParent) {
  560. return;
  561. }
  562. this.entityManager.changeEntityLocked = true;
  563. this.originTree.moveChilds(
  564. toParent,
  565. nodeIds.map((nodeId) => this.getNode(nodeId) as FlowNodeEntity),
  566. toIndex
  567. );
  568. this.entityManager.changeEntityLocked = false;
  569. this.fireRender();
  570. }
  571. /**
  572. * 注册布局
  573. * @param layout
  574. */
  575. registerLayout(layout: FlowLayout) {
  576. this.layouts.push(layout);
  577. }
  578. /**
  579. * 更新布局
  580. * @param layoutKey
  581. */
  582. setLayout(layoutKey: string) {
  583. if (this.currentLayoutKey === layoutKey) return;
  584. const layout = this.layouts.find((layout) => layout.name === layoutKey);
  585. if (!layout) return;
  586. this.currentLayoutKey = layoutKey;
  587. this.transformer.clear();
  588. layout.reload?.();
  589. this.fireRender();
  590. this.onLayoutChangeEmitter.fire(this.layout);
  591. }
  592. /**
  593. * 切换垂直或水平布局
  594. */
  595. toggleFixedLayout() {
  596. this.setLayout(
  597. this.layout.name === FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
  598. ? FlowLayoutDefault.VERTICAL_FIXED_LAYOUT
  599. : FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
  600. );
  601. }
  602. dispose() {
  603. this.registers.clear();
  604. this.nodeRegistryCache.clear();
  605. this.originTree.dispose();
  606. this.renderTree.dispose();
  607. this.onNodeUpdateEmitter.dispose();
  608. this.onNodeCreateEmitter.dispose();
  609. this.onNodeDisposeEmitter.dispose();
  610. this.onLayoutChangeEmitter.dispose();
  611. }
  612. }