In-depth React Flow Renderer (2): Building a drag action bar

In the previous blog, we introduced how to start the React Flow Renderer and create a basic workflow interface. This article will go further and focus on how to build a draggable action bar, which is one of the entrances for users to interact with the workflow.

introduction

The action bar is part of the workflow interface, usually located on the side or top of the interface. It contains a list of components from which the user can drag nodes onto the canvas. In our example, the action bar will be on the left side of the interface.

Create action bar component

First, let's take a look at how to create an action bar component. In our example, we used a React component named Slider. This component receives a componentsproperty called , which contains a list of available components.

// Slider/index.jsx

import React from 'react';
//项目中自定义的手风琴组件,请你使用自己项目中的组件
import {
    
     CustomAccordion } from '@/components/CustomeAccordion';

// 模拟节点
const mockComponent = [
  {
    
    
    'name': 'clear alarm',
    'type': 'ACTION',
    'clazz': 'action.TbClearAlarmNode'
  },
  {
    
    
    'name': 'create alarm',
    'type': 'ACTION',
    'clazz': 'action.TbCreateAlarmNode'
  },
  {
    
    
    'name': 'device profile',
    'type': 'ACTION',
    'clazz': 'profile.TbDeviceProfileNode'
  },
  {
    
    
    'name': 'log',
    'type': 'ACTION',
    'clazz': 'action.TbLogNode'
  },
  {
    
    
    'name': 'message type switch',
    'type': 'FILTER',
    'clazz': 'filter.TbMsgTypeSwitchNode'
  },
  {
    
    
    'name': 'rpc call request',
    'type': 'ACTION',
    'clazz': 'rpc.TbSendRPCRequestNode'
  },
  {
    
    
    'name': 'rule chain',
    'type': 'FLOW',
    'clazz': 'flow.TbRuleChainInputNode'
  },
  {
    
    
    'name': 'save attributes',
    'type': 'ACTION',
    'clazz': 'telemetry.TbMsgAttributesNode'
  },
  {
    
    
    'name': 'save timeseries',
    'type': 'ACTION',
    'clazz': 'telemetry.TbMsgTimeseriesNode'
  },
  {
    
    
    'name': 'script',
    'type': 'TRANSFORMATION',
    'clazz': 'transform.TbTransformMsgNode'
  }
];

export enum RuleNodeType {
    
    
  FILTER = 'FILTER',
  ENRICHMENT = 'ENRICHMENT',
  TRANSFORMATION = 'TRANSFORMATION',
  ACTION = 'ACTION',
  EXTERNAL = 'EXTERNAL',
  FLOW = 'FLOW',
  UNKNOWN = 'UNKNOWN',
  INPUT = 'INPUT',
}

export const ruleNodeTypeDescriptors = new Map<RuleNodeType, any>(
  [
    [
      RuleNodeType.FILTER,
      {
    
    
        value: RuleNodeType.FILTER,
        name: 'rulenode.type-filter',
        details: 'rulenode.type-filter-details',
        nodeClass: 'tb-filter-type',
        icon: 'filter_list'
      }
    ],
    [
      RuleNodeType.ENRICHMENT,
      {
    
    
        value: RuleNodeType.ENRICHMENT,
        name: 'rulenode.type-enrichment',
        details: 'rulenode.type-enrichment-details',
        nodeClass: 'tb-enrichment-type',
        icon: 'playlist_add'
      }
    ],
    [
      RuleNodeType.TRANSFORMATION,
      {
    
    
        value: RuleNodeType.TRANSFORMATION,
        name: 'rulenode.type-transformation',
        details: 'rulenode.type-transformation-details',
        nodeClass: 'tb-transformation-type',
        icon: 'transform'
      }
    ],
    [
      RuleNodeType.ACTION,
      {
    
    
        value: RuleNodeType.ACTION,
        name: 'rulenode.type-action',
        details: 'rulenode.type-action-details',
        nodeClass: 'tb-action-type',
        icon: 'flash_on'
      }
    ],
    [
      RuleNodeType.EXTERNAL,
      {
    
    
        value: RuleNodeType.EXTERNAL,
        name: 'rulenode.type-external',
        details: 'rulenode.type-external-details',
        nodeClass: 'tb-external-type',
        icon: 'cloud_upload'
      }
    ],
    [
      RuleNodeType.FLOW,
      {
    
    
        value: RuleNodeType.FLOW,
        name: 'rulenode.type-flow',
        details: 'rulenode.type-flow-details',
        nodeClass: 'tb-flow-type',
        icon: 'settings_ethernet'
      }
    ],
    [
      RuleNodeType.INPUT,
      {
    
    
        value: RuleNodeType.INPUT,
        name: 'rulenode.type-input',
        details: 'rulenode.type-input-details',
        nodeClass: 'tb-input-type',
        icon: 'input',
        special: true
      }
    ],
    [
      RuleNodeType.UNKNOWN,
      {
    
    
        value: RuleNodeType.UNKNOWN,
        name: 'rulenode.type-unknown',
        details: 'rulenode.type-unknown-details',
        nodeClass: 'tb-unknown-type',
        icon: 'help_outline'
      }
    ]
  ]
);

const classMap = new Map([
  ['ACTION', 'relation-node'],
  ['input', 'input-node'],
  ['FILTER', 'filter-node'],
  ['ENRICHMENT', 'enrichment-node'],
  ['TRANSFORMATION', 'transformation-node'],
  ['EXTERNAL', 'external-node'],
  ['FLOW', 'flow-node']
]);

// const allowType = ruleNodeTypeComponentTypes;
const allowNodesClazz = [
  'telemetry.TbMsgAttributesNode',
  'filter.TbMsgTypeSwitchNode',
  'action.TbLogNode',
  'rpc.TbSendRPCRequestNode',
  'profile.TbDeviceProfileNode',
  'telemetry.TbMsgTimeseriesNode',
  'action.TbCreateAlarmNode',
  'action.TbClearAlarmNode',
  'flow.TbRuleChainInputNode',
  'transform.TbTransformMsgNode'
];

export default function Slider() {
    
    
  const [allowType, setAllowType] = React.useState<any>(['input']);
  const [allowedNodes, setAllowedNodes] = React.useState<any>([]);

  React.useEffect(() => {
    
    
    // 将组件按名称进行排序
    const sortedComponents = mockComponent?.sort((a: any, b: any) =>
      a.name?.localeCompare(b.name)
    );

    // 过滤出符合条件的组件并拼接到allowedNodes数组中
    const filteredComponents =
      sortedComponents?.filter((component: any) =>
        allowNodesClazz.includes(component.clazz)
      ) || [];
    const updatedAllowedNodes = [...filteredComponents];

    // 获取所有组件的类型,并和allowType数组进行合并
    const updatedTypes = updatedAllowedNodes.map((component) => component.type);

    // 去除重复的节点并更新allowedNodes状态
    setAllowedNodes(Array.from(new Set(updatedAllowedNodes)));

    // 去除重复的类型并更新allowType状态(如果为空数组,则设置为默认值)
    setAllowType(Array.from(new Set(updatedTypes)) || []);
  }, []);

  return (
    <div className="sider">
      {
    
    allowType.map((type: any) =>
      //自定义手风琴,项目中使用的是mui,你可以使用其他组件库,这里就不贴出手风琴的代码了,请你根据你的项目,使用对应的组件。如果不需要手风琴组件。可以拥<div>来代替
        <CustomAccordion
          title={
    
    
            ruleNodeTypeDescriptors.get(type as any)?.name as string}
          key={
    
    type}
        >
          <div className="nodes">
            {
    
    allowedNodes
              .filter((node: any) => node.type === type)
              .map((x: any, i: number) =>
                <div
                  key={
    
    `${
      
      x.type}-${
      
      i}`}
                  className={
    
    `sider-node ${
      
      
                    classMap.get(x.type) || 'default-node'
                  }`}
                  onDragStart={
    
    (e) => onDragStart(e, x)}
                  draggable
                >
                  <div>{
    
    x.name}</div>
                  {
    
    /* 黑色遮罩层 */}
                  <div className="overlay"></div>
                </div>
              )}
          </div>
        </CustomAccordion>
      )}
    </div>
  );
}

In the above code, we define a Slidercomponent that maps a list of components into expandable custom components and adds drag and drop support for each component.

Drag event handling

The core function of the drag operation bar is how to handle drag events. In our example, we use onDragStarta function to handle the node drag start event. This function will set the type and name of the dragged node, and record the complete information of the dragged node.

/**
 * 处理节点拖拽开始事件的回调函数
  * @param {Event} evt - 拖拽事件对象
  * @param {Object} node - 被拖拽的节点对象
  */
const onDragStart = (evt: any, node: any) => {
    
    
  // 记录被拖拽的节点类型和名称
  evt.dataTransfer.setData(
    'application/reactflow',
    node.type + ',' + node.name
  );
  // 记录被拖拽的节点的完整信息
  evt.dataTransfer.setData('application/reactflownode', JSON.stringify(node));
  // 设置拖拽效果为移动
  evt.dataTransfer.effectAllowed = 'move';
};

This function will be triggered when the user drags the node, and will set the relevant data for subsequent use when placing the node on the canvas.

Summarize

By creating a draggable action bar, users can easily drag and drop nodes onto the workflow canvas. In this article, we learned how to create an action bar component, handle drag events, and display a list of components to the user. In the next blog, we will continue to delve into other aspects of the workflow interface, including canvas interactivity and node customization. Stay tuned!

Guess you like

Origin blog.csdn.net/m0_73117087/article/details/133353817