From ea76f462232ebc0edde124150889fae223ff7ffc Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Thu, 22 Feb 2024 15:01:41 +0800 Subject: [PATCH] block-selector --- .../workflow/block-selector/context.tsx | 137 ------------------ .../workflow/block-selector/index.tsx | 84 ++++++++--- .../workflow/block-selector/tabs.tsx | 4 +- .../workflow/block-selector/types.ts | 3 - web/app/components/workflow/index.tsx | 5 +- .../nodes/_base/components/add-node/hooks.ts | 40 ----- .../nodes/_base/components/add-node/index.tsx | 116 --------------- .../nodes/_base/components/next-step.tsx | 100 ++++++------- .../components/workflow/nodes/_base/node.tsx | 34 +---- web/app/components/workflow/nodes/index.tsx | 4 +- 10 files changed, 123 insertions(+), 404 deletions(-) delete mode 100644 web/app/components/workflow/block-selector/context.tsx delete mode 100644 web/app/components/workflow/block-selector/types.ts delete mode 100644 web/app/components/workflow/nodes/_base/components/add-node/hooks.ts delete mode 100644 web/app/components/workflow/nodes/_base/components/add-node/index.tsx diff --git a/web/app/components/workflow/block-selector/context.tsx b/web/app/components/workflow/block-selector/context.tsx deleted file mode 100644 index f16aa5bb8a..0000000000 --- a/web/app/components/workflow/block-selector/context.tsx +++ /dev/null @@ -1,137 +0,0 @@ -'use client' - -import { useCallback, useRef, useState } from 'react' -import { createContext, useContext } from 'use-context-selector' -import type { - OffsetOptions, - Placement, -} from '@floating-ui/react' -import { - FloatingPortal, - flip, - offset, - shift, - useDismiss, - useFloating, - useInteractions, -} from '@floating-ui/react' -import type { OnSelect } from './types' -import BlockSelector from './index' - -type UpdateParams = { - from?: string - placement?: Placement - offset?: OffsetOptions - open?: boolean - className?: string - callback?: OnSelect -} -export type BlockSelectorContextValue = { - from: string - open: boolean - setOpen: (open: boolean) => void - referenceRef: any - floatingRef: any - floatingStyles: React.CSSProperties - getFloatingProps: any - handleToggle: (v: UpdateParams) => void -} - -export const BlockSelectorContext = createContext({ - from: '', - open: false, - setOpen: () => {}, - referenceRef: null, - floatingRef: null, - floatingStyles: {}, - getFloatingProps: () => {}, - handleToggle: () => {}, -}) -export const useBlockSelectorContext = () => useContext(BlockSelectorContext) - -type BlockSelectorContextProviderProps = { - children: React.ReactNode -} -export const BlockSelectorContextProvider = ({ - children, -}: BlockSelectorContextProviderProps) => { - const [from, setFrom] = useState('node') - const [open, setOpen] = useState(false) - const [placement, setPlacement] = useState('top') - const [offsetValue, setOffsetValue] = useState(0) - const [className, setClassName] = useState('') - const callbackRef = useRef(undefined) - - const { refs, floatingStyles, context } = useFloating({ - placement, - strategy: 'fixed', - open, - onOpenChange: setOpen, - middleware: [ - flip(), - shift(), - offset(offsetValue), - ], - }) - const dismiss = useDismiss(context) - const { getFloatingProps } = useInteractions([ - dismiss, - ]) - - const handleToggle = useCallback(({ - from, - open, - placement, - offset, - className, - callback, - }: UpdateParams) => { - setFrom(from || 'node') - if (open !== undefined) - setOpen(open) - else - setOpen(v => !v) - setPlacement(placement || 'top') - setOffsetValue(offset || 0) - setClassName(className || '') - callbackRef.current = callback - }, []) - - const handleSelect = useCallback((type) => { - if (callbackRef.current) - callbackRef.current(type) - setOpen(v => !v) - }, []) - - return ( - - {children} - { - open && (from === 'node' || from === 'panel') && ( - -
- -
-
- ) - } -
- ) -} diff --git a/web/app/components/workflow/block-selector/index.tsx b/web/app/components/workflow/block-selector/index.tsx index 4c47a850e2..95565500c9 100644 --- a/web/app/components/workflow/block-selector/index.tsx +++ b/web/app/components/workflow/block-selector/index.tsx @@ -1,30 +1,80 @@ import type { FC } from 'react' -import { memo } from 'react' +import { + memo, + useState, +} from 'react' +import type { + OffsetOptions, + Placement, +} from '@floating-ui/react' +import type { BlockEnum } from '../types' import Tabs from './tabs' -import type { OnSelect } from './types' -import { SearchLg } from '@/app/components/base/icons/src/vender/line/general' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import { + Plus02, + SearchLg, +} from '@/app/components/base/icons/src/vender/line/general' type NodeSelectorProps = { - onSelect: OnSelect - className?: string + onSelect: (type: BlockEnum) => void + trigger?: (open: boolean) => React.ReactNode + placement?: Placement + offset?: OffsetOptions + popupClassName?: string + asChild?: boolean } const NodeSelector: FC = ({ onSelect, - className, + trigger, + placement = 'right', + offset = 6, + popupClassName, + asChild, }) => { + const [open, setOpen] = useState(false) return ( -
-
-
- - + + setOpen(v => !v)}> + { + trigger + ? trigger(open) + : ( + + ) + } + + +
+
+
+ + +
+
+
-
- -
+ + ) } diff --git a/web/app/components/workflow/block-selector/tabs.tsx b/web/app/components/workflow/block-selector/tabs.tsx index ce197bf0b6..03fa52bf97 100644 --- a/web/app/components/workflow/block-selector/tabs.tsx +++ b/web/app/components/workflow/block-selector/tabs.tsx @@ -4,7 +4,7 @@ import { useState, } from 'react' import BlockIcon from '../block-icon' -import type { OnSelect } from './types' +import type { BlockEnum } from '../types' import { BLOCK_CLASSIFICATIONS, BLOCK_GROUP_BY_CLASSIFICATION, @@ -12,7 +12,7 @@ import { } from './constants' export type TabsProps = { - onSelect: OnSelect + onSelect: (type: BlockEnum) => void } const Tabs: FC = ({ onSelect, diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts deleted file mode 100644 index b14ae1950b..0000000000 --- a/web/app/components/workflow/block-selector/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { BlockEnum } from '../types' - -export type OnSelect = (type: BlockEnum) => void diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 2a76e0128b..0d9727f420 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -23,7 +23,6 @@ import DebugAndPreview from './debug-and-preview' import ZoomInOut from './zoom-in-out' import CustomEdge from './custom-edge' import type { Node } from './types' -import { BlockSelectorContextProvider } from './block-selector/context' const nodeTypes = { custom: CustomNode, @@ -97,9 +96,7 @@ const WorkflowWrap: FC = ({ handleAddNextNode, handleUpdateNodeData, }}> - - - + ) } diff --git a/web/app/components/workflow/nodes/_base/components/add-node/hooks.ts b/web/app/components/workflow/nodes/_base/components/add-node/hooks.ts deleted file mode 100644 index 4b1898efad..0000000000 --- a/web/app/components/workflow/nodes/_base/components/add-node/hooks.ts +++ /dev/null @@ -1,40 +0,0 @@ -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, - } -} diff --git a/web/app/components/workflow/nodes/_base/components/add-node/index.tsx b/web/app/components/workflow/nodes/_base/components/add-node/index.tsx deleted file mode 100644 index 1444d022bb..0000000000 --- a/web/app/components/workflow/nodes/_base/components/add-node/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -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 = ({ - 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) => { - 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 ( - <> - - { - isOpen && hasBranches && ( - -
- { - branches.map(branch => ( -
{ - setDismissEnable(false) - handleToggle({ - open: true, - placement: 'right', - offset: 6, - callback: onAddNextNode, - }) - }} - > - {branch.name} -
- )) - } -
-
- ) - } - - ) -} - -export default memo(AddNode) diff --git a/web/app/components/workflow/nodes/_base/components/next-step.tsx b/web/app/components/workflow/nodes/_base/components/next-step.tsx index 9e757c764c..0626405376 100644 --- a/web/app/components/workflow/nodes/_base/components/next-step.tsx +++ b/web/app/components/workflow/nodes/_base/components/next-step.tsx @@ -9,7 +9,7 @@ import BlockIcon from '../../../block-icon' import type { Node } from '../../../types' import { BlockEnum } from '../../../types' import { useWorkflowContext } from '../../../context' -import { useBlockSelectorContext } from '../../../block-selector/context' +import BlockSelector from '../../../block-selector' import { Plus } from '@/app/components/base/icons/src/vender/line/general' import Button from '@/app/components/base/button' @@ -19,23 +19,43 @@ type NextStepProps = { const NextStep: FC = ({ selectedNode, }) => { - const { - from, - open, - referenceRef, - handleToggle, - } = useBlockSelectorContext() const { nodes, edges, - handleAddNextNode, } = useWorkflowContext() const outgoers = useMemo(() => { return getOutgoers(selectedNode, nodes, edges) }, [selectedNode, nodes, edges]) - const handleSelectBlock = useCallback((type: BlockEnum) => { - handleAddNextNode(selectedNode, type) - }, [selectedNode, handleAddNextNode]) + + const renderAddNextNodeTrigger = useCallback((open: boolean) => { + return ( +
+
+ +
+ SELECT NEXT BLOCK +
+ ) + }, []) + + const renderChangeCurrentNodeTrigger = useCallback((open: boolean) => { + return ( + + ) + }, []) return (
@@ -55,54 +75,28 @@ const NextStep: FC = ({ className='shrink-0 mr-1.5' />
{outgoer.data.name}
-
{ - handleToggle({ - from: 'panel', - className: 'w-[328px]', - placement: 'top-end', - offset: { - mainAxis: 6, - crossAxis: 8, - }, - }) + {}} + placement='top-end' + offset={{ + mainAxis: 6, + crossAxis: 8, }} - > - -
+ trigger={renderChangeCurrentNodeTrigger} + popupClassName='!w-[328px]' + />
)) } { (!outgoers.length || selectedNode.data.type === BlockEnum.IfElse) && ( -
{ - handleToggle({ - from: 'panel', - className: 'w-[328px]', - callback: handleSelectBlock, - }) - }} - ref={from === 'panel' ? referenceRef : null} - 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 && from === 'panel' && '!bg-gray-100'} - `} - > -
- -
- SELECT NEXT BLOCK -
+ {}} + placement='top' + offset={0} + trigger={renderAddNextNodeTrigger} + popupClassName='!w-[328px]' + /> ) }
diff --git a/web/app/components/workflow/nodes/_base/node.tsx b/web/app/components/workflow/nodes/_base/node.tsx index 1e51253658..7272653e38 100644 --- a/web/app/components/workflow/nodes/_base/node.tsx +++ b/web/app/components/workflow/nodes/_base/node.tsx @@ -5,16 +5,13 @@ import type { import { cloneElement, memo, - useCallback, useMemo, } from 'react' import type { NodeProps } from 'reactflow' -import { getOutgoers } from 'reactflow' import { useWorkflowContext } from '../../context' -import { BlockEnum } from '../../types' import NodeControl from '../../node-control' import BlockIcon from '../../block-icon' -import AddNode from './components/add-node/index' +import BlockSelector from '../../block-selector' type BaseNodeProps = { children: ReactElement @@ -27,34 +24,12 @@ const BaseNode: FC = ({ }) => { const { nodes, - edges, selectedNodeId, handleSelectedNodeIdChange, - handleAddNextNode, } = useWorkflowContext() const currentNode = useMemo(() => { return nodes.find(node => node.id === nodeId) }, [nodeId, nodes]) - const outgoers = useMemo(() => { - return getOutgoers(currentNode!, nodes, edges) - }, [currentNode, nodes, edges]) - 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 (
= ({
Define the initial parameters for launching a workflow
- {}} + asChild />
) diff --git a/web/app/components/workflow/nodes/index.tsx b/web/app/components/workflow/nodes/index.tsx index 35e3202d85..6d1e2562f4 100644 --- a/web/app/components/workflow/nodes/index.tsx +++ b/web/app/components/workflow/nodes/index.tsx @@ -25,7 +25,7 @@ const CustomNode = ({ type='target' position={Position.Left} className={` - !top-4 !left-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1] + !top-[17px] !left-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1] after:absolute after:w-0.5 after:h-2 after:-left-0.5 after:top-1 after:bg-primary-500 ${data.type === BlockEnum.Start && 'opacity-0'} `} @@ -41,7 +41,7 @@ const CustomNode = ({ type='source' position={Position.Right} className={` - !top-4 !right-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 + !top-[17px] !right-0 !w-4 !h-4 !bg-transparent !rounded-none !outline-none !border-none !translate-y-0 z-[1] after:absolute after:w-0.5 after:h-2 after:-right-0.5 after:top-1 after:bg-primary-500 `} />