| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- import { omit } from 'lodash';
- import { inject, injectable, multiInject, optional, postConstruct } from 'inversify';
- import { type Disposable, Emitter } from '@flowgram.ai/utils';
- import { type EntityData, type EntityDataRegistry, EntityManager } from '@flowgram.ai/core';
- import {
- AddNodeData,
- DEFAULT_FLOW_NODE_META,
- type FlowDocumentJSON,
- FlowLayout,
- FlowLayoutDefault,
- FlowNodeBaseType,
- type FlowNodeJSON,
- FlowNodeRegistry,
- FlowNodeType,
- } from './typings';
- import { FlowVirtualTree } from './flow-virtual-tree';
- import { FlowRenderTree } from './flow-render-tree';
- import {
- ConstantKeys,
- FlowDocumentOptions,
- FlowDocumentOptionsDefault,
- } from './flow-document-options';
- import { FlowDocumentContribution } from './flow-document-contribution';
- import { FlowDocumentConfig } from './flow-document-config';
- import { FlowDocumentTransformerEntity, FlowNodeEntity, FlowRendererStateEntity } from './entities';
- export type FlowDocumentProvider = () => FlowDocument;
- export const FlowDocumentProvider = Symbol('FlowDocumentProvider');
- /**
- * 流程整个文档数据
- */
- @injectable()
- export class FlowDocument<T = FlowDocumentJSON> implements Disposable {
- @inject(EntityManager) protected entityManager: EntityManager;
- @inject(FlowDocumentConfig) readonly config: FlowDocumentConfig;
- /**
- * 流程画布配置项
- */
- @inject(FlowDocumentOptions) @optional() public options: FlowDocumentOptions;
- @multiInject(FlowDocumentContribution)
- @optional()
- protected contributions: FlowDocumentContribution[] = [];
- protected registers = new Map<FlowNodeType, FlowNodeRegistry>();
- private nodeRegistryCache = new Map<string, any>();
- protected nodeDataRegistries: EntityDataRegistry[] = [];
- protected layouts: FlowLayout[] = [];
- protected currentLayoutKey: string = '';
- protected onNodeUpdateEmitter = new Emitter<{
- node: FlowNodeEntity;
- data: FlowNodeJSON;
- }>();
- protected onNodeCreateEmitter = new Emitter<{
- node: FlowNodeEntity;
- data: FlowNodeJSON;
- }>();
- protected onNodeDisposeEmitter = new Emitter<{
- node: FlowNodeEntity;
- }>();
- protected onLayoutChangeEmitter = new Emitter<FlowLayout>();
- readonly onNodeUpdate = this.onNodeUpdateEmitter.event;
- readonly onNodeCreate = this.onNodeCreateEmitter.event;
- readonly onNodeDispose = this.onNodeDisposeEmitter.event;
- readonly onLayoutChange = this.onLayoutChangeEmitter.event;
- root: FlowNodeEntity;
- /**
- * 原始的 tree 结构
- */
- originTree: FlowVirtualTree<FlowNodeEntity>;
- transformer: FlowDocumentTransformerEntity;
- /**
- * 渲染相关的全局轧辊台
- */
- renderState: FlowRendererStateEntity;
- /**
- * 渲染后的 tree 结构
- */
- renderTree: FlowRenderTree<FlowNodeEntity>;
- @postConstruct()
- init(): void {
- if (!this.options) this.options = FlowDocumentOptionsDefault;
- this.currentLayoutKey = this.options.defaultLayout || FlowLayoutDefault.VERTICAL_FIXED_LAYOUT;
- this.contributions.forEach((contrib) => contrib.registerDocument?.(this));
- this.root = this.addNode({ id: 'root', type: FlowNodeBaseType.ROOT });
- this.originTree = new FlowVirtualTree<FlowNodeEntity>(this.root);
- this.transformer = this.entityManager.createEntity<FlowDocumentTransformerEntity>(
- FlowDocumentTransformerEntity,
- { document: this }
- );
- this.renderState =
- this.entityManager.createEntity<FlowRendererStateEntity>(FlowRendererStateEntity);
- this.renderTree = new FlowRenderTree<FlowNodeEntity>(this.root, this.originTree, this);
- // 布局第一次加载时候触发一次
- this.layout.reload?.();
- }
- /**
- * 从数据初始化 O(n)
- * @param json
- */
- /**
- * 加载数据,可以被重载
- * @param json 文档数据更新
- * @param fireRender 是否要触发渲染,默认 true
- */
- fromJSON(json: FlowDocumentJSON | any, fireRender = true): void {
- // 清空 tree 数据 重新计算
- this.originTree.clear();
- this.renderTree.clear();
- // 暂停触发画布更新
- this.entityManager.changeEntityLocked = true;
- // 添加前的节点
- const oldNodes = this.entityManager.getEntities<FlowNodeEntity>(FlowNodeEntity);
- // 添加后的节点
- const newNodes: FlowNodeEntity[] = [this.root];
- this.addBlocksAsChildren(this.root, json.nodes || [], newNodes);
- // 删除无效的节点
- oldNodes.forEach((node) => {
- if (!newNodes.includes(node)) {
- node.dispose();
- }
- });
- this.entityManager.changeEntityLocked = false;
- this.transformer.loading = false;
- if (fireRender) this.fireRender();
- }
- get layout(): FlowLayout {
- const layout = this.layouts.find((layout) => layout.name == this.currentLayoutKey);
- if (!layout) {
- throw new Error(`Unknown flow layout: ${this.currentLayoutKey}`);
- }
- return layout;
- }
- async load(): Promise<void> {
- await Promise.all(this.contributions.map((c) => c.loadDocument?.(this)));
- }
- get loading(): boolean {
- return this.transformer.loading;
- }
- /**
- * 触发 render
- */
- fireRender(): void {
- if (this.transformer.isTreeDirty()) {
- this.entityManager.fireEntityChanged(FlowNodeEntity.type);
- this.entityManager.fireEntityChanged(FlowDocumentTransformerEntity.type);
- }
- }
- /**
- * 从指定节点的下一个节点新增
- * @param fromNode
- * @param json
- */
- addFromNode(fromNode: FlowNodeEntity | string, json: FlowNodeJSON): FlowNodeEntity {
- const node = typeof fromNode === 'string' ? this.getNode(fromNode)! : fromNode;
- this.entityManager.changeEntityLocked = true;
- const { parent } = node;
- const result = this.addNode({
- ...json,
- parent,
- // originParent,
- });
- this.originTree.insertAfter(node, result);
- this.entityManager.changeEntityLocked = false;
- this.entityManager.fireEntityChanged(FlowNodeEntity.type);
- return result;
- }
- removeNode(node: FlowNodeEntity | string) {
- if (typeof node === 'string') {
- this.getNode(node)?.dispose();
- } else {
- node.dispose();
- }
- }
- /**
- * 添加节点,如果节点已经存在则不会重复创建
- * @param data
- * @param addedNodes
- */
- addNode(
- data: AddNodeData,
- addedNodes?: FlowNodeEntity[],
- ignoreCreateEvent?: boolean
- ): FlowNodeEntity {
- const { id, type = 'block', originParent, parent, meta, hidden, index } = data;
- let node = this.getNode(id);
- let isNew = false;
- const register = this.getNodeRegistry(type, data.originParent);
- // node 类型变化则全部删除重新来
- if (node && node.flowNodeType !== data.type) {
- node.dispose();
- node = undefined;
- }
- if (!node) {
- const { dataRegistries } = register;
- node = this.entityManager.createEntity<FlowNodeEntity>(FlowNodeEntity, {
- id,
- document: this,
- flowNodeType: type,
- originParent,
- meta,
- });
- const datas = dataRegistries
- ? this.nodeDataRegistries.concat(...dataRegistries)
- : this.nodeDataRegistries;
- node.addInitializeData(datas);
- node.onDispose(() => this.onNodeDisposeEmitter.fire({ node: node! }));
- if (this.options.fromNodeJSON) {
- this.options.fromNodeJSON(node, data);
- }
- isNew = true;
- }
- // 初始化数据重制
- node.initData({
- originParent,
- parent,
- meta,
- hidden,
- index,
- });
- // 开始节点加到 root 里边
- if (node.isStart) {
- this.root.addChild(node);
- }
- this.onNodeUpdateEmitter.fire({ node, data });
- addedNodes?.push(node);
- // 自定义创建逻辑
- if (register.onCreate) {
- const extendNodes = register.onCreate(node, data);
- if (extendNodes && addedNodes) {
- addedNodes.push(...extendNodes);
- }
- } else if (data.blocks && data.blocks.length > 0) {
- // 兼容老的写法
- if (!data.blocks[0].type) {
- this.addInlineBlocks(node, data.blocks, addedNodes);
- } else {
- this.addBlocksAsChildren(node, data.blocks as FlowNodeJSON[], addedNodes);
- }
- }
- if (isNew && !ignoreCreateEvent) {
- this.onNodeCreateEmitter.fire({
- node,
- data,
- });
- }
- return node;
- }
- addBlocksAsChildren(
- parent: FlowNodeEntity,
- blocks: FlowNodeJSON[],
- addedNodes?: FlowNodeEntity[]
- ): void {
- for (const block of blocks) {
- this.addNode(
- {
- ...block,
- parent,
- },
- addedNodes
- );
- }
- }
- /**
- * block 格式:
- * node: (最原始的 id)
- * blockIcon
- * inlineBlocks
- * block
- * blockOrderIcon
- * block
- * blockOrderIcon
- * @param node
- * @param blocks
- * @param addedNodes
- */
- addInlineBlocks(
- node: FlowNodeEntity,
- blocks: FlowNodeJSON[],
- addedNodes: FlowNodeEntity[] = []
- ): FlowNodeEntity[] {
- // 块列表开始节点,用来展示块的按钮
- const blockIconNode = this.addNode({
- id: `$blockIcon$${node.id}`,
- type: FlowNodeBaseType.BLOCK_ICON,
- originParent: node,
- parent: node,
- });
- addedNodes.push(blockIconNode);
- // inlineblocks 为空则不创建
- if (blocks.length > 0) {
- // 水平布局
- const inlineBlocksNode = this.addNode({
- id: `$inlineBlocks$${node.id}`,
- type: FlowNodeBaseType.INLINE_BLOCKS,
- originParent: node,
- parent: node,
- });
- addedNodes.push(inlineBlocksNode);
- blocks.forEach((blockData) => {
- this.addBlock(node, blockData, addedNodes);
- });
- }
- return addedNodes;
- }
- /**
- * 添加单个 block
- * @param target
- * @param blockData
- * @param addedNodes
- * @param parent 默认去找 $inlineBlocks$
- */
- addBlock(
- target: FlowNodeEntity | string,
- blockData: FlowNodeJSON,
- addedNodes?: FlowNodeEntity[],
- parent?: FlowNodeEntity,
- index?: number
- ): FlowNodeEntity {
- const node: FlowNodeEntity = typeof target === 'string' ? this.getNode(target)! : target;
- const { onBlockChildCreate } = node.getNodeRegistry();
- if (onBlockChildCreate) {
- return onBlockChildCreate(node, blockData, addedNodes);
- }
- parent = parent || this.getNode(`$inlineBlocks$${node.id}`);
- // 块节点会生成一个空的 Block 节点用来切割 Block
- const block = this.addNode({
- ...omit(blockData, 'blocks'),
- type: blockData.type || FlowNodeBaseType.BLOCK,
- originParent: node,
- parent,
- index,
- });
- if (blockData.meta?.defaultCollapsed) {
- block.collapsed = true;
- }
- // 块开始节点
- const blockOrderIcon = this.addNode({
- id: `$blockOrderIcon$${blockData.id}`,
- type: FlowNodeBaseType.BLOCK_ORDER_ICON,
- originParent: node,
- meta: blockData.meta,
- data: blockData.data,
- parent: block,
- });
- addedNodes?.push(block, blockOrderIcon);
- if (blockData.blocks) {
- this.addBlocksAsChildren(block, blockData.blocks as FlowNodeJSON[], addedNodes);
- }
- return block;
- }
- /**
- * 根据 id 获取节点
- * @param id
- */
- getNode(id: string): FlowNodeEntity | undefined {
- if (!id) return undefined;
- return this.entityManager.getEntityById<FlowNodeEntity>(id);
- }
- /**
- * 注册节点
- * @param registries
- */
- registerFlowNodes<T extends FlowNodeRegistry<any>>(...registries: T[]): void {
- registries.forEach((newRegistry) => {
- if (!newRegistry) {
- throw new Error('[FlowDocument] registerFlowNodes parameters get undefined registry.');
- }
- const preRegistry = this.registers.get(newRegistry.type);
- this.registers.set(newRegistry.type, {
- ...preRegistry,
- ...newRegistry,
- meta: {
- ...preRegistry?.meta,
- ...newRegistry?.meta,
- },
- });
- });
- }
- /**
- * 导出数据,可以重载
- */
- toJSON(): T | any {
- return {
- nodes: this.root.toJSON().blocks,
- };
- }
- /**
- * @deprecated
- * use `getNodeRegistry` instead
- */
- getNodeRegister<T extends FlowNodeRegistry = FlowNodeRegistry>(
- type: FlowNodeType,
- originParent?: FlowNodeEntity
- ): T {
- return this.getNodeRegistry<T>(type, originParent);
- }
- getNodeRegistry<T extends FlowNodeRegistry = FlowNodeRegistry>(
- type: FlowNodeType,
- originParent?: FlowNodeEntity
- ): T {
- const typeKey = `${type}_${originParent?.flowNodeType || ''}`;
- if (this.nodeRegistryCache.has(typeKey)) {
- return this.nodeRegistryCache.get(typeKey) as T;
- }
- const customDefaultRegistry = this.options.getNodeDefaultRegistry?.(type);
- let register = this.registers.get(type) || { type };
- const extendRegisters: FlowNodeRegistry[] = [];
- // 继承重载
- if (register.extend && this.registers.has(register.extend)) {
- register = FlowNodeRegistry.merge(
- this.getNodeRegistry(register.extend),
- register,
- register.type
- );
- }
- // 父节点覆盖
- if (originParent) {
- const extendRegister = this.getNodeRegistry(
- originParent.flowNodeType
- ).extendChildRegistries?.find((r) => r.type === type);
- if (extendRegister) {
- if (extendRegister.extend && this.registers.has(extendRegister.extend)) {
- extendRegisters.push(this.registers.get(extendRegister.extend)!);
- }
- extendRegisters.push(extendRegister);
- }
- }
- register = FlowNodeRegistry.extend(register, extendRegisters);
- const defaultNodeMeta = DEFAULT_FLOW_NODE_META(type, this);
- defaultNodeMeta.spacing =
- this.options?.constants?.[ConstantKeys.NODE_SPACING] || defaultNodeMeta.spacing;
- const res = {
- ...customDefaultRegistry,
- ...register,
- meta: {
- ...defaultNodeMeta,
- ...customDefaultRegistry?.meta,
- ...register.meta,
- },
- } as T;
- this.nodeRegistryCache.set(typeKey, res);
- return res;
- }
- /**
- * 节点注入数据
- * @param nodeDatas
- */
- registerNodeDatas(...nodeDatas: EntityDataRegistry[]): void {
- this.nodeDataRegistries.push(...nodeDatas);
- }
- /**
- * traverse all nodes, O(n)
- * R
- * |
- * +---1
- * | |
- * | +---1.1
- * | |
- * | +---1.2
- * | |
- * | +---1.3
- * | | |
- * | | +---1.3.1
- * | | |
- * | | +---1.3.2
- * | |
- * | +---1.4
- * |
- * +---2
- * |
- * +---2.1
- *
- * sort: [1, 1.1, 1.2, 1.3, 1.3.1, 1.3.2, 1.4, 2, 2.1]
- * @param fn
- * @param node
- * @param depth
- * @return isBreak
- */
- traverse(
- fn: (node: FlowNodeEntity, depth: number, index: number) => boolean | void,
- node = this.root,
- depth = 0
- ): boolean | void {
- return this.originTree.traverse(fn, node, depth);
- }
- get size(): number {
- return this.getAllNodes().length;
- }
- hasNode(nodeId: string): boolean {
- return !!this.entityManager.getEntityById(nodeId);
- }
- getAllNodes(): FlowNodeEntity[] {
- return this.entityManager.getEntities(FlowNodeEntity);
- }
- toString(): string {
- return this.originTree.toString();
- }
- /**
- * 返回需要渲染的数据
- */
- getRenderDatas<T extends EntityData>(
- dataRegistry: EntityDataRegistry<T>,
- containHiddenNodes = true
- ): T[] {
- const result: T[] = [];
- this.renderTree.traverse((node) => {
- if (!containHiddenNodes && node.hidden) return;
- result.push(node.getData<T>(dataRegistry)!);
- });
- return result;
- }
- /**
- * 移动节点
- * @param param0
- * @returns
- */
- moveNodes({
- dropNodeId,
- sortNodeIds,
- inside = false,
- }: {
- dropNodeId: string;
- sortNodeIds: string[];
- inside?: boolean;
- }) {
- const dropEntity = this.getNode(dropNodeId);
- if (!dropEntity) {
- return;
- }
- const sortNodes = sortNodeIds.map((id) => this.getNode(id)!);
- // 按照顺序一个个移动到目标节点下
- this.entityManager.changeEntityLocked = true;
- for (const node of sortNodes.reverse()) {
- if (inside) {
- this.originTree.addChild(dropEntity, node, 0);
- } else {
- this.originTree.insertAfter(dropEntity, node);
- }
- }
- this.entityManager.changeEntityLocked = false;
- this.fireRender();
- }
- /**
- * 移动子节点
- * @param param0
- * @returns
- */
- moveChildNodes({
- toParentId,
- toIndex,
- nodeIds,
- }: {
- toParentId: string;
- nodeIds: string[];
- toIndex: number;
- }) {
- if (nodeIds.length === 0) {
- return;
- }
- const toParent = this.getNode(toParentId);
- if (!toParent) {
- return;
- }
- this.entityManager.changeEntityLocked = true;
- this.originTree.moveChilds(
- toParent,
- nodeIds.map((nodeId) => this.getNode(nodeId) as FlowNodeEntity),
- toIndex
- );
- this.entityManager.changeEntityLocked = false;
- this.fireRender();
- }
- /**
- * 注册布局
- * @param layout
- */
- registerLayout(layout: FlowLayout) {
- this.layouts.push(layout);
- }
- /**
- * 更新布局
- * @param layoutKey
- */
- setLayout(layoutKey: string) {
- if (this.currentLayoutKey === layoutKey) return;
- const layout = this.layouts.find((layout) => layout.name === layoutKey);
- if (!layout) return;
- this.currentLayoutKey = layoutKey;
- this.transformer.clear();
- layout.reload?.();
- this.fireRender();
- this.onLayoutChangeEmitter.fire(this.layout);
- }
- /**
- * 切换垂直或水平布局
- */
- toggleFixedLayout() {
- this.setLayout(
- this.layout.name === FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
- ? FlowLayoutDefault.VERTICAL_FIXED_LAYOUT
- : FlowLayoutDefault.HORIZONTAL_FIXED_LAYOUT
- );
- }
- dispose() {
- this.registers.clear();
- this.nodeRegistryCache.clear();
- this.originTree.dispose();
- this.renderTree.dispose();
- this.onNodeUpdateEmitter.dispose();
- this.onNodeCreateEmitter.dispose();
- this.onNodeDisposeEmitter.dispose();
- this.onLayoutChangeEmitter.dispose();
- }
- }
|