add-block.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. import {
  2. memo,
  3. useCallback,
  4. } from 'react'
  5. import produce from 'immer'
  6. import {
  7. RiAddLine,
  8. } from '@remixicon/react'
  9. import { useStoreApi } from 'reactflow'
  10. import { useTranslation } from 'react-i18next'
  11. import {
  12. generateNewNode,
  13. } from '../../utils'
  14. import {
  15. WorkflowHistoryEvent,
  16. useAvailableBlocks,
  17. useNodesReadOnly,
  18. useWorkflowHistory,
  19. } from '../../hooks'
  20. import { NODES_INITIAL_DATA } from '../../constants'
  21. import InsertBlock from './insert-block'
  22. import type { IterationNodeType } from './types'
  23. import cn from '@/utils/classnames'
  24. import BlockSelector from '@/app/components/workflow/block-selector'
  25. import { IterationStart } from '@/app/components/base/icons/src/vender/workflow'
  26. import type {
  27. OnSelectBlock,
  28. } from '@/app/components/workflow/types'
  29. import {
  30. BlockEnum,
  31. } from '@/app/components/workflow/types'
  32. import TooltipPlus from '@/app/components/base/tooltip-plus'
  33. type AddBlockProps = {
  34. iterationNodeId: string
  35. iterationNodeData: IterationNodeType
  36. }
  37. const AddBlock = ({
  38. iterationNodeId,
  39. iterationNodeData,
  40. }: AddBlockProps) => {
  41. const { t } = useTranslation()
  42. const store = useStoreApi()
  43. const { nodesReadOnly } = useNodesReadOnly()
  44. const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, true)
  45. const { availablePrevBlocks } = useAvailableBlocks(iterationNodeData.startNodeType, true)
  46. const { saveStateToHistory } = useWorkflowHistory()
  47. const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
  48. const {
  49. getNodes,
  50. setNodes,
  51. } = store.getState()
  52. const nodes = getNodes()
  53. const nodesWithSameType = nodes.filter(node => node.data.type === type)
  54. const newNode = generateNewNode({
  55. data: {
  56. ...NODES_INITIAL_DATA[type],
  57. title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${type}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${type}`),
  58. ...(toolDefaultValue || {}),
  59. isIterationStart: true,
  60. isInIteration: true,
  61. iteration_id: iterationNodeId,
  62. },
  63. position: {
  64. x: 117,
  65. y: 85,
  66. },
  67. zIndex: 1001,
  68. parentId: iterationNodeId,
  69. extent: 'parent',
  70. })
  71. const newNodes = produce(nodes, (draft) => {
  72. draft.forEach((node) => {
  73. if (node.id === iterationNodeId) {
  74. node.data._children = [newNode.id]
  75. node.data.start_node_id = newNode.id
  76. node.data.startNodeType = newNode.data.type
  77. }
  78. })
  79. draft.push(newNode)
  80. })
  81. setNodes(newNodes)
  82. saveStateToHistory(WorkflowHistoryEvent.NodeAdd)
  83. }, [store, t, iterationNodeId, saveStateToHistory])
  84. const renderTriggerElement = useCallback((open: boolean) => {
  85. return (
  86. <div className={cn(
  87. 'relative inline-flex items-center px-3 h-8 rounded-lg border-[0.5px] border-gray-50 bg-white shadow-xs cursor-pointer hover:bg-gray-200 text-[13px] font-medium text-gray-700',
  88. `${nodesReadOnly && '!cursor-not-allowed opacity-50'}`,
  89. open && '!bg-gray-50',
  90. )}>
  91. <RiAddLine className='mr-1 w-4 h-4' />
  92. {t('workflow.common.addBlock')}
  93. </div>
  94. )
  95. }, [nodesReadOnly, t])
  96. return (
  97. <div className='absolute top-12 left-6 flex items-center h-8 z-10'>
  98. <TooltipPlus popupContent={t('workflow.blocks.iteration-start')}>
  99. <div className='flex items-center justify-center w-6 h-6 rounded-full border-[0.5px] border-black/[0.02] shadow-md bg-primary-500'>
  100. <IterationStart className='w-4 h-4 text-white' />
  101. </div>
  102. </TooltipPlus>
  103. <div className='group/insert relative w-16 h-0.5 bg-gray-300'>
  104. {
  105. iterationNodeData.startNodeType && (
  106. <InsertBlock
  107. startNodeId={iterationNodeData.start_node_id}
  108. availableBlocksTypes={availablePrevBlocks}
  109. />
  110. )
  111. }
  112. <div className='absolute right-0 top-1/2 -translate-y-1/2 w-0.5 h-2 bg-primary-500'></div>
  113. </div>
  114. {
  115. !iterationNodeData.startNodeType && (
  116. <BlockSelector
  117. disabled={nodesReadOnly}
  118. onSelect={handleSelect}
  119. trigger={renderTriggerElement}
  120. triggerInnerClassName='inline-flex'
  121. popupClassName='!min-w-[256px]'
  122. availableBlocksTypes={availableNextBlocks}
  123. />
  124. )
  125. }
  126. </div>
  127. )
  128. }
  129. export default memo(AddBlock)