Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

add-block.tsx 3.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import {
  2. memo,
  3. useCallback,
  4. useState,
  5. } from 'react'
  6. import { RiAddCircleFill } from '@remixicon/react'
  7. import { useStoreApi } from 'reactflow'
  8. import { useTranslation } from 'react-i18next'
  9. import type { OffsetOptions } from '@floating-ui/react'
  10. import {
  11. generateNewNode,
  12. getNodeCustomTypeByNodeDataType,
  13. } from '../utils'
  14. import {
  15. useAvailableBlocks,
  16. useNodesReadOnly,
  17. usePanelInteractions,
  18. } from '../hooks'
  19. import { NODES_INITIAL_DATA } from '../constants'
  20. import { useWorkflowStore } from '../store'
  21. import TipPopup from './tip-popup'
  22. import cn from '@/utils/classnames'
  23. import BlockSelector from '@/app/components/workflow/block-selector'
  24. import type {
  25. OnSelectBlock,
  26. } from '@/app/components/workflow/types'
  27. import {
  28. BlockEnum,
  29. } from '@/app/components/workflow/types'
  30. type AddBlockProps = {
  31. renderTrigger?: (open: boolean) => React.ReactNode
  32. offset?: OffsetOptions
  33. }
  34. const AddBlock = ({
  35. renderTrigger,
  36. offset,
  37. }: AddBlockProps) => {
  38. const { t } = useTranslation()
  39. const store = useStoreApi()
  40. const workflowStore = useWorkflowStore()
  41. const { nodesReadOnly } = useNodesReadOnly()
  42. const { handlePaneContextmenuCancel } = usePanelInteractions()
  43. const [open, setOpen] = useState(false)
  44. const { availableNextBlocks } = useAvailableBlocks(BlockEnum.Start, false)
  45. const handleOpenChange = useCallback((open: boolean) => {
  46. setOpen(open)
  47. if (!open)
  48. handlePaneContextmenuCancel()
  49. }, [handlePaneContextmenuCancel])
  50. const handleSelect = useCallback<OnSelectBlock>((type, toolDefaultValue) => {
  51. const {
  52. getNodes,
  53. } = store.getState()
  54. const nodes = getNodes()
  55. const nodesWithSameType = nodes.filter(node => node.data.type === type)
  56. const { newNode } = generateNewNode({
  57. type: getNodeCustomTypeByNodeDataType(type),
  58. data: {
  59. ...NODES_INITIAL_DATA[type],
  60. title: nodesWithSameType.length > 0 ? `${t(`workflow.blocks.${type}`)} ${nodesWithSameType.length + 1}` : t(`workflow.blocks.${type}`),
  61. ...(toolDefaultValue || {}),
  62. _isCandidate: true,
  63. },
  64. position: {
  65. x: 0,
  66. y: 0,
  67. },
  68. })
  69. workflowStore.setState({
  70. candidateNode: newNode,
  71. })
  72. }, [store, workflowStore, t])
  73. const renderTriggerElement = useCallback((open: boolean) => {
  74. return (
  75. <TipPopup
  76. title={t('workflow.common.addBlock')}
  77. >
  78. <div className={cn(
  79. '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',
  80. `${nodesReadOnly && 'cursor-not-allowed text-text-disabled hover:bg-transparent hover:text-text-disabled'}`,
  81. open && 'bg-state-accent-active text-text-accent',
  82. )}>
  83. <RiAddCircleFill className='h-4 w-4' />
  84. </div>
  85. </TipPopup>
  86. )
  87. }, [nodesReadOnly, t])
  88. return (
  89. <BlockSelector
  90. open={open}
  91. onOpenChange={handleOpenChange}
  92. disabled={nodesReadOnly}
  93. onSelect={handleSelect}
  94. placement='right-start'
  95. offset={offset ?? {
  96. mainAxis: 4,
  97. crossAxis: -8,
  98. }}
  99. trigger={renderTrigger || renderTriggerElement}
  100. popupClassName='!min-w-[256px]'
  101. availableBlocksTypes={availableNextBlocks}
  102. />
  103. )
  104. }
  105. export default memo(AddBlock)