workflow-document.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803
  1. import { customAlphabet } from 'nanoid';
  2. import { inject, injectable, optional, postConstruct } from 'inversify';
  3. import { Disposable, Emitter, type IPoint } from '@flowgram.ai/utils';
  4. import { NodeEngineContext } from '@flowgram.ai/form-core';
  5. import { FlowDocument, FlowNodeBaseType, FlowNodeTransformData } from '@flowgram.ai/document';
  6. import {
  7. injectPlaygroundContext,
  8. PlaygroundConfigEntity,
  9. PlaygroundContext,
  10. PositionData,
  11. TransformData,
  12. } from '@flowgram.ai/core';
  13. import { WorkflowLinesManager } from './workflow-lines-manager';
  14. import {
  15. WorkflowDocumentOptions,
  16. WorkflowDocumentOptionsDefault,
  17. } from './workflow-document-option';
  18. import { getFlowNodeFormData } from './utils/flow-node-form-data';
  19. import { delay, fitView, getAntiOverlapPosition } from './utils';
  20. import {
  21. type WorkflowContentChangeEvent,
  22. WorkflowContentChangeType,
  23. WorkflowEdgeJSON,
  24. type WorkflowJSON,
  25. type WorkflowNodeJSON,
  26. type WorkflowNodeMeta,
  27. type WorkflowNodeRegistry,
  28. WorkflowSubCanvas,
  29. } from './typings';
  30. import { WorkflowSelectService } from './service/workflow-select-service';
  31. import { FREE_LAYOUT_KEY, type FreeLayout } from './layout';
  32. import { WorkflowNodeLinesData } from './entity-datas';
  33. import {
  34. WorkflowLineEntity,
  35. WorkflowLinePortInfo,
  36. WorkflowNodeEntity,
  37. WorkflowPortEntity,
  38. } from './entities';
  39. const nanoid = customAlphabet('1234567890', 5);
  40. export const WorkflowDocumentProvider = Symbol('WorkflowDocumentProvider');
  41. export type WorkflowDocumentProvider = () => WorkflowDocument;
  42. @injectable()
  43. export class WorkflowDocument extends FlowDocument {
  44. private _onContentChangeEmitter = new Emitter<WorkflowContentChangeEvent>();
  45. protected readonly onLoadedEmitter = new Emitter<void>();
  46. readonly onContentChange = this._onContentChangeEmitter.event;
  47. private _onReloadEmitter = new Emitter<WorkflowDocument>();
  48. readonly onReload = this._onReloadEmitter.event;
  49. private disposed = false;
  50. /**
  51. * 数据加载完成
  52. */
  53. readonly onLoaded = this.onLoadedEmitter.event;
  54. protected _loading = false;
  55. @inject(WorkflowLinesManager) linesManager: WorkflowLinesManager;
  56. @inject(PlaygroundConfigEntity) playgroundConfig: PlaygroundConfigEntity;
  57. @injectPlaygroundContext() playgroundContext: PlaygroundContext;
  58. @inject(WorkflowDocumentOptions)
  59. options: WorkflowDocumentOptions = {};
  60. @inject(NodeEngineContext) @optional() nodeEngineContext: NodeEngineContext;
  61. @inject(WorkflowSelectService) selectServices: WorkflowSelectService;
  62. get loading(): boolean {
  63. return this._loading;
  64. }
  65. async fitView(easing?: boolean): Promise<void> {
  66. return fitView(this, this.playgroundConfig, easing).then(() => {
  67. this.linesManager.forceUpdate();
  68. });
  69. }
  70. @postConstruct()
  71. init(): void {
  72. super.init();
  73. this.currentLayoutKey = this.options.defaultLayout || FREE_LAYOUT_KEY;
  74. this.linesManager.init(this);
  75. this.playgroundConfig.getCursors = () => this.options.cursors;
  76. this.linesManager.onAvailableLinesChange((e) => this.fireContentChange(e));
  77. this.playgroundConfig.onReadonlyOrDisabledChange(({ readonly }) => {
  78. if (this.nodeEngineContext) {
  79. this.nodeEngineContext.readonly = readonly;
  80. }
  81. });
  82. }
  83. async load(): Promise<void> {
  84. this._loading = true;
  85. await super.load();
  86. this._loading = false;
  87. this.onLoadedEmitter.fire();
  88. }
  89. async reload(json: WorkflowJSON, delayTime = 0): Promise<void> {
  90. this._loading = true;
  91. this.clear();
  92. this.fromJSON(json);
  93. // loading添加delay,避免reload时触发fireContentChange的副作用
  94. await delay(delayTime);
  95. this._loading = false;
  96. this._onReloadEmitter.fire(this);
  97. }
  98. /**
  99. * 从数据加载
  100. * @param json
  101. */
  102. fromJSON(json: Partial<WorkflowJSON>, fireRender = true): void {
  103. const { flattenJSON, nodeBlocks, nodeEdges } = this.flatJSON(json);
  104. const nestedJSON = this.nestJSON(flattenJSON, nodeBlocks, nodeEdges);
  105. // 触发画布更新
  106. this.entityManager.changeEntityLocked = true;
  107. // 逐层渲染
  108. this.renderJSON(nestedJSON);
  109. this.entityManager.changeEntityLocked = false;
  110. this.transformer.loading = false;
  111. // 批量触发画布更新
  112. if (fireRender) {
  113. this.fireRender();
  114. }
  115. }
  116. /**
  117. * 清空画布
  118. */
  119. clear(): void {
  120. this.getAllNodes().map((node) => node.dispose()); // 清空节点
  121. this.linesManager.getAllLines().map((line) => line.dispose()); // 清空线条
  122. this.getAllPorts().map((port) => port.dispose()); // 清空端口
  123. this.selectServices.clear(); // 清空选择
  124. }
  125. /**
  126. * 创建流程节点
  127. * @param json
  128. */
  129. createWorkflowNode(
  130. json: WorkflowNodeJSON,
  131. isClone: boolean = false,
  132. parentId?: string
  133. ): WorkflowNodeEntity {
  134. // 是否是一个已经存在的节点
  135. const isExistedNode = this.getNode(json.id);
  136. const parent = this.getNode(parentId ?? this.root.id) ?? this.root;
  137. const node = this.addNode(
  138. {
  139. ...json,
  140. parent,
  141. },
  142. undefined,
  143. true
  144. ) as WorkflowNodeEntity;
  145. const registry = node.getNodeRegistry() as WorkflowNodeRegistry;
  146. const { formMeta } = registry;
  147. const meta = node.getNodeMeta<WorkflowNodeMeta>();
  148. const formData = getFlowNodeFormData(node);
  149. const transform = node.getData<FlowNodeTransformData>(FlowNodeTransformData)!;
  150. const freeLayout = this.layout as FreeLayout;
  151. transform.onDataChange(() => {
  152. // TODO 这个有点难以理解,其实是为了同步size 数据
  153. freeLayout.syncTransform(node);
  154. });
  155. let { position } = meta;
  156. if (!position) {
  157. // 获取默认的位置
  158. position = this.getNodeDefaultPosition(json.type);
  159. }
  160. // 更新节点位置信息
  161. node.getData(TransformData)!.update({
  162. position,
  163. });
  164. // 初始化表单数据
  165. if (formMeta && formData && !formData.formModel.initialized) {
  166. // 如果表单数据在前置步骤(fromJSON)内已定义,则跳过表单初始化逻辑
  167. formData.createForm(formMeta, json.data);
  168. formData.onDataChange(() => {
  169. this.fireContentChange({
  170. type: WorkflowContentChangeType.NODE_DATA_CHANGE,
  171. toJSON: () => formData.toJSON(),
  172. entity: node,
  173. });
  174. });
  175. }
  176. // 位置变更
  177. const positionData = node.getData<PositionData>(PositionData)!;
  178. positionData.onDataChange(() => {
  179. this.fireContentChange({
  180. type: WorkflowContentChangeType.MOVE_NODE,
  181. toJSON: () => positionData.toJSON(),
  182. entity: node,
  183. });
  184. });
  185. const subCanvas = this.getNodeSubCanvas(node);
  186. if (!isExistedNode && !subCanvas?.isCanvas) {
  187. this.fireContentChange({
  188. type: WorkflowContentChangeType.ADD_NODE,
  189. entity: node,
  190. toJSON: () => this.toNodeJSON(node),
  191. });
  192. node.toDispose.push(
  193. Disposable.create(() => {
  194. this.fireContentChange({
  195. type: WorkflowContentChangeType.DELETE_NODE,
  196. entity: node,
  197. toJSON: () => this.toNodeJSON(node),
  198. });
  199. })
  200. );
  201. node.toDispose.push(
  202. Disposable.create(() => {
  203. if (!node.parent || node.parent.flowNodeType === FlowNodeBaseType.ROOT) {
  204. return;
  205. }
  206. const parentTransform = node.parent.getData(FlowNodeTransformData);
  207. // 加延迟是因为这个回调触发时节点实体还没有被销毁
  208. setTimeout(() => {
  209. parentTransform.fireChange();
  210. }, 0);
  211. })
  212. );
  213. }
  214. // 若存在子节点,则创建子节点
  215. if (json.blocks) {
  216. this.renderJSON(
  217. { nodes: json.blocks, edges: json.edges ?? [] },
  218. {
  219. parent: node,
  220. isClone,
  221. }
  222. );
  223. }
  224. // 子画布联动
  225. if (subCanvas) {
  226. const canvasTransform = subCanvas.canvasNode.getData<TransformData>(TransformData);
  227. canvasTransform.update({
  228. position: subCanvas.parentNode.getNodeMeta()?.canvasPosition,
  229. });
  230. subCanvas.parentNode.onDispose(() => {
  231. subCanvas.canvasNode.dispose();
  232. });
  233. subCanvas.canvasNode.onDispose(() => {
  234. subCanvas.parentNode.dispose();
  235. });
  236. }
  237. this.onNodeCreateEmitter.fire({
  238. node,
  239. data: json,
  240. });
  241. return node;
  242. }
  243. /**
  244. * 获取默认的 x y 坐标, 默认为当前画布可视区域中心
  245. * @param type
  246. * @protected
  247. */
  248. getNodeDefaultPosition(type: string | number): IPoint {
  249. const { size } = this.getNodeRegistry(type).meta || {};
  250. // 当前可视区域的中心位置
  251. let position = this.playgroundConfig.getViewport(true).center;
  252. if (size) {
  253. position = {
  254. x: position.x,
  255. y: position.y - size.height / 2,
  256. };
  257. }
  258. // 去掉叠加的
  259. return getAntiOverlapPosition(this, position);
  260. }
  261. /**
  262. * 通过类型创建节点, 如果没有提供position 则直接放在画布中间
  263. * @param type
  264. */
  265. createWorkflowNodeByType(
  266. type: string | number,
  267. position?: IPoint,
  268. json: Partial<WorkflowNodeJSON> = {},
  269. parentID?: string
  270. ): WorkflowNodeEntity {
  271. let id: string = json.id as string;
  272. if (id === undefined) {
  273. // 保证 id 不要重复
  274. do {
  275. id = `1${nanoid()}`;
  276. } while (this.entityManager.getEntityById(id));
  277. } else {
  278. if (this.entityManager.getEntityById(id)) {
  279. throw new Error(`[WorkflowDocument.createWorkflowNodeByType] Node Id "${id}" duplicated.`);
  280. }
  281. }
  282. return this.createWorkflowNode(
  283. {
  284. ...json,
  285. id,
  286. type,
  287. meta: { position, ...json?.meta }, // TODO title 和 meta 要从注册数据去拿
  288. data: json?.data,
  289. blocks: json?.blocks,
  290. edges: json?.edges,
  291. },
  292. false,
  293. parentID
  294. );
  295. }
  296. getAllNodes(): WorkflowNodeEntity[] {
  297. return this.entityManager
  298. .getEntities<WorkflowNodeEntity>(WorkflowNodeEntity)
  299. .filter((n) => n.id !== FlowNodeBaseType.ROOT);
  300. }
  301. getAllPorts(): WorkflowPortEntity[] {
  302. return this.entityManager
  303. .getEntities<WorkflowPortEntity>(WorkflowPortEntity)
  304. .filter((p) => p.node.id !== FlowNodeBaseType.ROOT);
  305. }
  306. /**
  307. * 获取画布中的非游离节点
  308. * 1. 开始节点
  309. * 2. 从开始节点出发能走到的节点
  310. * 3. 结束节点
  311. * 4. 默认所有子画布内节点为游离节点
  312. */
  313. getAssociatedNodes(): WorkflowNodeEntity[] {
  314. const allNode = this.getAllNodes();
  315. const allLines = this.linesManager
  316. .getAllLines()
  317. .filter((line) => line.from && line.to)
  318. .map((line) => ({
  319. from: line.from.id,
  320. to: line.to!.id,
  321. }));
  322. const startNodeId = allNode.find((node) => node.isStart)!.id;
  323. const endNodeId = allNode.find((node) => node.isNodeEnd)!.id;
  324. // 子画布内节点无需开始/结束
  325. const nodeInSubCanvas = allNode
  326. .filter((node) => node.parent?.flowNodeType === FlowNodeBaseType.SUB_CANVAS)
  327. .map((node) => node.id);
  328. const associatedCache = new Set([endNodeId, ...nodeInSubCanvas]);
  329. const bfs = (nodeId: string) => {
  330. if (associatedCache.has(nodeId)) {
  331. return;
  332. }
  333. associatedCache.add(nodeId);
  334. const nextNodes = allLines.reduce((ids, { from, to }) => {
  335. if (from === nodeId && !associatedCache.has(to)) {
  336. ids.push(to);
  337. }
  338. return ids;
  339. }, [] as string[]);
  340. nextNodes.forEach(bfs);
  341. };
  342. bfs(startNodeId);
  343. const associatedNodes = allNode.filter((node) => associatedCache.has(node.id));
  344. return associatedNodes;
  345. }
  346. /**
  347. * 触发渲染
  348. */
  349. fireRender() {
  350. this.entityManager.fireEntityChanged(WorkflowNodeEntity.type);
  351. this.entityManager.fireEntityChanged(WorkflowLineEntity.type);
  352. this.entityManager.fireEntityChanged(WorkflowPortEntity.type);
  353. }
  354. fireContentChange(event: WorkflowContentChangeEvent): void {
  355. if (this._loading || this.disposed || this.entityManager.changeEntityLocked) {
  356. return;
  357. }
  358. this._onContentChangeEmitter.fire(event);
  359. }
  360. toNodeJSON(node: WorkflowNodeEntity): WorkflowNodeJSON {
  361. // 如果是子画布,返回其父节点的JSON
  362. const subCanvas = this.getNodeSubCanvas(node);
  363. if (subCanvas?.isCanvas === true) {
  364. return this.toNodeJSON(subCanvas.parentNode);
  365. }
  366. const json = this.toNodeJSONFromOptions(node);
  367. const children = this.getNodeChildren(node);
  368. // 计算子节点 JSON
  369. const blocks = children.map((child) => this.toNodeJSON(child));
  370. // 计算子线条 JSON
  371. const linesMap = new Map<string, WorkflowEdgeJSON>();
  372. children.forEach((child) => {
  373. const childLinesData = child.getData<WorkflowNodeLinesData>(WorkflowNodeLinesData);
  374. [...childLinesData.inputLines, ...childLinesData.outputLines]
  375. .filter(Boolean)
  376. .forEach((line) => {
  377. const lineJSON = this.toLineJSON(line);
  378. if (!lineJSON || linesMap.has(line.id)) {
  379. return;
  380. }
  381. linesMap.set(line.id, lineJSON);
  382. });
  383. });
  384. const edges = Array.from(linesMap.values()); // 使用 Map 防止线条重复
  385. // 拼接 JSON
  386. if (blocks.length > 0) json.blocks = blocks;
  387. if (edges.length > 0) json.edges = edges;
  388. return json;
  389. }
  390. /**
  391. * 节点转换为JSON, 没有format的过程
  392. * @param node
  393. * @returns
  394. */
  395. private toNodeJSONFromOptions(node: WorkflowNodeEntity): WorkflowNodeJSON {
  396. if (this.options.toNodeJSON) {
  397. return this.options.toNodeJSON(node) as WorkflowNodeJSON;
  398. }
  399. return WorkflowDocumentOptionsDefault.toNodeJSON!(node) as WorkflowNodeJSON;
  400. }
  401. copyNode(
  402. node: WorkflowNodeEntity,
  403. newNodeId?: string | undefined,
  404. format?: (json: WorkflowNodeJSON) => WorkflowNodeJSON,
  405. position?: IPoint
  406. ): WorkflowNodeEntity {
  407. let json = this.toNodeJSON(node);
  408. if (format) {
  409. json = format(json);
  410. }
  411. position = position || {
  412. x: json.meta!.position!.x + 30,
  413. y: json.meta!.position!.y + 30,
  414. };
  415. return this.createWorkflowNode(
  416. {
  417. id: newNodeId || `1${nanoid()}`,
  418. type: node.flowNodeType,
  419. meta: {
  420. ...json.meta,
  421. position,
  422. },
  423. data: json.data,
  424. blocks: json.blocks,
  425. edges: json.edges,
  426. },
  427. true,
  428. node.parent?.id
  429. );
  430. }
  431. copyNodeFromJSON(
  432. flowNodeType: string,
  433. nodeJSON: WorkflowNodeJSON,
  434. newNodeId?: string | undefined,
  435. position?: IPoint,
  436. parentId?: string
  437. ): WorkflowNodeEntity {
  438. position = position || {
  439. x: nodeJSON.meta!.position!.x + 30,
  440. y: nodeJSON.meta!.position!.y + 30,
  441. };
  442. return this.createWorkflowNode(
  443. {
  444. id: newNodeId || `1${nanoid()}`,
  445. type: flowNodeType,
  446. meta: {
  447. ...nodeJSON.meta,
  448. position,
  449. },
  450. data: nodeJSON.data,
  451. blocks: nodeJSON.blocks,
  452. edges: nodeJSON.edges,
  453. },
  454. true,
  455. parentId
  456. );
  457. }
  458. canRemove(node: WorkflowNodeEntity, silent?: boolean): boolean {
  459. const meta = node.getNodeMeta<WorkflowNodeMeta>();
  460. if (meta.deleteDisable) {
  461. return false;
  462. }
  463. if (this.options.canDeleteNode && !this.options.canDeleteNode(node, silent)) {
  464. return false;
  465. }
  466. return true;
  467. }
  468. /**
  469. * 判断端口是否为错误态
  470. */
  471. isErrorPort(port: WorkflowPortEntity) {
  472. if (typeof this.options.isErrorPort === 'function') {
  473. return this.options.isErrorPort(port);
  474. }
  475. return false;
  476. }
  477. /**
  478. * 导出数据
  479. */
  480. toJSON(): WorkflowJSON {
  481. // 要等 一些节点的 dispose 触发结束
  482. // await delay(10);
  483. const rootJSON = this.toNodeJSON(this.root);
  484. return {
  485. nodes: rootJSON.blocks ?? [],
  486. edges: rootJSON.edges ?? [],
  487. };
  488. }
  489. dispose() {
  490. if (this.disposed) {
  491. return;
  492. }
  493. super.dispose();
  494. this.disposed = true;
  495. this._onReloadEmitter.dispose();
  496. }
  497. private getEdgeID(edge: WorkflowEdgeJSON): string {
  498. return WorkflowLineEntity.portInfoToLineId({
  499. from: edge.sourceNodeID,
  500. to: edge.targetNodeID,
  501. fromPort: edge.sourcePortID,
  502. toPort: edge.targetPortID,
  503. });
  504. }
  505. /**
  506. * 拍平树形json结构,将结构信息提取到map
  507. */
  508. private flatJSON(json: Partial<WorkflowJSON> = { nodes: [], edges: [] }): {
  509. flattenJSON: WorkflowJSON;
  510. nodeBlocks: Map<string, string[]>;
  511. nodeEdges: Map<string, string[]>;
  512. } {
  513. const nodeBlocks = new Map<string, string[]>();
  514. const nodeEdges = new Map<string, string[]>();
  515. const rootNodes = json.nodes ?? [];
  516. const rootEdges = json.edges ?? [];
  517. const flattenNodeJSONs: WorkflowNodeJSON[] = [...rootNodes];
  518. const flattenEdgeJSONs: WorkflowEdgeJSON[] = [...rootEdges];
  519. const rootBlockIDs: string[] = rootNodes.map((node) => node.id);
  520. const rootEdgeIDs: string[] = rootEdges.map((edge) => this.getEdgeID(edge));
  521. nodeBlocks.set(FlowNodeBaseType.ROOT, rootBlockIDs);
  522. nodeEdges.set(FlowNodeBaseType.ROOT, rootEdgeIDs);
  523. // 如需支持多层结构,以下部分改为递归
  524. rootNodes.forEach((nodeJSON) => {
  525. const { blocks, edges } = nodeJSON;
  526. if (blocks) {
  527. flattenNodeJSONs.push(...blocks);
  528. const blockIDs: string[] = [];
  529. blocks.forEach((block) => {
  530. blockIDs.push(block.id);
  531. });
  532. nodeBlocks.set(nodeJSON.id, blockIDs);
  533. delete nodeJSON.blocks;
  534. }
  535. if (edges) {
  536. flattenEdgeJSONs.push(...edges);
  537. const edgeIDs: string[] = [];
  538. edges.forEach((edge) => {
  539. const edgeID = this.getEdgeID(edge);
  540. edgeIDs.push(edgeID);
  541. });
  542. nodeEdges.set(nodeJSON.id, edgeIDs);
  543. delete nodeJSON.edges;
  544. }
  545. });
  546. const flattenJSON: WorkflowJSON = {
  547. nodes: flattenNodeJSONs,
  548. edges: flattenEdgeJSONs,
  549. };
  550. return {
  551. flattenJSON,
  552. nodeBlocks,
  553. nodeEdges,
  554. };
  555. }
  556. /**
  557. * 对JSON进行分层
  558. */
  559. private nestJSON(
  560. flattenJSON: WorkflowJSON,
  561. nodeBlocks: Map<string, string[]>,
  562. nodeEdges: Map<string, string[]>
  563. ): WorkflowJSON {
  564. const nestJSON: WorkflowJSON = {
  565. nodes: [],
  566. edges: [],
  567. };
  568. const nodeMap = new Map<string, WorkflowNodeJSON>();
  569. const edgeMap = new Map<string, WorkflowEdgeJSON>();
  570. const rootBlockSet = new Set<string>(nodeBlocks.get(FlowNodeBaseType.ROOT) ?? []);
  571. const rootEdgeSet = new Set<string>(nodeEdges.get(FlowNodeBaseType.ROOT) ?? []);
  572. // 构造缓存
  573. flattenJSON.nodes.forEach((nodeJSON) => {
  574. nodeMap.set(nodeJSON.id, nodeJSON);
  575. });
  576. flattenJSON.edges.forEach((edgeJSON) => {
  577. const edgeID = this.getEdgeID(edgeJSON);
  578. edgeMap.set(edgeID, edgeJSON);
  579. });
  580. // 恢复层级数据
  581. flattenJSON.nodes.forEach((nodeJSON) => {
  582. if (rootBlockSet.has(nodeJSON.id)) {
  583. nestJSON.nodes.push(nodeJSON);
  584. }
  585. // 恢复blocks
  586. if (nodeBlocks.has(nodeJSON.id)) {
  587. const blockIDs = nodeBlocks.get(nodeJSON.id)!;
  588. const blockJSONs: WorkflowNodeJSON[] = blockIDs
  589. .map((blockID) => nodeMap.get(blockID)!)
  590. .filter(Boolean);
  591. nodeJSON.blocks = blockJSONs;
  592. }
  593. // 恢复edges
  594. if (nodeEdges.has(nodeJSON.id)) {
  595. const edgeIDs = nodeEdges.get(nodeJSON.id)!;
  596. const edgeJSONs: WorkflowEdgeJSON[] = edgeIDs
  597. .map((edgeID) => edgeMap.get(edgeID)!)
  598. .filter(Boolean);
  599. nodeJSON.edges = edgeJSONs;
  600. }
  601. });
  602. flattenJSON.edges.forEach((edgeJSON) => {
  603. const edgeID = this.getEdgeID(edgeJSON);
  604. if (rootEdgeSet.has(edgeID)) {
  605. nestJSON.edges.push(edgeJSON);
  606. }
  607. });
  608. return nestJSON;
  609. }
  610. /**
  611. * 逐层创建节点和线条
  612. */
  613. private renderJSON(
  614. json: WorkflowJSON,
  615. options?: {
  616. parent?: WorkflowNodeEntity;
  617. isClone?: boolean;
  618. }
  619. ) {
  620. // await delay(0); // Loop 节点 onCreate 存在异步创建子画布
  621. const { parent = this.root, isClone = false } = options ?? {};
  622. // 创建节点
  623. const containerID = this.getNodeSubCanvas(parent)?.canvasNode.id ?? parent.id;
  624. json.nodes.forEach((nodeJSON: WorkflowNodeJSON) => {
  625. this.createWorkflowNode(nodeJSON, isClone, containerID);
  626. }),
  627. // 创建线条
  628. json.edges.forEach((edge) => this.createWorkflowLine(edge, containerID));
  629. }
  630. private getNodeSubCanvas(node: WorkflowNodeEntity): WorkflowSubCanvas | undefined {
  631. if (!node) return;
  632. const nodeMeta = node.getNodeMeta<WorkflowNodeMeta>();
  633. const subCanvas = nodeMeta.subCanvas?.(node);
  634. return subCanvas;
  635. }
  636. private getNodeChildren(node: WorkflowNodeEntity): WorkflowNodeEntity[] {
  637. if (!node) return [];
  638. const subCanvas = this.getNodeSubCanvas(node);
  639. const childrenWithCanvas = subCanvas
  640. ? subCanvas.canvasNode.collapsedChildren
  641. : node.collapsedChildren;
  642. // 过滤掉子画布的JSON数据
  643. const children = childrenWithCanvas
  644. .filter((child) => {
  645. const childMeta = child.getNodeMeta<WorkflowNodeMeta>();
  646. return !childMeta.subCanvas?.(node)?.isCanvas;
  647. })
  648. .filter(Boolean);
  649. return children;
  650. }
  651. private toLineJSON(line: WorkflowLineEntity): WorkflowEdgeJSON | undefined {
  652. const lineJSON = line.toJSON();
  653. if (!line.to || !line.info.to || !line.toPort) {
  654. return;
  655. }
  656. // 父子节点之间连线,需替换子画布为父节点
  657. const fromSubCanvas = this.getNodeSubCanvas(line.from);
  658. const toSubCanvas = this.getNodeSubCanvas(line.to);
  659. if (fromSubCanvas && !fromSubCanvas.isCanvas && toSubCanvas && toSubCanvas.isCanvas) {
  660. // 忽略子画布与父节点的连线
  661. return;
  662. }
  663. if (line.from === line.to.parent && fromSubCanvas) {
  664. return {
  665. ...lineJSON,
  666. sourceNodeID: fromSubCanvas.parentNode.id,
  667. };
  668. }
  669. if (line.to === line.from.parent && toSubCanvas) {
  670. return {
  671. ...lineJSON,
  672. targetNodeID: toSubCanvas.parentNode.id,
  673. };
  674. }
  675. return lineJSON;
  676. }
  677. private createWorkflowLine(
  678. json: WorkflowEdgeJSON,
  679. parentId?: string
  680. ): WorkflowLineEntity | undefined {
  681. const fromNode = this.getNode(json.sourceNodeID);
  682. const toNode = this.getNode(json.targetNodeID);
  683. // 脏数据清除
  684. if (!fromNode || !toNode) {
  685. return;
  686. }
  687. const lineInfo: WorkflowLinePortInfo = {
  688. from: json.sourceNodeID,
  689. fromPort: json.sourcePortID,
  690. to: json.targetNodeID,
  691. toPort: json.targetPortID,
  692. };
  693. if (!parentId) {
  694. return this.linesManager.createLine(lineInfo);
  695. }
  696. // 父子节点之间连线,需替换父节点为子画布
  697. const canvasNode = this.getNode(parentId);
  698. if (!canvasNode) {
  699. return this.linesManager.createLine(lineInfo);
  700. }
  701. const parentSubCanvas = this.getNodeSubCanvas(canvasNode);
  702. if (!parentSubCanvas) {
  703. return this.linesManager.createLine(lineInfo);
  704. }
  705. if (lineInfo.from === parentSubCanvas.parentNode.id) {
  706. return this.linesManager.createLine({
  707. ...lineInfo,
  708. from: parentSubCanvas.canvasNode.id,
  709. });
  710. }
  711. if (lineInfo.to === parentSubCanvas.parentNode.id) {
  712. return this.linesManager.createLine({
  713. ...lineInfo,
  714. to: parentSubCanvas.canvasNode.id,
  715. });
  716. }
  717. return this.linesManager.createLine(lineInfo);
  718. }
  719. }