diff --git a/.eslintrc.js b/.eslintrc.js index b07c449..7b186a5 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -53,5 +53,6 @@ module.exports = { '@typescript-eslint/no-floating-promises': 'off', 'generator-star-spacing': 'off', '@typescript-eslint/brace-style': 'off', + 'multiline-ternary': 'off', } } diff --git a/packages/flow/src/Diagrams/Compiler/graph.ts b/packages/flow/src/Diagrams/Compiler/graph.ts index 17f07e2..65dcff3 100644 --- a/packages/flow/src/Diagrams/Compiler/graph.ts +++ b/packages/flow/src/Diagrams/Compiler/graph.ts @@ -1,6 +1,6 @@ import { type Edge, type Node } from 'reactflow'; -import { OperatorMap, getOperatorFromNode } from '../Operators'; -import { type IBaseNodeData } from '../Nodes/types'; +import { getOperatorFromNode } from '../Operators'; +import { type IMetaOperatorData } from '../Operators/types'; import { EosCoreSymbol } from './runtime'; import { type Layer, flatLayer } from '../State/Layer'; import { message } from 'antd'; @@ -168,10 +168,9 @@ function generateBlock( const sortedNode = nodeGraph.getSortedNodes(); const declarations: string[] = sortedNode - .map((node: Node) => { - const Operator = - getOperatorFromNode(node) || OperatorMap.get(node.data.operatorName); - return Operator?.generateBlockDeclarations?.({ + .map((node: Node) => { + const operator = getOperatorFromNode(node); + return operator?.generateBlockDeclarations?.({ node, nodeGraph, formatVariableName, @@ -182,10 +181,9 @@ function generateBlock( .filter((x): x is string => Boolean(x)); const relations: string[] = sortedNode - .map((node: Node) => { - const Operator = - getOperatorFromNode(node) || OperatorMap.get(node.data.operatorName); - return Operator?.generateBlockRelation?.({ + .map((node: Node) => { + const operator = getOperatorFromNode(node); + return operator?.generateBlockRelation?.({ node, nodeGraph, formatVariableName, @@ -196,10 +194,9 @@ function generateBlock( .filter((x): x is string => Boolean(x)); const outputs: string[] = sortedNode - .map((node: Node) => { - const Operator = - getOperatorFromNode(node) || OperatorMap.get(node.data.operatorName); - return Operator?.generateBlockOutput?.({ + .map((node: Node) => { + const operator = getOperatorFromNode(node); + return operator?.generateBlockOutput?.({ node, nodeGraph, formatVariableName, diff --git a/packages/flow/src/Diagrams/Nodes/ContainerNode/ContainerNode.module.less b/packages/flow/src/Diagrams/Nodes/ContainerNode/ContainerNode.module.less deleted file mode 100644 index 667f53a..0000000 --- a/packages/flow/src/Diagrams/Nodes/ContainerNode/ContainerNode.module.less +++ /dev/null @@ -1,3 +0,0 @@ -.container { - --color-node-theme: #FF8080; -} \ No newline at end of file diff --git a/packages/flow/src/Diagrams/Nodes/ContainerNode/index.tsx b/packages/flow/src/Diagrams/Nodes/ContainerNode/index.tsx deleted file mode 100644 index 851f7d0..0000000 --- a/packages/flow/src/Diagrams/Nodes/ContainerNode/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { type NodeProps } from 'reactflow'; -import { BaseNode } from '../components/BaseNode'; -import css from './ContainerNode.module.less'; -import { useDiagramsContext } from '../../State/DiagramsProvider'; - -export function ContainerNode(props: NodeProps) { - const { data } = props; - const { setActiveLayerId } = useDiagramsContext(); - - return ( - { - setActiveLayerId(data.layerId); - }} - {...props} - className={css.container} - /> - ); -} diff --git a/packages/flow/src/Diagrams/Nodes/DoNode/index.tsx b/packages/flow/src/Diagrams/Nodes/DoNode/index.tsx index d831937..186e3a3 100644 --- a/packages/flow/src/Diagrams/Nodes/DoNode/index.tsx +++ b/packages/flow/src/Diagrams/Nodes/DoNode/index.tsx @@ -1,31 +1,25 @@ import { type NodeProps } from 'reactflow'; -import { BaseNode } from '../components/BaseNode'; import css from './DoNode.module.less'; import { Button, Modal } from 'antd'; import { useState } from 'react'; import { Editor } from '../../components/Editor'; +/** @deprecated */ export function DoNode(props: NodeProps) { const [visible, setVisible] = useState(false); return ( <> - { - setVisible(true); - }} - > - 点击编辑 - - } - className={css.container} - /> + > }) { const { updateNode } = useDiagramsActions(); const operator = getOperatorFromNode(node); + function renderSinglePort(port: EndPoint) { + if (port.type !== 'source' && port.type !== 'target') { + return
unknown type: {port.type}
; + } + + return ( +
+
+ {port.label || port.variableName} +
+ +
+ ); + } + if (!endPointOptions?.endPointList?.length || !operator) { return null; } @@ -27,31 +51,7 @@ export function NodePorts(props: { node: Partial> }) {
{item.label}
{item.children?.map((port) => { - if (port.type !== 'source' && port.type !== 'target') { - return
unknown type: {port.type}
; - } - - return ( -
-
- {port.label || port.variableName} -
- -
- ); + return renderSinglePort(port); })}
{item.allowAddAndRemoveChildren && ( @@ -75,7 +75,7 @@ export function NodePorts(props: { node: Partial> }) { ); } else { - return null; + return renderSinglePort(item); } })} diff --git a/packages/flow/src/Diagrams/Nodes/Node/index.tsx b/packages/flow/src/Diagrams/Nodes/Node/index.tsx index a7d6bec..651ec84 100644 --- a/packages/flow/src/Diagrams/Nodes/Node/index.tsx +++ b/packages/flow/src/Diagrams/Nodes/Node/index.tsx @@ -5,18 +5,53 @@ import css from './Node.module.less'; import { type IMetaOperatorData } from '../../Operators/types'; import { NodePorts } from './components/NodePorts'; import { useDiagramsHookOption } from '../../State/DiagramsProvider'; +import { message } from 'antd'; + +export interface INodeProps { + showValue?: boolean; + getBriefValue?: () => { value: string; hasDetail?: boolean }; + getDetailValue?: () => string; +} export function Node(props: NodeProps) { const operator = getOperatorFromNode(props); const { currentStateRef, actionsRef } = useDiagramsHookOption(); const { selected, data } = props; + const nodeOptions: INodeProps | undefined = operator?.getNodeProps?.(props); + + function renderValueSection() { + if (!nodeOptions?.showValue) { + return null; + } + + const briefValue = nodeOptions?.getBriefValue?.(); + + if (!briefValue) { + return null; + } + + return ( +
{ + if (briefValue.hasDetail) { + message.info(nodeOptions?.getDetailValue?.() || ''); + } + }} + > + {briefValue.value} +
+ ); + } + const valueSection = renderValueSection(); return (
) { }); }} > - {operator?.description && ( -
+
+ {operator?.description && (
{operator?.description}
- {/*
{data.label}
*/} - {/*
{title}
*/} -
- )} + )} + {valueSection &&
{valueSection}
} + {/*
{data.label}
*/} +
diff --git a/packages/flow/src/Diagrams/Nodes/NodeTypeEnum.ts b/packages/flow/src/Diagrams/Nodes/NodeTypeEnum.ts index a4c218a..4608b9f 100644 --- a/packages/flow/src/Diagrams/Nodes/NodeTypeEnum.ts +++ b/packages/flow/src/Diagrams/Nodes/NodeTypeEnum.ts @@ -1,18 +1,3 @@ export enum NodeTypeEnum { - /** 状态节点 */ - StateNode = 'StateNode', - /** 组合节点 */ - CompositionNode = 'CompositionNode', - /** 流操作 */ - StreamOperatorNode = 'StreamOperatorNode', - /** 值操作 */ - ValueOperatorNode = 'ValueOperatorNode', - /** - * 容器 - * @see https://reactflow.dev/learn/layouting/sub-flows#adding-child-nodes - * @see https://reactflow.dev/examples/layout/sub-flows - * */ - ContainerNode = 'ContainerNode', - DoNode = 'DoNode', Node = 'Node', } diff --git a/packages/flow/src/Diagrams/Nodes/StateNode/StateNode.module.less b/packages/flow/src/Diagrams/Nodes/StateNode/StateNode.module.less deleted file mode 100644 index f2a6948..0000000 --- a/packages/flow/src/Diagrams/Nodes/StateNode/StateNode.module.less +++ /dev/null @@ -1,10 +0,0 @@ -.state-node__container { - --color-node-theme: #0079FF; -} - -.value-container { - text-align: center; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} \ No newline at end of file diff --git a/packages/flow/src/Diagrams/Nodes/StateNode/index.tsx b/packages/flow/src/Diagrams/Nodes/StateNode/index.tsx deleted file mode 100644 index 0d9df14..0000000 --- a/packages/flow/src/Diagrams/Nodes/StateNode/index.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { type NodeProps } from 'reactflow'; -import { BaseNode } from '../components/BaseNode'; -import { type IStateNodeData, StateNodeValueTypeEnum } from '../types'; -import css from './StateNode.module.less'; -import { message } from 'antd'; - -export function StateNode(props: NodeProps) { - const { - data: { value, valueType }, - } = props; - - return ( - { - if (valueType === StateNodeValueTypeEnum.Object) { - message.info(value); - } - }} - > - {valueType === StateNodeValueTypeEnum.Object - ? 'Object(click to view)' - : JSON.stringify(value)} - - } - {...props} - className={css['state-node__container']} - /> - ); -} diff --git a/packages/flow/src/Diagrams/Nodes/StreamOperatorNode/StreamOperatorNode.module.less b/packages/flow/src/Diagrams/Nodes/StreamOperatorNode/StreamOperatorNode.module.less deleted file mode 100644 index d19a23c..0000000 --- a/packages/flow/src/Diagrams/Nodes/StreamOperatorNode/StreamOperatorNode.module.less +++ /dev/null @@ -1,3 +0,0 @@ -.container { - --color-node-theme: #FF0060; -} \ No newline at end of file diff --git a/packages/flow/src/Diagrams/Nodes/StreamOperatorNode/index.tsx b/packages/flow/src/Diagrams/Nodes/StreamOperatorNode/index.tsx deleted file mode 100644 index cc0bd94..0000000 --- a/packages/flow/src/Diagrams/Nodes/StreamOperatorNode/index.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { type NodeProps, type Node } from 'reactflow'; -import { BaseNode } from '../components/BaseNode'; -import { NodePort, type IStreamOperatorNodeData } from '../types'; -import { useDiagramsActions } from '../../State/DiagramsProvider'; -import css from './StreamOperatorNode.module.less'; - -export function StreamOperatorNode(props: NodeProps) { - const { data, id } = props; - const { updateNode } = useDiagramsActions(); - - return ( - { - updateNode(id, (node: Node) => ({ - ...node, - data: { - ...node.data, - targetPorts: node.data.targetPorts.concat( - new NodePort({ - label: `input-${node.data.targetPorts?.length + 1 || 0}`, - }), - ), - }, - })); - } - : undefined - } - {...props} - className={css.container} - /> - ); -} diff --git a/packages/flow/src/Diagrams/Nodes/components/BaseNode/BaseNode.module.less b/packages/flow/src/Diagrams/Nodes/components/BaseNode/BaseNode.module.less deleted file mode 100644 index e182d9c..0000000 --- a/packages/flow/src/Diagrams/Nodes/components/BaseNode/BaseNode.module.less +++ /dev/null @@ -1,301 +0,0 @@ -.node__container { - --color-bg: #2e2e2e; - --content-padding: 12px; - - display: grid; - - color: #e2e2e2; - font-size: 16px; - gap: 4px; - - min-width: 125px; - max-width: 200px; -} - -.node__operator-name { - font-size: 12px; -} - -.node__content { - min-height: 40px; - - border: 1px solid transparent; - border-radius: 4px; - - background-color: var(--color-bg); - - padding: var(--content-padding) var(--content-padding); - padding-top: calc(var(--content-padding) / 2); - - transition: border-color cubic-bezier(0.215, 0.610, 0.355, 1) .25s; -} - -.is-selected { - .node__content { - border-color: var(--color-node-theme, #9a7844); - } -} - -.node__port-list { - @gap: 8px; - @padding-left: 16px; - --nest-level: 1; - --padding-left: calc(@padding-left * var(--nest-level)); - --last-padding-left: calc(@padding-left * (var(--nest-level, 0) - 1)); - - list-style-type: none; - margin: 0; - padding: 0; - - font-size: 12px; - - display: grid; - gap: @gap; - - >li { - position: relative; - } - - &:not(.is-sub-list) { - margin-top: 16px; - } - - &.is-sub-list { - >li:not(.node__port-children) { - &::before { - content: ''; - position: absolute; - top: -@gap; - bottom: 50%; - - width: @padding-left; - border: 1px solid white; - } - - &::after { - content: ''; - position: absolute; - top: 50%; - bottom: -4px; - width: @padding-left; - border: 1px solid white; - transform: translateY(-2px); - } - - >.node__port-dot { - position: absolute; - top: 50%; - width: 4px; - height: 4px; - border-radius: 50%; - background-color: white; - transform: translateY(-50%); - } - - &.is-last { - &::before { - bottom: 50%; - } - - &::after { - display: none; - } - } - } - - >.node__port-children:not(:last-child) { - &::before { - content: ''; - position: absolute; - top: -@gap; - bottom: -1px; - width: 1px; - background-color: white; - } - } - } - - :global { - .react-flow__handle { - width: 9px; - height: 9px; - } - } - - &.source { - text-align: right; - - :global { - .react-flow__handle { - top: 50%; - right: calc(var(--content-padding) * -1); - transform: translate(50%, -50%); - } - } - - &.is-sub-list { - >li { - &:not(.node__port-children) { - padding-right: var(--padding-left, @padding-left); - } - - &::before { - right: var(--last-padding-left); - border-color: transparent white white transparent; - } - - &::after { - right: var(--last-padding-left); - border-color: transparent white transparent transparent; - } - - >.node__port-dot { - right: var(--padding-left); - } - - &.is-last { - &::before { - border-bottom-right-radius: 4px; - } - } - } - } - - &.is-sub-list { - >.node__port-children:not(:last-child) { - &::before { - right: var(--last-padding-left); - } - } - } - } - - &.target { - text-align: left; - - :global { - .react-flow__handle { - top: 50%; - left: calc(var(--content-padding) * -1); - transform: translate(-50%, -50%); - } - } - - &.is-sub-list { - >li { - &:not(.node__port-children) { - padding-left: var(--padding-left, @padding-left); - } - - &::before { - left: var(--last-padding-left); - border-color: transparent transparent white white; - } - - &::after { - left: var(--last-padding-left); - border-color: transparent transparent transparent white; - } - - >.node__port-dot { - left: var(--padding-left); - } - - &.is-last { - &::before { - border-bottom-left-radius: 4px; - } - } - } - } - - &.is-sub-list { - >.node__port-children:not(:last-child) { - &::before { - left: var(--last-padding-left); - } - } - } - } -} - -.node__port-children:not(:last-child)>.is-sub-list { - margin-bottom: 8px; -} - -.node__title { - padding-bottom: 2px; - margin-bottom: 8px; - border-bottom: 3px solid var(--color-node-theme, #9a7844); -} - -.node__port-expand-trigger { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: center; - - position: absolute; - top: 50%; - - font-size: 10px; - cursor: pointer; - - background-color: var(--color-bg); - - >svg { - transform: rotate(180deg); - transition: transform cubic-bezier(0.215, 0.610, 0.355, 1) 0.6s; - } - - - &.is-children-hidden { - >svg { - transform: rotate(0deg); - } - } - - .target & { - left: calc(var(--padding-left) + 0.5px); - transform: translate(-50%, -50%); - } - - .source & { - right: calc(var(--padding-left) + 0.5px); - transform: translate(50%, -50%); - } -} - -.node__port-expand-line { - position: absolute; - top: calc(50% + 4px); - bottom: -2px; - - .target & { - left: var(--padding-left); - border-left: 1px solid white; - } - - .source & { - right: var(--padding-left); - border-right: 1px solid white; - } -} - -.node__port-label { - padding: 0 6px; -} - -.add-button { - background: none; - border: none; - cursor: pointer; - display: block; - text-align: center; - color: #e2e2e2; - border: 1px #e2e2e2 solid; -} - -.node__operator-description { - font-size: 10px; -} \ No newline at end of file diff --git a/packages/flow/src/Diagrams/Nodes/components/BaseNode/PortList.tsx b/packages/flow/src/Diagrams/Nodes/components/BaseNode/PortList.tsx deleted file mode 100644 index ffbeee1..0000000 --- a/packages/flow/src/Diagrams/Nodes/components/BaseNode/PortList.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import React, { useState } from 'react'; -import classnames from 'classnames'; -import { Handle, type HandleProps, Position } from 'reactflow'; -import { type NodePort } from '../../../Operators/Operator'; -import css from './BaseNode.module.less'; - -export interface PortListProps { - value?: NodePort[]; - type: HandleProps['type']; - nestLevel?: number; - onPortAdd?: () => void; -} - -export const PortList: React.FC = (props) => { - const { value = [], type, nestLevel = 0, onPortAdd } = props; - const [hideChildrenIds, setHideChildren] = useState([]); - - function checkIsHideChildren(id: string) { - return !hideChildrenIds?.includes(id); - } - - function handleToggleHideChildren(id: string) { - return () => { - setHideChildren((hideIds) => { - const hideIdsWillUpdate = [...hideIds]; - if (hideIdsWillUpdate?.includes(id)) { - hideIdsWillUpdate.splice(hideIdsWillUpdate.indexOf(id)); - } else { - hideIdsWillUpdate.push(id); - } - return hideIdsWillUpdate; - }); - }; - } - - return ( -
    - {value.map((port, index) => { - const hasChildren = Boolean(port?.children?.length); - const hideChildren = checkIsHideChildren(port.id); - - return ( - -
  • - {Boolean(nestLevel) && !hasChildren && ( -
    - )} - {hasChildren && ( - <> - {/* TODO: 处理收起时候,链接内容的修改 */} -
    - -
    - {!hideChildren && ( -
    - )} - - )} -
    {port.label}
    - -
  • - {!hideChildren && hasChildren && ( -
  • - -
  • - )} -
    - ); - })} - {onPortAdd && ( -
  • - { - onPortAdd?.(); - }} - > - add port - -
  • - )} -
- ); -}; diff --git a/packages/flow/src/Diagrams/Nodes/components/BaseNode/index.tsx b/packages/flow/src/Diagrams/Nodes/components/BaseNode/index.tsx deleted file mode 100644 index 8ceff77..0000000 --- a/packages/flow/src/Diagrams/Nodes/components/BaseNode/index.tsx +++ /dev/null @@ -1,81 +0,0 @@ -import { type PropsWithChildren } from 'react'; -import classnames from 'classnames'; -import { type NodeProps } from 'reactflow'; -import { type IBaseNodeData } from '../../types'; -import { PortList } from './PortList'; -import css from './BaseNode.module.less'; - -export function BaseNode( - props: PropsWithChildren< - NodeProps & { - className?: string; - title?: React.ReactNode; - onSourcePortAdd?: () => void; - onTargetPortAdd?: () => void; - onDoubleClick?: () => void; - description?: string; - onFocus?: () => void; - } - >, -) { - const { - data, - selected, - className, - title, - onSourcePortAdd, - onTargetPortAdd, - onDoubleClick, - description, - onFocus, - } = props; - const operatorName = data?.operatorName; - - const ports = [ - { - type: 'source' as const, - value: data.sourcePorts, - }, - { - type: 'target' as const, - value: data.targetPorts, - }, - ]; - - return ( -
{ - onFocus?.(); - }} - > -
{operatorName}
-
-
-
{description}
-
{data.label}
-
{title}
-
- {ports?.map(({ type, value }) => { - if (!value?.length) { - return null; - } - return ( - - ); - })} -
-
- ); -} diff --git a/packages/flow/src/Diagrams/Nodes/index.ts b/packages/flow/src/Diagrams/Nodes/index.ts index 41ba402..b7b575b 100644 --- a/packages/flow/src/Diagrams/Nodes/index.ts +++ b/packages/flow/src/Diagrams/Nodes/index.ts @@ -1,14 +1,6 @@ -import { StateNode } from './StateNode'; -import { StreamOperatorNode } from './StreamOperatorNode'; -import { ContainerNode } from './ContainerNode'; -import { DoNode } from './DoNode'; import { Node } from './Node'; import { NodeTypeEnum } from './NodeTypeEnum'; export const nodeTypes: Record = { - [NodeTypeEnum.StateNode]: StateNode, - [NodeTypeEnum.StreamOperatorNode]: StreamOperatorNode, - [NodeTypeEnum.ContainerNode]: ContainerNode, - [NodeTypeEnum.DoNode]: DoNode, [NodeTypeEnum.Node]: Node, }; diff --git a/packages/flow/src/Diagrams/Nodes/types/index.ts b/packages/flow/src/Diagrams/Nodes/types/index.ts deleted file mode 100644 index edfbd91..0000000 --- a/packages/flow/src/Diagrams/Nodes/types/index.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { v4 as uuid } from 'uuid'; - -export class NodePort { - id: string; - type: string; - label: string = ''; - isConnectable?: boolean; - children?: NodePort[]; - - constructor(data: Partial) { - Object.assign(this, data); - this.id ??= uuid(); - this.type ??= 'unknown port type'; - } -} - -export interface IBaseNodeData { - /** 类型唯一标识 */ - operatorType: string; - /** 可以修改 */ - operatorName: string; - label?: string; - description?: string; - sourcePorts: NodePort[]; - targetPorts: NodePort[]; -} - -export enum StateNodeValueTypeEnum { - String = 'string', - Number = 'number', - Boolean = 'boolean', - Object = 'object', -} - -export type IStateNodeData = IBaseNodeData & { - valueType: StateNodeValueTypeEnum; - value: string | number | boolean | undefined; -}; - -export type IStreamOperatorNodeData = IBaseNodeData & { - allowAddTargetPort?: boolean; -}; -export enum StateNodePortTypeEnum { - State = 'State', - UpdateHanlder = 'UpdateHanlder', -} diff --git a/packages/flow/src/Diagrams/Operators/ConstStateOperator/index.tsx b/packages/flow/src/Diagrams/Operators/ConstStateOperator/index.tsx deleted file mode 100644 index fb51f5e..0000000 --- a/packages/flow/src/Diagrams/Operators/ConstStateOperator/index.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { - StateNodePortTypeEnum, - NodePort, - type IStateNodeData, -} from '../../Nodes/types'; -import { type IGenerationOption } from '../../Compiler/graph'; -import { EosOperatorsSymbol } from '../../Compiler/runtime'; -import { StateOperator } from '../StateOperator'; - -export class ConstStateOperator extends StateOperator { - constructor(data?: Partial) { - super(); - - // init ports - this.data = { - ...this.data, - sourcePorts: [ - new NodePort({ - label: 'data', - type: StateNodePortTypeEnum.State, - }), - ], - targetPorts: [], - operatorType: 'ConstStateOperator', - operatorName: 'ConstStateOperator', - ...data?.data, - } as IStateNodeData; - } - - static generateBlockDeclarations?( - options: IGenerationOption, - ): string[] { - const { node } = options; - - return [ - `const ${ConstStateOperator.getStateSymbol( - options, - )} = new ${EosOperatorsSymbol}.constValue(${JSON.stringify( - node.data.value, - )})`, - ]; - } - - static generateBlockRelation?( - options: IGenerationOption, - ): string[] { - return []; - } -} diff --git a/packages/flow/src/Diagrams/Operators/CustomOperator/AttributeControl/index.tsx b/packages/flow/src/Diagrams/Operators/CustomOperator/AttributeControl/index.tsx index ce87078..a4810be 100644 --- a/packages/flow/src/Diagrams/Operators/CustomOperator/AttributeControl/index.tsx +++ b/packages/flow/src/Diagrams/Operators/CustomOperator/AttributeControl/index.tsx @@ -1,5 +1,8 @@ import { type Node } from 'reactflow'; -import { useDiagramsContext } from '../../../State/DiagramsProvider'; +import { + useDiagramsContext, + useDiagramsHookOption, +} from '../../../State/DiagramsProvider'; import { Button, Form, Input } from 'antd'; import { findLayer } from '../../../State/Layer'; import { type CustomOperator } from '..'; @@ -8,7 +11,8 @@ import { getOperatorFromNode } from '../../OperatorMap'; export function AttributeControl(props: { node: Node }) { const { node } = props; - const { updateNode, layer, setLayer } = useDiagramsContext(); + const { updateNode, setLayer } = useDiagramsContext(); + const { currentStateRef, actionsRef } = useDiagramsHookOption(); const operator = getOperatorFromNode(node); @@ -46,9 +50,10 @@ export function AttributeControl(props: { node: Node }) { + + ); } diff --git a/packages/flow/src/Diagrams/Panels/FlowDiagram/index.tsx b/packages/flow/src/Diagrams/Panels/FlowDiagram/index.tsx index 8b51300..1ef6e71 100644 --- a/packages/flow/src/Diagrams/Panels/FlowDiagram/index.tsx +++ b/packages/flow/src/Diagrams/Panels/FlowDiagram/index.tsx @@ -18,18 +18,11 @@ import ReactFlow, { Panel, } from 'reactflow'; import { OPERATOR_TYPE_DATA } from '../OperatorPanel'; -import { - OperatorMap, - NextOperatorMap, - getOperatorFromNode, -} from '../../Operators'; +import { OperatorMap, getOperatorFromNode } from '../../Operators'; import { useDiagramsContext } from '../../State/DiagramsProvider'; import { nodeTypes } from '../../Nodes'; -import { NodeTypeEnum } from '../../Nodes/NodeTypeEnum'; import { isSameSourceHandle, isSameTargetHandle } from '../../utils'; -// import { defaultLayerData } from './defaultData'; import css from './FlowDiagram.module.less'; -import { type Operator } from '../../Operators/Operator'; import { useLatest } from 'ahooks'; import { BackToLayer } from '../LayerPanel/BackToLayer'; @@ -38,19 +31,7 @@ const nodeColor = (node: Node) => { if (operator?.nodeColor) { return operator?.nodeColor; } - - switch (node.type) { - case NodeTypeEnum.StateNode: - return '#0079FF'; - case NodeTypeEnum.StreamOperatorNode: - return '#FF0060'; - case NodeTypeEnum.ContainerNode: - return '#FF8080'; - case NodeTypeEnum.DoNode: - return '#FFF5E0'; - default: - return '#ff0072'; - } + return '#ff0072'; }; const getLayoutedElements = (nodes: Node[], edges: Edge[]) => { const g = new Dagre.graphlib.Graph().setDefaultEdgeLabel(() => ({})); @@ -149,7 +130,7 @@ export const FlowDiagram: React.FC = () => { const handleDrop: DragEventHandler = (event) => { const operatorType = event.dataTransfer.getData(OPERATOR_TYPE_DATA); if (operatorType) { - const operator = NextOperatorMap.get(operatorType); + const operator = OperatorMap.get(operatorType); if (operator) { const operatorInstance = operator.create(); if (operator.isUnique) { @@ -210,68 +191,7 @@ export const FlowDiagram: React.FC = () => { }, }); }); - return; - } - } - // TODO: DELETE - const operatorName = operatorType; - const Operator = OperatorMap.get(operatorName); - - if (Operator) { - const operatorInstance = new Operator(); - if (operatorInstance.unique) { - if (nodes.find((item) => item.type === operatorInstance.type)) { - message.warning('只允许存在一个'); - return; - } - } - const { clientX, clientY } = event; - const rect = dropTarget.current?.getBoundingClientRect(); - if (rect) { - operatorInstance.position = { - x: clientX - rect.left, - y: clientY - rect.y, - }; } - - setNodes((eles) => [...eles, operatorInstance]); - setTimeout(() => { - const node = nodesRef.current.find( - (item) => item.id === operatorInstance.id, - ); - - if (node) { - const pos = { - x: node?.position?.x - (node?.width || 0) / 2, - y: node.position.y - Math.max((node?.height || 0) / 5, 30), - }; - - node.position = pos; - // TODO: zoom - updateNodePos([node], false, false); - } - - setNodes((eles) => { - const target = eles.find((item) => item.id === operatorInstance.id); - if (target) { - operatorInstance.style = { - visibility: 'visible', - }; - } - return [...eles]; - }); - - Operator?.onAfterCreate?.({ - node: node as Operator, - currentState: latestState.current, - actions: { - updateEdge, - updateNode, - setActiveLayerId, - setLayer, - }, - }); - }); } }; diff --git a/packages/flow/src/Diagrams/Panels/OperatorPanel/index.tsx b/packages/flow/src/Diagrams/Panels/OperatorPanel/index.tsx index f9079ce..19efabe 100644 --- a/packages/flow/src/Diagrams/Panels/OperatorPanel/index.tsx +++ b/packages/flow/src/Diagrams/Panels/OperatorPanel/index.tsx @@ -1,5 +1,5 @@ import React, { type DragEventHandler } from 'react'; -import { OperatorMap, NextOperatorMap } from '../../Operators'; +import { OperatorMap } from '../../Operators'; import css from './OperatorPanel.module.less'; export const OPERATOR_TYPE_DATA = 'operator_type'; @@ -18,19 +18,7 @@ export const OperatorPanel: React.FC = () => {
Operators
- {[...OperatorMap.entries()].map(([name, Operator]) => { - return ( -
- {name} -
- ); - })} - {[...NextOperatorMap.entries()].map(([name, operator]) => { + {[...OperatorMap.entries()].map(([name, operator]) => { return (
; + +type ISetEdgeOption = ISetNodeOption; + export interface DiagramsContextType { /** ====== current ====== */ nodes: Node[]; - setNodes: React.Dispatch[]>>; - updateNode: (id: string, action: React.SetStateAction>) => void; + setNodes: ( + action: React.SetStateAction[]>, + options?: ISetNodeOption, + ) => void; + updateNode: ( + id: string, + action: React.SetStateAction>, + options?: IUpdateNodeOption, + ) => void; edges: Edge[]; - setEdges: React.Dispatch[]>>; - updateEdge: (id: string, action: React.SetStateAction>) => void; + setEdges: ( + action: React.SetStateAction[]>, + options?: ISetEdgeOption, + ) => void; + updateEdge: ( + id: string, + action: React.SetStateAction>, + options?: ISetEdgeOption, + ) => void; /** ====== all ====== */ layer: Layer; @@ -68,6 +91,7 @@ export function useDiagramsHookOption() { updateNode, setActiveLayerId, setLayer, + setEdges, layer, activeLayerId, } = useDiagramsContext(); @@ -81,6 +105,7 @@ export function useDiagramsHookOption() { const actionsRef = useLatest({ updateEdge, updateNode, + setEdges, setActiveLayerId, setLayer, }); @@ -144,9 +169,10 @@ export const DiagramsContextInnerProvider: React.FC = (props) => { const setNodes = useMemoizedFn(function setNodes( action: React.SetStateAction, + options?: ISetNodeOption, ) { setLayer((layer) => { - const activeLayer = findLayer(layer, activeLayerId); + const activeLayer = findLayer(layer, options?.layerId || activeLayerId); if (activeLayer) { activeLayer.nodes = typeof action === 'function' ? action(activeLayer.nodes) : action; @@ -163,9 +189,10 @@ export const DiagramsContextInnerProvider: React.FC = (props) => { const setEdges = useMemoizedFn(function setEdges( action: React.SetStateAction, + options?: ISetEdgeOption, ) { setLayer((layer) => { - const activeLayer = findLayer(layer, activeLayerId); + const activeLayer = findLayer(layer, options?.layerId || activeLayerId); if (activeLayer) { activeLayer.edges = typeof action === 'function' ? action(activeLayer.edges) : action; @@ -179,7 +206,7 @@ export const DiagramsContextInnerProvider: React.FC = (props) => { const updateEdge = useMemoizedFn(function updateEdge( id: string, updateElementAction: React.SetStateAction, - updateInternal?: boolean, + options?: ISetEdgeOption, ) { if (!id) { return; @@ -199,39 +226,38 @@ export const DiagramsContextInnerProvider: React.FC = (props) => { ? updateElementAction(target) : updateElementAction; return elesWillUpdate.concat(res); - }); - - if (updateInternal) { - setTimeout(() => { - updateNodeInternals(id); - }, 0); - } + }, options); }); const updateNode = useMemoizedFn(function updateNode( id: string, updateElementAction: React.SetStateAction, - updateInternal?: boolean, + option?: IUpdateNodeOption, ) { if (!id) { return; } - setNodes((eles) => { - const targetIndex = eles.findIndex((item) => item.id === id); + const { updateInternal, layerId } = option || {}; - if (targetIndex < 0) { - return eles; - } + setNodes( + (eles) => { + const targetIndex = eles.findIndex((item) => item.id === id); - const elesWillUpdate = [...eles]; - const target = elesWillUpdate.splice(targetIndex, 1)?.[0]; - const res = - typeof updateElementAction === 'function' - ? updateElementAction(target) - : updateElementAction; - return elesWillUpdate.concat(res); - }); + if (targetIndex < 0) { + return eles; + } + + const elesWillUpdate = [...eles]; + const target = elesWillUpdate.splice(targetIndex, 1)?.[0]; + const res = + typeof updateElementAction === 'function' + ? updateElementAction(target) + : updateElementAction; + return elesWillUpdate.concat(res); + }, + { layerId }, + ); if (updateInternal) { setTimeout(() => { @@ -240,6 +266,20 @@ export const DiagramsContextInnerProvider: React.FC = (props) => { } }); + const currentStateRef = useLatest({ + nodes, + edges, + layer, + activeLayerId, + }); + const actionsRef = useLatest({ + updateEdge, + updateNode, + setEdges, + setActiveLayerId: () => undefined, + setLayer, + }); + const setActiveLayerId = useMemoizedFn((activeId: string) => { const prevActiveId = activeLayerId; _setActiveLayerId(activeId); @@ -256,9 +296,10 @@ export const DiagramsContextInnerProvider: React.FC = (props) => { if (targetNode) { const operator = getOperatorFromNode(targetNode); - operator?.refreshNode(targetNode as any, { - updateNode, - layer: layerRef.current, + operator?.refreshNode({ + node: targetNode, + actions: actionsRef.current, + currentState: currentStateRef.current, }); } } diff --git a/packages/flow/src/Diagrams/utils/index.ts b/packages/flow/src/Diagrams/utils/index.ts index 6fd15a9..7287946 100644 --- a/packages/flow/src/Diagrams/utils/index.ts +++ b/packages/flow/src/Diagrams/utils/index.ts @@ -18,3 +18,7 @@ export function isSameSourceHandle(source: Edge, target: Edge | Connection) { source.sourceHandle === target.sourceHandle ); } + +export async function sleepMs(milSec: number) { + return await new Promise((resolve) => setTimeout(resolve, milSec)); +}