123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- import {
- memo,
- useCallback,
- useState,
- } from 'react'
- import { RiAddCircleFill } from '@remixicon/react'
- import { useStoreApi } from 'reactflow'
- import { useTranslation } from 'react-i18next'
- import type { OffsetOptions } from '@floating-ui/react'
- import {
- generateNewNode,
- } from '../utils'
- import {
- useAvailableBlocks,
- useNodesReadOnly,
- usePanelInteractions,
- } from '../hooks'
- import { NODES_INITIAL_DATA } from '../constants'
- import { useWorkflowStore } from '../store'
- import TipPopup from './tip-popup'
- import cn from '@/utils/classnames'
- import BlockSelector from '@/app/components/workflow/block-selector'
- import type {
- OnSelectBlock,
- } from '@/app/components/workflow/types'
- import {
- BlockEnum,
- } from '@/app/components/workflow/types'
- type AddBlockProps = {
- renderTrigger?: (open: boolean) => React.ReactNode
- offset?: OffsetOptions
- }
- const AddBlock = ({
- renderTrigger,
- offset,
- }: AddBlockProps) => {
- const { t } = useTranslation()
- const store = useStoreApi()
- const workflowStore = useWorkflowStore()
- const { nodesReadOnly } = useNodesReadOnly()
- const { handlePaneContextmenuCancel } = usePanelInteractions()
- const [open, setOpen] = useState(false)
- const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
- const handleOpenChange = useCallback((open: boolean) => {
- setOpen(open)
- if (!open)
- handlePaneContextmenuCancel()
- }, [handlePaneContextmenuCancel])
- const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
- const {
- getNodes,
- } = store.getState()
- 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,
- },
- })
- workflowStore.setState({
- candidateNode: newNode,
- })
- }, [store, workflowStore, t])
- const renderTriggerElement = useCallback((open: boolean) => {
- return (
- <TipPopup
- title={t('workflow.common.addBlock')}
- >
- <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',
- )}>
- <RiAddCircleFill className='w-4 h-4' />
- </div>
- </TipPopup>
- )
- }, [nodesReadOnly, t])
- return (
- <BlockSelector
- open={open}
- onOpenChange={handleOpenChange}
- disabled={nodesReadOnly}
- onSelect={handleSelect}
- placement='top-start'
- offset={offset ?? {
- mainAxis: 4,
- crossAxis: -8,
- }}
- trigger={renderTrigger || renderTriggerElement}
- popupClassName='!min-w-[256px]'
- availableBlocksTypes={availableNextBlocks}
- />
- )
- }
- export default memo(AddBlock)
|