mirror of https://github.com/langgenius/dify.git
add node
This commit is contained in:
parent
31490417d1
commit
671654da71
|
|
@ -22,6 +22,7 @@ type UpdateParams = {
|
|||
from?: string
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
open?: boolean
|
||||
className?: string
|
||||
callback?: OnSelect
|
||||
}
|
||||
|
|
@ -30,6 +31,9 @@ export type BlockSelectorContextValue = {
|
|||
open: boolean
|
||||
setOpen: (open: boolean) => void
|
||||
referenceRef: any
|
||||
floatingRef: any
|
||||
floatingStyles: React.CSSProperties
|
||||
getFloatingProps: any
|
||||
handleToggle: (v: UpdateParams) => void
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +42,9 @@ export const BlockSelectorContext = createContext<BlockSelectorContextValue>({
|
|||
open: false,
|
||||
setOpen: () => {},
|
||||
referenceRef: null,
|
||||
floatingRef: null,
|
||||
floatingStyles: {},
|
||||
getFloatingProps: () => {},
|
||||
handleToggle: () => {},
|
||||
})
|
||||
export const useBlockSelectorContext = () => useContext(BlockSelectorContext)
|
||||
|
|
@ -73,13 +80,17 @@ export const BlockSelectorContextProvider = ({
|
|||
|
||||
const handleToggle = useCallback(({
|
||||
from,
|
||||
open,
|
||||
placement,
|
||||
offset,
|
||||
className,
|
||||
callback,
|
||||
}: UpdateParams) => {
|
||||
setFrom(from || 'node')
|
||||
setOpen(v => !v)
|
||||
if (open !== undefined)
|
||||
setOpen(open)
|
||||
else
|
||||
setOpen(v => !v)
|
||||
setPlacement(placement || 'top')
|
||||
setOffsetValue(offset || 0)
|
||||
setClassName(className || '')
|
||||
|
|
@ -99,10 +110,13 @@ export const BlockSelectorContextProvider = ({
|
|||
setOpen,
|
||||
handleToggle,
|
||||
referenceRef: refs.setReference,
|
||||
floatingRef: refs.setFloating,
|
||||
floatingStyles,
|
||||
getFloatingProps,
|
||||
}}>
|
||||
{children}
|
||||
{
|
||||
open && (
|
||||
open && (from === 'node' || from === 'panel') && (
|
||||
<FloatingPortal>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
import { useState } from 'react'
|
||||
import {
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react'
|
||||
|
||||
export const useAddBranch = () => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [dismissEnable, setDismissEnable] = useState(true)
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement: 'bottom',
|
||||
strategy: 'fixed',
|
||||
open: isOpen,
|
||||
onOpenChange: setIsOpen,
|
||||
middleware: [
|
||||
flip(),
|
||||
shift(),
|
||||
offset(4),
|
||||
],
|
||||
})
|
||||
const dismiss = useDismiss(context, {
|
||||
enabled: dismissEnable,
|
||||
})
|
||||
const { getFloatingProps } = useInteractions([
|
||||
dismiss,
|
||||
])
|
||||
|
||||
return {
|
||||
refs,
|
||||
floatingStyles,
|
||||
getFloatingProps,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
setDismissEnable,
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
import type { FC, MouseEvent } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { FloatingPortal } from '@floating-ui/react'
|
||||
import { useBlockSelectorContext } from '../../../../block-selector/context'
|
||||
import type {
|
||||
BlockEnum,
|
||||
Node,
|
||||
} from '../../../../types'
|
||||
import { useAddBranch } from './hooks'
|
||||
import { Plus02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type AddNodeProps = {
|
||||
outgoers: Node[]
|
||||
onAddNextNode: (type: BlockEnum) => void
|
||||
branches?: { id: string; name: string }[]
|
||||
}
|
||||
const AddNode: FC<AddNodeProps> = ({
|
||||
onAddNextNode,
|
||||
branches,
|
||||
}) => {
|
||||
const {
|
||||
refs,
|
||||
isOpen,
|
||||
setIsOpen,
|
||||
setDismissEnable,
|
||||
floatingStyles,
|
||||
getFloatingProps,
|
||||
} = useAddBranch()
|
||||
const {
|
||||
from,
|
||||
open,
|
||||
referenceRef,
|
||||
handleToggle,
|
||||
} = useBlockSelectorContext()
|
||||
const hasBranches = branches && !!branches.length
|
||||
const handleAdd = (e: MouseEvent<HTMLDivElement>) => {
|
||||
e.stopPropagation()
|
||||
|
||||
if (hasBranches)
|
||||
return setIsOpen(v => !v)
|
||||
|
||||
handleToggle({
|
||||
placement: 'right',
|
||||
offset: 6,
|
||||
callback: onAddNextNode,
|
||||
})
|
||||
}
|
||||
const buttonRef = useMemo(() => {
|
||||
if (hasBranches)
|
||||
return refs.setReference
|
||||
|
||||
if (from === 'node')
|
||||
return referenceRef
|
||||
|
||||
return null
|
||||
}, [from, hasBranches, referenceRef, refs.setReference])
|
||||
const buttonShouldShow = useMemo(() => {
|
||||
if (hasBranches && isOpen)
|
||||
return true
|
||||
|
||||
return open && from === 'node'
|
||||
}, [from, hasBranches, isOpen, open])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={buttonRef}
|
||||
onClick={handleAdd}
|
||||
className={`
|
||||
hidden absolute -bottom-2 left-1/2 -translate-x-1/2 items-center justify-center
|
||||
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
|
||||
${buttonShouldShow && '!flex'}
|
||||
`}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
{
|
||||
isOpen && hasBranches && (
|
||||
<FloatingPortal>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
className='p-1 w-[108px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'
|
||||
>
|
||||
{
|
||||
branches.map(branch => (
|
||||
<div
|
||||
key={branch.id}
|
||||
className='flex items-center px-3 pr-2 h-[30px] text-[13px] font-medium text-gray-700 cursor-pointer rounded-lg hover:bg-gray-50'
|
||||
onClick={() => {
|
||||
setDismissEnable(false)
|
||||
handleToggle({
|
||||
open: true,
|
||||
placement: 'right',
|
||||
offset: 6,
|
||||
callback: onAddNextNode,
|
||||
})
|
||||
}}
|
||||
>
|
||||
{branch.name}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</FloatingPortal>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(AddNode)
|
||||
|
|
@ -6,10 +6,8 @@ import {
|
|||
} from 'react'
|
||||
import { getOutgoers } from 'reactflow'
|
||||
import BlockIcon from '../../../block-icon'
|
||||
import type {
|
||||
BlockEnum,
|
||||
Node,
|
||||
} from '../../../types'
|
||||
import type { Node } from '../../../types'
|
||||
import { BlockEnum } from '../../../types'
|
||||
import { useWorkflowContext } from '../../../context'
|
||||
import { useBlockSelectorContext } from '../../../block-selector/context'
|
||||
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
|
@ -47,7 +45,44 @@ const NextStep: FC<NextStepProps> = ({
|
|||
<div className='shrink-0 w-6'></div>
|
||||
<div className='grow'>
|
||||
{
|
||||
!outgoers.length && (
|
||||
!!outgoers.length && outgoers.map(outgoer => (
|
||||
<div
|
||||
key={outgoer.id}
|
||||
className='group flex items-center mb-3 last-of-type:mb-0 px-2 h-9 rounded-lg border-[0.5px] border-gray-200 bg-white hover:bg-gray-50 shadow-xs text-xs text-gray-700 cursor-pointer'
|
||||
>
|
||||
<BlockIcon
|
||||
type={outgoer.data.type}
|
||||
className='shrink-0 mr-1.5'
|
||||
/>
|
||||
<div className='grow'>{outgoer.data.name}</div>
|
||||
<div
|
||||
ref={from === 'panel' ? referenceRef : null}
|
||||
onClick={() => {
|
||||
handleToggle({
|
||||
from: 'panel',
|
||||
className: 'w-[328px]',
|
||||
placement: 'top-end',
|
||||
offset: {
|
||||
mainAxis: 6,
|
||||
crossAxis: 8,
|
||||
},
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className={`
|
||||
hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md
|
||||
${open && '!bg-gray-100 !flex'}
|
||||
`}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
{
|
||||
(!outgoers.length || selectedNode.data.type === BlockEnum.IfElse) && (
|
||||
<div
|
||||
onClick={() => {
|
||||
handleToggle({
|
||||
|
|
@ -70,40 +105,6 @@ const NextStep: FC<NextStepProps> = ({
|
|||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
!!outgoers.length && outgoers.map(outgoer => (
|
||||
<div
|
||||
key={outgoer.id}
|
||||
className='group flex items-center mb-3 last-of-type:mb-0 px-2 h-9 rounded-lg border-[0.5px] border-gray-200 bg-white hover:bg-gray-50 shadow-xs text-xs text-gray-700 cursor-pointer'
|
||||
>
|
||||
<BlockIcon
|
||||
type={outgoer.data.type}
|
||||
className='shrink-0 mr-1.5'
|
||||
/>
|
||||
<div className='grow'>{outgoer.data.name}</div>
|
||||
<div
|
||||
ref={from === 'panel' ? referenceRef : null}
|
||||
onClick={() => {
|
||||
handleToggle({
|
||||
from: 'panel',
|
||||
className: 'w-[328px]',
|
||||
placement: 'top-end',
|
||||
offset: 6,
|
||||
})
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
className={`
|
||||
hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md
|
||||
${open && '!bg-gray-100 !flex'}
|
||||
`}
|
||||
>
|
||||
Change
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,11 +11,10 @@ import {
|
|||
import type { NodeProps } from 'reactflow'
|
||||
import { getOutgoers } from 'reactflow'
|
||||
import { useWorkflowContext } from '../../context'
|
||||
import type { BlockEnum } from '../../types'
|
||||
import { useBlockSelectorContext } from '../../block-selector/context'
|
||||
import { BlockEnum } from '../../types'
|
||||
import NodeControl from '../../node-control'
|
||||
import BlockIcon from '../../block-icon'
|
||||
import { Plus02 } from '@/app/components/base/icons/src/vender/line/general'
|
||||
import AddNode from './components/add-node/index'
|
||||
|
||||
type BaseNodeProps = {
|
||||
children: ReactElement
|
||||
|
|
@ -33,12 +32,6 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||
handleSelectedNodeIdChange,
|
||||
handleAddNextNode,
|
||||
} = useWorkflowContext()
|
||||
const {
|
||||
from,
|
||||
open,
|
||||
referenceRef,
|
||||
handleToggle,
|
||||
} = useBlockSelectorContext()
|
||||
const currentNode = useMemo(() => {
|
||||
return nodes.find(node => node.id === nodeId)
|
||||
}, [nodeId, nodes])
|
||||
|
|
@ -48,6 +41,20 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||
const handleSelectBlock = useCallback((type: BlockEnum) => {
|
||||
handleAddNextNode(currentNode!, type)
|
||||
}, [currentNode, handleAddNextNode])
|
||||
const branches = useMemo(() => {
|
||||
if (data.type === BlockEnum.IfElse) {
|
||||
return [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Is True',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Is False',
|
||||
},
|
||||
]
|
||||
}
|
||||
}, [data])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -73,28 +80,11 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||
<div className='px-3 pt-1 pb-1 text-xs text-gray-500'>
|
||||
Define the initial parameters for launching a workflow
|
||||
</div>
|
||||
{
|
||||
!outgoers.length && (
|
||||
<div
|
||||
ref={from === 'node' ? referenceRef : null}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleToggle({
|
||||
placement: 'right',
|
||||
offset: 6,
|
||||
callback: handleSelectBlock,
|
||||
})
|
||||
}}
|
||||
className={`
|
||||
hidden absolute -bottom-2 left-1/2 -translate-x-1/2 items-center justify-center
|
||||
w-4 h-4 rounded-full bg-primary-600 cursor-pointer z-10 group-hover:flex
|
||||
${open && from === 'node' && '!flex'}
|
||||
`}
|
||||
>
|
||||
<Plus02 className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<AddNode
|
||||
outgoers={outgoers}
|
||||
branches={branches}
|
||||
onAddNextNode={handleSelectBlock}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue