299 lines
8.4 KiB
TypeScript
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
|