| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  | import { | 
					
						
							|  |  |  |   memo, | 
					
						
							|  |  |  |   useCallback, | 
					
						
							|  |  |  |   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' | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   generateNewNode, | 
					
						
							| 
									
										
										
										
											2025-04-01 16:52:07 +08:00
										 |  |  |   getNodeCustomTypeByNodeDataType, | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  | } from '../utils' | 
					
						
							|  |  |  | import { | 
					
						
							| 
									
										
										
										
											2024-06-05 14:00:47 +08:00
										 |  |  |   useAvailableBlocks, | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  |   useNodesReadOnly, | 
					
						
							|  |  |  |   usePanelInteractions, | 
					
						
							|  |  |  | } from '../hooks' | 
					
						
							|  |  |  | import { NODES_INITIAL_DATA } from '../constants' | 
					
						
							|  |  |  | import { useWorkflowStore } from '../store' | 
					
						
							|  |  |  | import TipPopup from './tip-popup' | 
					
						
							| 
									
										
										
										
											2024-07-09 15:05:40 +08:00
										 |  |  | import cn from '@/utils/classnames' | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  | 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) | 
					
						
							| 
									
										
										
										
											2024-06-05 14:00:47 +08:00
										 |  |  |   const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false) | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   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) | 
					
						
							| 
									
										
										
										
											2024-09-10 15:23:16 +08:00
										 |  |  |     const { newNode } = generateNewNode({ | 
					
						
							| 
									
										
										
										
											2025-04-01 16:52:07 +08:00
										 |  |  |       type: getNodeCustomTypeByNodeDataType(type), | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  |       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( | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |           'flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary', | 
					
						
							| 
									
										
										
										
											2024-12-17 12:20:49 +08:00
										 |  |  |           `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`, | 
					
						
							|  |  |  |           open && 'bg-state-accent-active text-text-accent', | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  |         )}> | 
					
						
							| 
									
										
										
										
											2025-03-21 17:41:03 +08:00
										 |  |  |           <RiAddCircleFill className='h-4 w-4' /> | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  |         </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]' | 
					
						
							| 
									
										
										
										
											2024-06-05 14:00:47 +08:00
										 |  |  |       availableBlocksTypes={availableNextBlocks} | 
					
						
							| 
									
										
										
										
											2024-05-09 17:18:51 +08:00
										 |  |  |     /> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default memo(AddBlock) |