next step

This commit is contained in:
StyleZhang
2024-02-28 16:19:35 +08:00
parent 76ff004ea5
commit f1b868d5d9
11 changed files with 430 additions and 213 deletions

View File

@ -1,175 +0,0 @@
import {
memo,
useCallback,
} from 'react'
import {
getOutgoers,
useStoreApi,
} from 'reactflow'
import BlockIcon from '../../../block-icon'
import type { Node } from '../../../types'
import { useStore } from '../../../store'
import BlockSelector from '../../../block-selector'
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
import Button from '@/app/components/base/button'
const NextStep = () => {
const store = useStoreApi()
const selectedNode = useStore(state => state.selectedNode)
const outgoers: Node[] = getOutgoers(selectedNode as Node, store.getState().getNodes(), store.getState().edges)
const svgHeight = outgoers.length > 1 ? (outgoers.length + 1) * 36 + 12 * outgoers.length : 36
const renderAddNextNodeTrigger = useCallback((open: boolean) => {
return (
<div
className={`
relative 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 renderChangeCurrentNodeTrigger = useCallback((open: boolean) => {
return (
<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>
)
}, [])
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'>
<BlockIcon type={selectedNode!.data.type} />
</div>
<svg className='shrink-0 w-6' style={{ height: svgHeight }}>
{
outgoers.length < 2 && (
<g>
<path
d='M0,18 L24,18'
strokeWidth={1}
stroke='#D0D5DD'
fill='none'
/>
<rect
x={0}
y={16}
width={1}
height={4}
fill='#98A2B3'
/>
<rect
x={23}
y={16}
width={1}
height={4}
fill='#98A2B3'
/>
</g>
)
}
{
outgoers.length > 1 && (
<g>
{
Array(outgoers.length + 1).fill(0).map((_, index) => (
<g key={index}>
{
index === 0 && (
<path
d='M0,18 L24,18'
strokeWidth={1}
stroke='#D0D5DD'
fill='none'
/>
)
}
{
index > 0 && (
<path
d={`M0,18 Q12,18 12,28 L12,${index * 48 + 18 - 10} Q12,${index * 48 + 18} 24,${index * 48 + 18}`}
strokeWidth={1}
stroke='#D0D5DD'
fill='none'
/>
)
}
<rect
x={23}
y={index * 48 + 18 - 2}
width={1}
height={4}
fill='#98A2B3'
/>
</g>
))
}
<rect
x={0}
y={16}
width={1}
height={4}
fill='#98A2B3'
/>
</g>
)
}
</svg>
<div className='grow'>
{
!!outgoers.length && outgoers.map(outgoer => (
<div
key={outgoer.id}
className='relative 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'
>
<div className='absolute left-1 -top-[7.5px] flex items-center px-0.5 h-3 bg-white text-[10px] text-gray-500 font-semibold rounded-[5px]'>
IS TRUE
</div>
<BlockIcon
type={outgoer.data.type}
className='shrink-0 mr-1.5'
/>
<div className='grow'>{outgoer.data.title}</div>
<BlockSelector
onSelect={() => {}}
placement='top-end'
offset={{
mainAxis: 6,
crossAxis: 8,
}}
trigger={renderChangeCurrentNodeTrigger}
popupClassName='!w-[328px]'
/>
</div>
))
}
{
(!outgoers.length || outgoers.length > 1) && (
<BlockSelector
onSelect={() => {}}
placement='top'
offset={0}
trigger={renderAddNextNodeTrigger}
popupClassName='!w-[328px]'
/>
)
}
</div>
</div>
)
}
export default memo(NextStep)

View File

@ -0,0 +1,61 @@
import {
memo,
useCallback,
} from 'react'
import BlockSelector from '../../../../block-selector'
import { useWorkflow } from '../../../../hooks'
import type { BlockEnum } from '../../../../types'
import { Plus } from '@/app/components/base/icons/src/vender/line/general'
type AddProps = {
nodeId: string
sourceHandle: string
branchName?: string
}
const Add = ({
nodeId,
sourceHandle,
branchName,
}: AddProps) => {
const { handleAddNextNode } = useWorkflow()
const handleSelect = useCallback((type: BlockEnum) => {
handleAddNextNode(nodeId, type, sourceHandle)
}, [nodeId, sourceHandle, handleAddNextNode])
const renderTrigger = useCallback((open: boolean) => {
return (
<div
className={`
relative 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'}
`}
>
{
branchName && (
<div className='absolute left-1 -top-[7.5px] flex items-center px-0.5 h-3 bg-white text-[10px] text-gray-500 font-semibold rounded-[5px]'>
{branchName.toLocaleUpperCase()}
</div>
)
}
<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>
)
}, [branchName])
return (
<BlockSelector
onSelect={handleSelect}
placement='top'
offset={0}
trigger={renderTrigger}
popupClassName='!w-[328px]'
/>
)
}
export default memo(Add)

View File

@ -0,0 +1,90 @@
import { memo } from 'react'
import {
getConnectedEdges,
getOutgoers,
useEdges,
useStoreApi,
} from 'reactflow'
import BlockIcon from '../../../../block-icon'
import type { Node } from '../../../../types'
import { useStore } from '../../../../store'
import Add from './add'
import Item from './item'
import Line from './line'
const NextStep = () => {
const store = useStoreApi()
const selectedNode = useStore(state => state.selectedNode)
const branches = selectedNode?.data.branches
const edges = useEdges()
const outgoers = getOutgoers(selectedNode as Node, store.getState().getNodes(), edges)
const connectedEdges = getConnectedEdges([selectedNode] as Node[], edges).filter(edge => edge.source === selectedNode!.id)
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'>
<BlockIcon type={selectedNode!.data.type} />
</div>
<Line linesNumber={branches ? branches.length : 1} />
<div className='grow'>
{
!branches && !!outgoers.length && (
<Item
parentNodeId={selectedNode!.id}
nodeId={outgoers[0].id}
sourceHandle='source'
data={outgoers[0].data}
/>
)
}
{
!branches && !outgoers.length && (
<Add
nodeId={selectedNode!.id}
sourceHandle='source'
/>
)
}
{
branches?.length && (
branches.map((branch) => {
const connected = connectedEdges.find(edge => edge.sourceHandle === branch.id)
const target = outgoers.find(outgoer => outgoer.id === connected?.target)
return (
<div
key={branch.id}
className='mb-3 last-of-type:mb-0'
>
{
connected && (
<Item
data={target!.data!}
parentNodeId={selectedNode!.id}
nodeId={target!.id}
sourceHandle={branch.id}
branchName={branch.name}
/>
)
}
{
!connected && (
<Add
key={branch.id}
nodeId={selectedNode!.id}
sourceHandle={branch.id}
branchName={branch.name}
/>
)
}
</div>
)
})
)
}
</div>
</div>
)
}
export default memo(NextStep)

View File

@ -0,0 +1,75 @@
import {
memo,
useCallback,
} from 'react'
import type {
BlockEnum,
CommonNodeType,
} from '../../../../types'
import BlockIcon from '../../../../block-icon'
import BlockSelector from '../../../../block-selector'
import { useWorkflow } from '../../../../hooks'
import Button from '@/app/components/base/button'
type ItemProps = {
parentNodeId: string
nodeId: string
sourceHandle: string
branchName?: string
data: CommonNodeType
}
const Item = ({
parentNodeId,
nodeId,
sourceHandle,
branchName,
data,
}: ItemProps) => {
const { handleChangeCurrentNode } = useWorkflow()
const handleSelect = useCallback((type: BlockEnum) => {
handleChangeCurrentNode(parentNodeId, nodeId, type, sourceHandle)
}, [parentNodeId, nodeId, sourceHandle, handleChangeCurrentNode])
const renderTrigger = useCallback((open: boolean) => {
return (
<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>
)
}, [])
return (
<div
className='relative 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'
>
{
branchName && (
<div className='absolute left-1 -top-[7.5px] flex items-center px-0.5 h-3 bg-white text-[10px] text-gray-500 font-semibold rounded-[5px]'>
{branchName.toLocaleUpperCase()}
</div>
)
}
<BlockIcon
type={data.type}
className='shrink-0 mr-1.5'
/>
<div className='grow'>{data.title}</div>
<BlockSelector
onSelect={handleSelect}
placement='top-end'
offset={{
mainAxis: 6,
crossAxis: 8,
}}
trigger={renderTrigger}
popupClassName='!w-[328px]'
/>
</div>
)
}
export default memo(Item)

View File

@ -0,0 +1,59 @@
import { memo } from 'react'
type LineProps = {
linesNumber: number
}
const Line = ({
linesNumber,
}: LineProps) => {
const svgHeight = linesNumber * 36 + (linesNumber - 1) * 12
return (
<svg className='shrink-0 w-6' style={{ height: svgHeight }}>
{
Array(linesNumber).fill(0).map((_, index) => (
<g key={index}>
{
index === 0 && (
<>
<rect
x={0}
y={16}
width={1}
height={4}
fill='#98A2B3'
/>
<path
d='M0,18 L24,18'
strokeWidth={1}
stroke='#D0D5DD'
fill='none'
/>
</>
)
}
{
index > 0 && (
<path
d={`M0,18 Q12,18 12,28 L12,${index * 48 + 18 - 10} Q12,${index * 48 + 18} 24,${index * 48 + 18}`}
strokeWidth={1}
stroke='#D0D5DD'
fill='none'
/>
)
}
<rect
x={23}
y={index * 48 + 18 - 2}
width={1}
height={4}
fill='#98A2B3'
/>
</g>
))
}
</svg>
)
}
export default memo(Line)

View File

@ -7,7 +7,7 @@ import {
Handle,
Position,
getConnectedEdges,
useStoreApi,
useEdges,
} from 'reactflow'
import { BlockEnum } from '../../../types'
import type { Node } from '../../../types'
@ -15,7 +15,7 @@ import BlockSelector from '../../../block-selector'
import { useWorkflow } from '../../../hooks'
type NodeHandleProps = {
handleId?: string
handleId: string
handleClassName?: string
nodeSelectorClassName?: string
} & Pick<NodeProps, 'id' | 'data'>
@ -28,9 +28,10 @@ export const NodeTargetHandle = ({
nodeSelectorClassName,
}: NodeHandleProps) => {
const [open, setOpen] = useState(false)
const store = useStoreApi()
const connectedEdges = getConnectedEdges([{ id } as Node], store.getState().edges)
const edges = useEdges()
const connectedEdges = getConnectedEdges([{ id } as Node], edges)
const connected = connectedEdges.find(edge => edge.targetHandle === handleId && edge.target === id)
const handleOpenChange = useCallback((v: boolean) => {
setOpen(v)
}, [])
@ -86,8 +87,8 @@ export const NodeSourceHandle = ({
}: NodeHandleProps) => {
const [open, setOpen] = useState(false)
const { handleAddNextNode } = useWorkflow()
const store = useStoreApi()
const connectedEdges = getConnectedEdges([{ id } as Node], store.getState().edges)
const edges = useEdges()
const connectedEdges = getConnectedEdges([{ id } as Node], edges)
const connected = connectedEdges.find(edge => edge.sourceHandle === handleId && edge.source === id)
const handleOpenChange = useCallback((v: boolean) => {
setOpen(v)
@ -97,8 +98,8 @@ export const NodeSourceHandle = ({
handleOpenChange(!open)
}
const handleSelect = useCallback((type: BlockEnum) => {
handleAddNextNode(id, type)
}, [handleAddNextNode, id])
handleAddNextNode(id, type, handleId)
}, [handleAddNextNode, id, handleId])
return (
<>