Files
hudi-service/service-web/client/src/components/flow/node/AmisNode.tsx

299 lines
8.4 KiB
TypeScript

import {CopyFilled, DeleteFilled, EditFilled} from '@ant-design/icons'
import {type Edge, Handle, type Node, type NodeProps, NodeResizeControl, NodeToolbar, Position} from '@xyflow/react'
import {type ClassName, classnames, type Schema} from 'amis'
import {Button, Drawer, Space, Tooltip} from 'antd'
import {type CSSProperties, type JSX, useCallback, useState} from 'react'
import styled from 'styled-components'
import {amisRender, commonInfo, horizontalFormOptions} from '../../../util/amis.tsx'
import {generateAllIncomerOutputVariablesFormOptions} from '../Helper.tsx'
import {useDataStore} from '../store/DataStore.ts'
import {useFlowStore} from '../store/FlowStore.ts'
import {OutputVariableTypeMap} from '../types.ts'
export function inputsFormColumns(
nodeId: string,
inputSchema: Record<string, Record<string, any>>,
nodes: Node[],
edges: Edge[],
data: any,
): Schema[] {
return [
{
type: 'input-kvs',
name: 'inputs',
label: '输入变量',
addButtonText: '新增输入',
draggable: false,
keyItem: {
...horizontalFormOptions(),
label: '参数名称',
},
valueItems: [
{
...horizontalFormOptions(),
type: 'select',
name: 'variable',
label: '变量',
required: true,
selectMode: 'group',
options: generateAllIncomerOutputVariablesFormOptions(
nodeId,
inputSchema,
nodes,
edges,
data,
),
},
],
},
]
}
export function outputsFormColumns(editable: boolean = false, required: boolean = false): Schema[] {
return [
{
disabled: !editable,
type: 'input-kvs',
name: 'outputs',
label: '输出变量',
addButtonText: '新增输出',
draggable: false,
keyItem: {
...horizontalFormOptions(),
label: '参数名称',
},
required: required,
valueItems: [
{
...horizontalFormOptions(),
type: 'select',
name: 'type',
label: '参数',
required: true,
selectFirst: true,
options: Object.keys(OutputVariableTypeMap).map(key => ({
// @ts-ignore
label: OutputVariableTypeMap[key],
value: key,
})),
},
],
},
]
}
type AmisNodeProps = {
className: ClassName,
style?: CSSProperties,
nodeProps: NodeProps
extraNodeDescription?: JSX.Element
handler: JSX.Element
columnSchema?: () => Schema[]
resize?: { minWidth: number, minHeight: number }
}
const AmisNodeContainerDiv = styled.div`
`
export const StartNodeHandler = () => {
return <Handle type="source" position={Position.Right} id="source"/>
}
export const EndNodeHandler = () => {
return <Handle type="target" position={Position.Left} id="target"/>
}
export const NormalNodeHandler = () => {
return (
<>
<StartNodeHandler/>
<EndNodeHandler/>
</>
)
}
export const nodeClassName = (name: string) => {
return `flow-node flow-node-${name}`
}
const AmisNode: (props: AmisNodeProps) => JSX.Element = ({
className,
style,
nodeProps,
extraNodeDescription,
handler,
columnSchema,
resize,
}) => {
const {removeNode} = useFlowStore()
const {getDataById, setDataById, removeDataById} = useDataStore()
const {id} = nodeProps
// @ts-ignore
const nodeData = getDataById(id)
const nodeName = nodeData?.node?.name ?? ''
const nodeDescription = nodeData?.node?.description ?? ''
const [editDrawerOpen, setEditDrawerOpen] = useState(false)
const [editDrawerForm, setEditDrawerForm] = useState<JSX.Element>(<></>)
const onOpenEditDrawerClick = useCallback(() => {
setEditDrawerForm(
amisRender(
{
type: 'wrapper',
size: 'none',
body: [
{
debug: commonInfo.debug,
type: 'form',
...horizontalFormOptions(),
wrapWithPanel: false,
onEvent: {
submitSucc: {
actions: [
{
actionType: 'custom',
// @ts-ignore
script: (context, action, event) => {
setDataById(
id,
{
...context.props.data,
finished: true,
},
)
setEditDrawerOpen(false)
},
},
],
},
},
body: [
{
type: 'input-text',
name: 'node.name',
label: '节点名称',
placeholder: nodeName,
},
{
type: 'textarea',
name: 'node.description',
label: '节点描述',
placeholder: nodeDescription,
},
{
type: 'divider',
},
...(columnSchema?.() ?? []),
{
type: 'wrapper',
size: 'none',
className: 'space-x-2 text-right',
body: [
{
type: 'action',
label: '取消',
onEvent: {
click: {
actions: [
{
actionType: 'custom',
// @ts-ignore
script: (context, action, event) => {
setEditDrawerOpen(false)
},
},
],
},
},
},
{
type: 'submit',
label: '保存',
level: 'primary',
},
],
},
],
},
],
},
getDataById(id),
),
)
setEditDrawerOpen(true)
}, [id])
const onRemoveClick = useCallback(() => {
removeNode(id)
removeDataById(id)
}, [])
return (
<AmisNodeContainerDiv className={classnames(className, 'w-64')} style={style}>
<Drawer
title="节点编辑"
open={editDrawerOpen}
closeIcon={false}
maskClosable={false}
destroyOnHidden
size="large"
>
{editDrawerForm}
</Drawer>
<NodeToolbar>
<Space>
<Tooltip title="复制节点">
<Button
className="text-secondary"
disabled
type="text"
size="small"
icon={<CopyFilled/>}
/>
</Tooltip>
<Tooltip title="编辑节点">
<Button
className="text-secondary"
type="text"
size="small"
icon={<EditFilled/>}
onClick={() => onOpenEditDrawerClick()}
/>
</Tooltip>
<Tooltip title="删除节点">
<Button
className="text-secondary"
type="text"
size="small"
icon={<DeleteFilled/>}
onClick={() => onRemoveClick()}
/>
</Tooltip>
</Space>
</NodeToolbar>
<div className="node-card h-full flex flex-col bg-white rounded-md border border-gray-100 border-solid">
<div
className="node-card-header items-center flex justify-between p-2 border-t-0 border-l-0 border-r-0 border-b border-gray-100 border-solid">
<span className="font-bold">{nodeName}</span>
<span className="text-gray-300 text-sm">{id}</span>
</div>
<div className="node-card-description flex flex-col flex-1 p-2 text-secondary text-sm">
<div className="node-card-description-node">
{nodeDescription}
</div>
<div className="node-card-description-extra flex-1 mt-1">
{extraNodeDescription}
</div>
</div>
</div>
{resize ? <>
<NodeResizeControl
minWidth={resize.minWidth}
minHeight={resize.minHeight}
/>
</> : undefined}
{handler}
</AmisNodeContainerDiv>
)
}
export default AmisNode