Flowgram.ai插件开发指南:自定义节点交互行为全解析

Flowgram.ai插件开发指南:自定义节点交互行为全解析

【免费下载链接】flowgram.ai 【免费下载链接】flowgram.ai 项目地址: https://gitcode.com/gh_mirrors/fl/flowgram.ai

引言

在Flowgram.ai的开发过程中,节点是核心组件,其交互行为直接影响用户体验。本文将深入探讨如何开发自定义节点交互行为的插件,重点介绍free-node-panel-plugin的实现原理和使用方法,帮助开发者快速上手插件开发。

插件基础

插件结构

Flowgram.ai的插件通常包含以下几个核心部分:

  • package.json:插件的元数据和依赖配置
  • src/index.ts:插件的入口文件,导出插件创建函数
  • src/create-plugin.ts:插件的创建逻辑
  • src/service.ts:插件的核心服务实现
  • src/type.ts:插件相关的类型定义
  • src/utils.ts:插件的工具函数

以下是free-node-panel-plugin的目录结构:

free-node-panel-plugin/
├── package.json
├── src/
│   ├── index.ts
│   ├── create-plugin.ts
│   ├── service.ts
│   ├── type.ts
│   ├── utils.ts
│   └── layer.ts
└── tsconfig.json

插件注册流程

Flowgram.ai插件的注册流程如下:

mermaid

free-node-panel-plugin详解

package.json配置

package.json是插件的元数据文件,定义了插件的名称、版本、依赖等信息。以下是free-node-panel-plugin的关键配置:

{
  "name": "@flowgram.ai/free-node-panel-plugin",
  "version": "0.1.8",
  "exports": {
    "types": "./dist/index.d.ts",
    "import": "./dist/esm/index.js",
    "require": "./dist/index.js"
  },
  "dependencies": {
    "@flowgram.ai/core": "workspace:*",
    "@flowgram.ai/document": "workspace:*",
    "@flowgram.ai/free-history-plugin": "workspace:*",
    "@flowgram.ai/free-layout-core": "workspace:*",
    "@flowgram.ai/renderer": "workspace:*",
    "@flowgram.ai/utils": "workspace:*",
    "inversify": "^6.0.1",
    "reflect-metadata": "~0.2.2",
    "lodash-es": "^4.17.21"
  },
  "peerDependencies": {
    "react": ">=16.8",
    "react-dom": ">=16.8",
    "styled-components": ">=4"
  }
}
  • exports字段定义了模块的导出方式,支持TypeScript类型定义、ES模块和CommonJS模块
  • dependencies字段列出了插件依赖的核心库,使用workspace:*表示使用工作区中的本地包
  • peerDependencies字段指定了插件运行所需的宿主环境依赖

插件入口文件

src/index.ts是插件的入口文件,负责导出插件的创建函数和相关类型:

export { createFreeNodePanelPlugin } from './create-plugin';
export { WorkflowNodePanelService } from './service';
export type {
  NodePanelResult,
  NodePanelRenderProps,
  NodePanelRender,
  NodePanelLayerOptions as NodePanelServiceOptions,
  NodePanelPluginOptions,
} from './type';
export { type IWorkflowNodePanelUtils, WorkflowNodePanelUtils } from './utils';

插件创建逻辑

src/create-plugin.ts实现了插件的创建逻辑,使用definePluginCreator函数定义插件:

import { definePluginCreator, type PluginBindConfig, type PluginContext } from '@flowgram.ai/core';

import { NodePanelPluginOptions } from './type';
import { WorkflowNodePanelService } from './service';
import { WorkflowNodePanelLayer } from './layer';

export const createFreeNodePanelPlugin = definePluginCreator({
  onBind({ bind }: PluginBindConfig) {
    bind(WorkflowNodePanelService).toSelf().inSingletonScope();
  },
  onInit: (ctx: PluginContext, opts: NodePanelPluginOptions) => {
    ctx.playground.registerLayer(WorkflowNodePanelLayer, {
      renderer: opts.renderer,
    });
  },
  onDispose: (ctx: PluginContext) => {
    const nodePanelService = ctx.get(WorkflowNodePanelService);
    nodePanelService.dispose();
  },
});

插件创建过程包含三个关键生命周期钩子:

  1. onBind:用于绑定依赖注入服务
  2. onInit:插件初始化时调用,用于注册UI层等
  3. onDispose:插件销毁时调用,用于清理资源

核心服务实现

WorkflowNodePanelService

WorkflowNodePanelServicefree-node-panel-plugin的核心服务,负责节点面板的调用和节点创建逻辑。

依赖注入

服务使用InversifyJS进行依赖注入,注入了Flowgram.ai的核心服务:

@injectable()
export class WorkflowNodePanelService {
  @inject(WorkflowDocument) private readonly document: WorkflowDocument;
  @inject(WorkflowDragService) private readonly dragService: WorkflowDragService;
  @inject(WorkflowSelectService) private readonly selectService: WorkflowSelectService;
  @inject(WorkflowLinesManager) private readonly linesManager: WorkflowLinesManager;
  @inject(PlaygroundConfigEntity) private readonly playgroundConfig: PlaygroundConfigEntity;
  @inject(HistoryService) private readonly historyService: HistoryService;
  
  // ...
}
节点面板调用

callNodePanel方法用于唤起节点面板,并处理用户选择结果:

public async call(
  callParams: NodePanelCallParams
): Promise<WorkflowNodeEntity | WorkflowNodeEntity[] | undefined> {
  const {
    panelPosition,
    fromPort,
    enableMultiAdd = false,
    panelProps = {},
    containerNode,
    afterAddNode,
  } = callParams;

  if (!panelPosition || this.playgroundConfig.readonly) {
    return;
  }

  const nodes: WorkflowNodeEntity[] = [];

  return new Promise((resolve) => {
    this.callNodePanel({
      position: panelPosition,
      enableMultiAdd,
      panelProps,
      containerNode: WorkflowNodePanelUtils.getContainerNode({
        fromPort,
        containerNode,
      }),
      onSelect: async (panelParams?: NodePanelResult) => {
        const node = await this.addNode(callParams, panelParams);
        afterAddNode?.(node);
        if (!enableMultiAdd) {
          resolve(node);
        } else if (node) {
          nodes.push(node);
        }
      },
      onClose: () => {
        resolve(enableMultiAdd ? nodes : undefined);
      },
    });
  });
}
节点创建

addNode方法处理节点的创建逻辑,包括位置计算、连线创建和节点拖拽等:

private async addNode(
  callParams: NodePanelCallParams,
  panelParams: NodePanelResult
): Promise<WorkflowNodeEntity | undefined> {
  const {
    panelPosition,
    fromPort,
    toPort,
    canAddNode,
    autoOffsetPadding = { x: 100, y: 100 },
    enableBuildLine = false,
    enableSelectPosition = false,
    enableAutoOffset = false,
    enableDragNode = false,
  } = callParams;

  // 坐标计算逻辑
  const nodePosition: PositionSchema = callParams.customPosition
    ? callParams.customPosition({ nodeType, selectPosition })
    : WorkflowNodePanelUtils.adjustNodePosition({
        nodeType,
        position: enableSelectPosition ? selectPosition : panelPosition,
        fromPort,
        toPort,
        containerNode,
        document: this.document,
        dragService: this.dragService,
      });

  // 创建节点
  const node: WorkflowNodeEntity = this.document.createWorkflowNodeByType(
    nodeType,
    nodePosition,
    nodeJSON ?? ({} as WorkflowNodeJSON),
    containerNode?.id
  );

  // 后续处理:连线创建、节点拖拽等
  if (enableBuildLine) {
    WorkflowNodePanelUtils.buildLine({
      fromPort,
      node,
      toPort,
      linesManager: this.linesManager,
    });
  }

  if (enableDragNode) {
    this.selectService.selectNode(node);
    this.dragService.startDragSelectedNodes(selectEvent);
  }

  return node;
}

工具类

WorkflowNodePanelUtils提供了一系列辅助方法,用于节点位置调整、连线创建等:

export class WorkflowNodePanelUtils {
  static adjustNodePosition(...): PositionSchema {
    // 计算节点位置
  }

  static buildLine(...): void {
    // 创建连线
  }

  static subNodesAutoOffset(...): void {
    // 子节点自动偏移
  }
}

插件开发实战

创建自定义节点交互插件

以下是创建自定义节点交互插件的步骤:

  1. 初始化插件项目
mkdir custom-node-interaction-plugin
cd custom-node-interaction-plugin
npm init -y
  1. 安装依赖
npm install @flowgram.ai/core @flowgram.ai/free-layout-core inversify reflect-metadata
npm install -D typescript tsup
  1. 编写插件代码

创建src/create-plugin.ts

import { definePluginCreator } from '@flowgram.ai/core';
import { CustomNodeInteractionService } from './service';

export const createCustomNodeInteractionPlugin = definePluginCreator({
  onBind({ bind }) {
    bind(CustomNodeInteractionService).toSelf().inSingletonScope();
  },
  onInit: (ctx) => {
    const service = ctx.get(CustomNodeInteractionService);
    service.initialize();
  },
});

创建src/service.ts

import { injectable, inject } from 'inversify';
import { WorkflowNodeEntity } from '@flowgram.ai/free-layout-core';
import { WorkflowSelectService } from '@flowgram.ai/free-layout-core';

@injectable()
export class CustomNodeInteractionService {
  @inject(WorkflowSelectService) private readonly selectService: WorkflowSelectService;

  initialize() {
    // 注册自定义交互逻辑
    this.selectService.onNodeSelect(this.handleNodeSelect);
  }

  private handleNodeSelect = (node: WorkflowNodeEntity) => {
    // 自定义节点选中逻辑
    console.log('Node selected:', node.id);
    // 添加自定义交互行为
  };
}
  1. 导出插件

创建src/index.ts

export { createCustomNodeInteractionPlugin } from './create-plugin';
export { CustomNodeInteractionService } from './service';
  1. 构建插件

配置tsup.config.js

export default {
  entry: ['src/index.ts'],
  format: ['cjs', 'esm'],
  dts: true,
  sourcemap: true,
};

添加构建脚本到package.json

{
  "scripts": {
    "build": "tsup"
  }
}

在Flowgram.ai中使用插件

在Flowgram.ai应用中注册并使用自定义插件:

import { createPlayground } from '@flowgram.ai/core';
import { createCustomNodeInteractionPlugin } from 'custom-node-interaction-plugin';

const playground = createPlayground({
  container: document.getElementById('app'),
  plugins: [
    createCustomNodeInteractionPlugin(),
    // 其他插件
  ],
});

常见问题解决

依赖冲突

如果插件依赖与Flowgram.ai核心依赖版本冲突,可使用peerDependencies指定兼容版本范围:

{
  "peerDependencies": {
    "@flowgram.ai/core": ">=0.1.0 <0.2.0"
  }
}

服务注入失败

确保在onBind钩子中正确绑定服务,并使用@injectable()装饰器:

@injectable()
export class MyService {
  // ...
}

// 在插件中绑定
onBind({ bind }) {
  bind(MyService).toSelf().inSingletonScope();
}

UI层渲染问题

如果自定义UI层不显示,检查是否在onInit中正确注册:

onInit: (ctx, opts) => {
  ctx.playground.registerLayer(MyCustomLayer, {
    // 配置项
  });
}

总结

本文详细介绍了Flowgram.ai插件开发的核心概念和实践方法,以free-node-panel-plugin为例深入解析了插件的结构和实现原理。通过本文的学习,开发者可以掌握自定义节点交互行为插件的开发技巧,为Flowgram.ai扩展更多强大的功能。

插件开发是Flowgram.ai生态的重要组成部分,希望本文能够帮助开发者更好地参与到Flowgram.ai的生态建设中。如需进一步了解插件开发,可参考Flowgram.ai的官方文档和示例代码。

【免费下载链接】flowgram.ai 【免费下载链接】flowgram.ai 项目地址: https://gitcode.com/gh_mirrors/fl/flowgram.ai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值