mirror of https://github.com/langgenius/dify.git
block-selector
This commit is contained in:
parent
c6f1900a93
commit
59d8f926c8
|
|
@ -11,45 +11,10 @@ const initialNodes = [
|
|||
position: { x: 330, y: 30 },
|
||||
data: { type: 'start' },
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'custom',
|
||||
position: { x: 330, y: 212 },
|
||||
data: { type: 'start' },
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'custom',
|
||||
position: { x: 150, y: 394 },
|
||||
data: { type: 'start' },
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'custom',
|
||||
position: { x: 510, y: 394 },
|
||||
data: { type: 'start' },
|
||||
},
|
||||
]
|
||||
|
||||
const initialEdges = [
|
||||
{
|
||||
id: '1',
|
||||
source: '1',
|
||||
target: '2',
|
||||
type: 'custom',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
source: '2',
|
||||
target: '3',
|
||||
type: 'custom',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
source: '2',
|
||||
target: '4',
|
||||
type: 'custom',
|
||||
},
|
||||
|
||||
]
|
||||
|
||||
const Page: FC = () => {
|
||||
|
|
|
|||
|
|
@ -1,53 +1,87 @@
|
|||
import type { FC, ReactElement } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useState,
|
||||
} from 'react'
|
||||
import Tabs from './tabs'
|
||||
import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
FloatingPortal,
|
||||
flip,
|
||||
offset,
|
||||
shift,
|
||||
useClick,
|
||||
useDismiss,
|
||||
useFloating,
|
||||
useInteractions,
|
||||
} from '@floating-ui/react'
|
||||
import Tabs from './tabs'
|
||||
import { SearchLg } from '@/app/components/base/icons/src/vender/line/general'
|
||||
|
||||
type NodeSelectorProps = {
|
||||
children: ReactElement
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
className?: string
|
||||
children: (props: any) => ReactElement
|
||||
}
|
||||
const NodeSelector: FC<NodeSelectorProps> = ({
|
||||
placement = 'top',
|
||||
offset: offsetValue = 0,
|
||||
className,
|
||||
children,
|
||||
}) => {
|
||||
const [open, setOpen] = useState(false)
|
||||
const handleTrigger: any = useCallback((e: MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
setOpen(v => !v)
|
||||
}, [])
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement,
|
||||
strategy: 'fixed',
|
||||
open,
|
||||
onOpenChange: setOpen,
|
||||
middleware: [
|
||||
flip(),
|
||||
shift(),
|
||||
offset(offsetValue),
|
||||
],
|
||||
})
|
||||
const click = useClick(context)
|
||||
const dismiss = useDismiss(context, {
|
||||
bubbles: false,
|
||||
})
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([
|
||||
click,
|
||||
dismiss,
|
||||
])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
placement='right-start'
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={handleTrigger}>
|
||||
{children}
|
||||
</PortalToFollowElemTrigger>
|
||||
<PortalToFollowElemContent className='z-50'>
|
||||
<div className='w-[256px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg'>
|
||||
<div className='px-2 pt-2'>
|
||||
<div className='flex items-center px-2 rounded-lg bg-gray-100'>
|
||||
<SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
|
||||
<input
|
||||
className='grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
|
||||
placeholder='Search block'
|
||||
/>
|
||||
<>
|
||||
{children({ ...getReferenceProps(), ref: refs.setReference, open })}
|
||||
{
|
||||
open && (
|
||||
<FloatingPortal>
|
||||
<div
|
||||
ref={refs.setFloating}
|
||||
style={floatingStyles}
|
||||
{...getFloatingProps()}
|
||||
className='z-[1000]'
|
||||
>
|
||||
<div className={`w-[256px] rounded-lg border-[0.5px] border-gray-200 bg-white shadow-lg ${className}`}>
|
||||
<div className='px-2 pt-2'>
|
||||
<div className='flex items-center px-2 rounded-lg bg-gray-100'>
|
||||
<SearchLg className='shrink-0 ml-[1px] mr-[5px] w-3.5 h-3.5 text-gray-400' />
|
||||
<input
|
||||
className='grow px-0.5 py-[7px] text-[13px] bg-transparent appearance-none outline-none'
|
||||
placeholder='Search block'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Tabs />
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
</FloatingPortal>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { FC } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import { getOutgoers } from 'reactflow'
|
||||
|
|
@ -25,6 +26,42 @@ const NextStep: FC<NextStepProps> = ({
|
|||
return getOutgoers(selectedNode, nodes, edges)
|
||||
}, [selectedNode, nodes, edges])
|
||||
|
||||
const renderBlockSelectorChildren = useCallback(({ open, ref, ...restProps }: any) => {
|
||||
return (
|
||||
<div
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
className={`
|
||||
flex items-center px-2 w-[328px] h-9 rounded-lg border border-dashed border-gray-200 bg-gray-50
|
||||
hover:bg-gray-100 text-xs text-gray-500 cursor-pointer
|
||||
${open && '!bg-gray-100'}
|
||||
`}
|
||||
>
|
||||
<div className='flex items-center justify-center mr-1.5 w-5 h-5 rounded-[5px] bg-gray-200'>
|
||||
<Plus className='w-3 h-3' />
|
||||
</div>
|
||||
SELECT NEXT BLOCK
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
const renderBlockSelectorButtonChildren = useCallback(({ open, ref, ...restProps }: any) => {
|
||||
return (
|
||||
<div
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
>
|
||||
<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>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='flex py-1'>
|
||||
<div className='shrink-0 relative flex items-center justify-center w-9 h-9 bg-white rounded-lg border-[0.5px] border-gray-200 shadow-xs'>
|
||||
|
|
@ -34,13 +71,8 @@ const NextStep: FC<NextStepProps> = ({
|
|||
<div className='grow'>
|
||||
{
|
||||
!outgoers.length && (
|
||||
<BlockSelector>
|
||||
<div className='flex items-center px-2 w-[328px] h-9 rounded-lg border border-dashed border-gray-200 bg-gray-50 hover:bg-gray-100 text-xs text-gray-500 cursor-pointer'>
|
||||
<div className='flex items-center justify-center mr-1.5 w-5 h-5 rounded-[5px] bg-gray-200'>
|
||||
<Plus className='w-3 h-3' />
|
||||
</div>
|
||||
SELECT NEXT BLOCK
|
||||
</div>
|
||||
<BlockSelector className='!w-[328px]'>
|
||||
{renderBlockSelectorChildren}
|
||||
</BlockSelector>
|
||||
)
|
||||
}
|
||||
|
|
@ -55,10 +87,11 @@ const NextStep: FC<NextStepProps> = ({
|
|||
className='shrink-0 mr-1.5'
|
||||
/>
|
||||
<div className='grow'>{outgoer.data.name}</div>
|
||||
<BlockSelector>
|
||||
<Button className='hidden group-hover:flex px-2 py-0 h-6 bg-white text-xs text-gray-700 font-medium rounded-md'>
|
||||
Change
|
||||
</Button>
|
||||
<BlockSelector
|
||||
placement='top-end'
|
||||
offset={6}
|
||||
>
|
||||
{renderBlockSelectorButtonChildren}
|
||||
</BlockSelector>
|
||||
</div>
|
||||
))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,11 @@ import type {
|
|||
FC,
|
||||
ReactNode,
|
||||
} from 'react'
|
||||
import { memo, useMemo } from 'react'
|
||||
import {
|
||||
memo,
|
||||
useCallback,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import {
|
||||
getOutgoers,
|
||||
useNodeId,
|
||||
|
|
@ -33,6 +37,23 @@ const BaseNode: FC<BaseNodeProps> = ({
|
|||
const outgoers = useMemo(() => {
|
||||
return getOutgoers(currentNode!, nodes, edges)
|
||||
}, [currentNode, nodes, edges])
|
||||
const renderBlockSelectorChildren = useCallback(({ open, ref, ...restProps }: any) => {
|
||||
return (
|
||||
<div onClick={e => e.stopPropagation()}>
|
||||
<div
|
||||
{...restProps}
|
||||
ref={ref}
|
||||
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 && '!flex'}
|
||||
`}
|
||||
>
|
||||
<Plus className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -57,17 +78,16 @@ 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>
|
||||
<BlockSelector>
|
||||
<div
|
||||
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
|
||||
${!outgoers.length && 'group-hover:flex'}
|
||||
`}
|
||||
>
|
||||
<Plus className='w-2.5 h-2.5 text-white' />
|
||||
</div>
|
||||
</BlockSelector>
|
||||
{
|
||||
!outgoers.length && (
|
||||
<BlockSelector
|
||||
placement='right'
|
||||
offset={6}
|
||||
>
|
||||
{renderBlockSelectorChildren}
|
||||
</BlockSelector>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue