dify/web/app/components/workflow/operator/add-block.tsx

109 lines
3.4 KiB
TypeScript
Raw Normal View History

2024-08-23 16:17:14 +08:00
import React, { memo, useCallback, useEffect, useState } from 'react'
2024-06-20 11:05:08 +08:00
import { RiAddCircleFill } from '@remixicon/react'
2024-05-09 17:18:51 +08:00
import { useStoreApi } from 'reactflow'
import { useTranslation } from 'react-i18next'
import type { OffsetOptions } from '@floating-ui/react'
2024-08-23 16:17:14 +08:00
import { generateNewNode } from '../utils'
2024-05-09 17:18:51 +08:00
import {
useAvailableBlocks,
2024-05-09 17:18:51 +08:00
useNodesReadOnly,
usePanelInteractions,
} from '../hooks'
import { NODES_INITIAL_DATA } from '../constants'
2024-08-23 15:54:29 +08:00
import { useStore, useWorkflowStore } from '../store'
2024-05-09 17:18:51 +08:00
import TipPopup from './tip-popup'
import cn from '@/utils/classnames'
2024-05-09 17:18:51 +08:00
import BlockSelector from '@/app/components/workflow/block-selector'
2024-08-23 16:17:14 +08:00
import type { OnSelectBlock } from '@/app/components/workflow/types'
import { BlockEnum } from '@/app/components/workflow/types'
2024-05-09 17:18:51 +08:00
type AddBlockProps = {
renderTrigger?: (open: boolean) => React.ReactNode
offset?: OffsetOptions
2024-08-23 16:17:14 +08:00
useShortcut?: boolean
2024-05-09 17:18:51 +08:00
}
2024-08-23 16:17:14 +08:00
2024-05-09 17:18:51 +08:00
const AddBlock = ({
renderTrigger,
offset,
2024-08-23 16:17:14 +08:00
useShortcut = false,
2024-05-09 17:18:51 +08:00
}: AddBlockProps) => {
const { t } = useTranslation()
const store = useStoreApi()
const workflowStore = useWorkflowStore()
const { nodesReadOnly } = useNodesReadOnly()
2024-08-23 15:54:29 +08:00
const { handlePanelContextmenuCancel } = usePanelInteractions()
const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
2024-05-09 17:18:51 +08:00
2024-08-23 15:54:29 +08:00
const showAddBlock = useStore(state => state.showAddBlock)
const setShowAddBlock = useStore(state => state.setShowAddBlock)
2024-08-23 16:17:14 +08:00
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
if (useShortcut && showAddBlock)
setIsOpen(true)
}, [useShortcut, showAddBlock])
2024-05-09 17:18:51 +08:00
const handleOpenChange = useCallback((open: boolean) => {
2024-08-23 16:17:14 +08:00
setIsOpen(open)
if (useShortcut)
setShowAddBlock(open)
2024-05-09 17:18:51 +08:00
if (!open)
2024-08-23 15:54:29 +08:00
handlePanelContextmenuCancel()
2024-08-23 16:17:14 +08:00
}, [useShortcut, setShowAddBlock, handlePanelContextmenuCancel])
2024-05-09 17:18:51 +08:00
const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
2024-08-23 16:17:14 +08:00
const { getNodes } = store.getState()
2024-05-09 17:18:51 +08:00
const nodes = getNodes()
const nodesWithSameType = nodes.filter(node => node.data.type === type)
const newNode = generateNewNode({
data: {
...NODES_INITIAL_DATA[type],
title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${type}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${type}`),
...(toolDefaultValue || {}),
_isCandidate: true,
},
position: {
x: 0,
y: 0,
},
})
2024-08-23 16:17:14 +08:00
workflowStore.setState({ candidateNode: newNode })
2024-05-09 17:18:51 +08:00
}, [store, workflowStore, t])
const renderTriggerElement = useCallback((open: boolean) => {
return (
2024-08-23 16:18:19 +08:00
<TipPopup title={t('workflow.common.addBlock')} shortcuts={['shift', 'a']}>
2024-05-09 17:18:51 +08:00
<div className={cn(
'flex items-center justify-center w-8 h-8 rounded-lg hover:bg-black/5 hover:text-gray-700 cursor-pointer',
`${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
open && '!bg-black/5',
)}>
2024-06-20 11:05:08 +08:00
<RiAddCircleFill className='w-4 h-4' />
2024-05-09 17:18:51 +08:00
</div>
</TipPopup>
)
}, [nodesReadOnly, t])
return (
<BlockSelector
2024-08-23 16:17:14 +08:00
open={isOpen}
2024-05-09 17:18:51 +08:00
onOpenChange={handleOpenChange}
disabled={nodesReadOnly}
onSelect={handleSelect}
placement='top-start'
offset={offset ?? {
mainAxis: 4,
crossAxis: -8,
}}
trigger={renderTrigger || renderTriggerElement}
popupClassName='!min-w-[256px]'
availableBlocksTypes={availableNextBlocks}
2024-05-09 17:18:51 +08:00
/>
)
}
export default memo(AddBlock)