From 074e660a6745760217b9ec6100a8b314d865dec4 Mon Sep 17 00:00:00 2001 From: Joel Date: Thu, 31 Oct 2024 11:37:47 +0800 Subject: [PATCH] feat: can add tree view --- .../plugins/test/tools-picker/page.tsx | 3 +- web/app/components/tools/types.ts | 10 +- .../workflow/block-selector/all-tools.tsx | 2 +- .../workflow/block-selector/index-bar.tsx | 37 +++++- .../market-place-plugin/list.tsx | 2 +- .../workflow/block-selector/tool-item.tsx | 117 ------------------ .../block-selector/tool/action-item.tsx | 64 ++++++++++ .../tool/tool-list-flat-view/list.tsx | 16 +++ .../tool/tool-list-tree-view/item.tsx | 41 ++++++ .../tool/tool-list-tree-view/list.tsx | 33 +++++ .../workflow/block-selector/tool/tool.tsx | 95 ++++++++++++++ .../workflow/block-selector/tools.tsx | 97 +++++++-------- .../block-selector/view-type-select.tsx | 6 +- 13 files changed, 343 insertions(+), 180 deletions(-) delete mode 100644 web/app/components/workflow/block-selector/tool-item.tsx create mode 100644 web/app/components/workflow/block-selector/tool/action-item.tsx create mode 100644 web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx create mode 100644 web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx create mode 100644 web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx create mode 100644 web/app/components/workflow/block-selector/tool/tool.tsx diff --git a/web/app/(commonLayout)/plugins/test/tools-picker/page.tsx b/web/app/(commonLayout)/plugins/test/tools-picker/page.tsx index e2b9bd9dc8..7fdf66ebcd 100644 --- a/web/app/(commonLayout)/plugins/test/tools-picker/page.tsx +++ b/web/app/(commonLayout)/plugins/test/tools-picker/page.tsx @@ -22,7 +22,8 @@ const ToolsPicker = () => { setCustomTools(customTools) setWorkflowTools(workflowTools) })() - }) + }, []) + return (
(ViewType.list) + const [activeView, setActiveView] = useState(ViewType.flat) const tools = useMemo(() => { let mergedTools: ToolWithProvider[] = [] diff --git a/web/app/components/workflow/block-selector/index-bar.tsx b/web/app/components/workflow/block-selector/index-bar.tsx index 8068d9ecbc..e21bcedeb9 100644 --- a/web/app/components/workflow/block-selector/index-bar.tsx +++ b/web/app/components/workflow/block-selector/index-bar.tsx @@ -1,8 +1,25 @@ import { pinyin } from 'pinyin-pro' import type { FC, RefObject } from 'react' +import type { ToolWithProvider } from '../types' +import { CollectionType } from '../../tools/types' -export const groupItems = (items: Array, getFirstChar: (item: string) => string) => { - const groups = items.reduce((acc, item) => { +/* +{ + A: { + 'google': [ // plugin organize name + ...tools + ], + 'custom': [ // custom tools + ...tools + ], + 'workflow': [ // workflow as tools + ...tools + ] + } +} +*/ +export const groupItems = (items: ToolWithProvider[], getFirstChar: (item: ToolWithProvider) => string) => { + const groups = items.reduce((acc: Record>, item) => { const firstChar = getFirstChar(item) if (!firstChar || firstChar.length === 0) return acc @@ -19,9 +36,21 @@ export const groupItems = (items: Array, getFirstChar: (item: string) => st letter = '#' if (!acc[letter]) - acc[letter] = [] + acc[letter] = {} + + let groupName: string = '' + if (item.type === CollectionType.builtIn) + groupName = item.author + else if (item.type === CollectionType.custom) + groupName = 'custom' + else + groupName = 'workflow' + + if (!acc[letter][groupName]) + acc[letter][groupName] = [] + + acc[letter][groupName].push(item) - acc[letter].push(item) return acc }, {}) diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx index a3e828c5b0..3da4e04af5 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -54,7 +54,7 @@ const List = ({ window.open(urlWithSearchText, '_blank') } - if (!hasSearchText) { + if (hasSearchText) { return ( void -} - -const ToolItem: FC = ({ - className, - isToolPlugin, - provider, - payload, - onSelect, -}) => { - const language = useGetLanguage() - const [isFold, { - toggle: toggleFold, - }] = useBoolean(false) - - const FoldIcon = isFold ? RiArrowDownSLine : RiArrowRightSLine - - const actions = [ - 'DuckDuckGo AI Search', - 'DuckDuckGo Connect', - ] - - return ( - - -
{payload.label[language]}
-
{payload.description[language]}
-
- )} - > -
-
{ - if (isToolPlugin) { - toggleFold() - return - } - onSelect(BlockEnum.Tool, { - provider_id: provider.id, - provider_type: provider.type, - provider_name: provider.name, - tool_name: payload.name, - tool_label: payload.label[language], - title: payload.label[language], - }) - }} - > -
- -
{payload.label[language]}
-
- {isToolPlugin && ( - - )} -
- {(!isFold && isToolPlugin) && ( -
- {actions.map(action => ( -
{ - onSelect(BlockEnum.Tool, { - provider_id: provider.id, - provider_type: provider.type, - provider_name: provider.name, - tool_name: payload.name, - tool_label: payload.label[language], - title: payload.label[language], - }) - }} - > -
{action}
-
- ))} -
- )} -
- - - ) -} -export default React.memo(ToolItem) diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx new file mode 100644 index 0000000000..c233200b05 --- /dev/null +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -0,0 +1,64 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { ToolWithProvider } from '../../types' +import { BlockEnum } from '../../types' +import type { ToolDefaultValue } from '../types' +import Tooltip from '@/app/components/base/tooltip' +import type { Tool } from '@/app/components/tools/types' +import { useGetLanguage } from '@/context/i18n' +import BlockIcon from '../../block-icon' + +type Props = { + className?: string + provider: ToolWithProvider + payload: Tool + onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void +} + +const ToolItem: FC = ({ + className, + provider, + payload, + onSelect, +}) => { + const language = useGetLanguage() + + return ( + + +
{payload.label[language]}
+
{payload.description[language]}
+ + )} + > +
{ + onSelect(BlockEnum.Tool, { + provider_id: provider.id, + provider_type: provider.type, + provider_name: provider.name, + tool_name: payload.name, + tool_label: payload.label[language], + title: payload.label[language], + }) + }} + > +
{payload.name}
+
+
+ ) +} +export default React.memo(ToolItem) diff --git a/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx new file mode 100644 index 0000000000..3639da5f2b --- /dev/null +++ b/web/app/components/workflow/block-selector/tool/tool-list-flat-view/list.tsx @@ -0,0 +1,16 @@ +'use client' +import type { FC } from 'react' +import React from 'react' + +type Props = { + +} + +const ToolViewFlatView: FC = () => { + return ( +
+ list... +
+ ) +} +export default React.memo(ToolViewFlatView) diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx new file mode 100644 index 0000000000..68bc97ee85 --- /dev/null +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/item.tsx @@ -0,0 +1,41 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { ToolWithProvider } from '../../../types' +import Tool from '../tool' +import type { BlockEnum } from '../../../types' +import { ViewType } from '../../view-type-select' +import type { ToolDefaultValue } from '../../types' + +type Props = { + groupName: string + toolList: ToolWithProvider[] + onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void +} + +const Item: FC = ({ + groupName, + toolList, + onSelect, +}) => { + return ( +
+
+ {groupName} +
+
+ {toolList.map((tool: ToolWithProvider) => ( + + ))} +
+
+ ) +} + +export default React.memo(Item) diff --git a/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx new file mode 100644 index 0000000000..a621f08747 --- /dev/null +++ b/web/app/components/workflow/block-selector/tool/tool-list-tree-view/list.tsx @@ -0,0 +1,33 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { ToolWithProvider } from '../../../types' +import type { BlockEnum } from '../../../types' +import type { ToolDefaultValue } from '../../types' +import Item from './item' + +type Props = { + payload: Record + onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void +} + +const OrgTools: FC = ({ + payload, + onSelect, +}) => { + if (!payload) return null + + return ( +
+ {Object.keys(payload).map(groupName => ( + + ))} +
+ ) +} +export default React.memo(OrgTools) diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx new file mode 100644 index 0000000000..fd12a13b44 --- /dev/null +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -0,0 +1,95 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import cn from '@/utils/classnames' +import { RiArrowDownSLine, RiArrowRightSLine } from '@remixicon/react' +import { useGetLanguage } from '@/context/i18n' +import { CollectionType } from '../../../tools/types' +import type { ToolWithProvider } from '../../types' +import { BlockEnum } from '../../types' +import type { ToolDefaultValue } from '../types' +import { ViewType } from '../view-type-select' +import ActonItem from './action-item' +import BlockIcon from '../../block-icon' + +import { useBoolean } from 'ahooks' + +type Props = { + className?: string + payload: ToolWithProvider + viewType: ViewType + isShowLetterIndex: boolean + onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void +} + +const Tool: FC = ({ + className, + payload, + viewType, + isShowLetterIndex, + onSelect, +}) => { + const language = useGetLanguage() + const isTreeView = viewType === ViewType.tree + const actions = payload.tools + const isToolPlugin = payload.type === CollectionType.builtIn + const [isFold, { + toggle: toggleFold, + }] = useBoolean(false) + const FoldIcon = isFold ? RiArrowDownSLine : RiArrowRightSLine + const { + label, + } = payload + + return ( +
+
+
{ + // if (isToolPlugin) { + // toggleFold() + // return + // } + // onSelect(BlockEnum.Tool, { + // provider_id: provider.id, + // provider_type: provider.type, + // provider_name: provider.name, + // tool_name: payload.name, + // tool_label: payload.label[language], + // title: payload.label[language], + // }) + // }} + > +
+ +
{payload.label[language]}
+
+ {isToolPlugin && ( + + )} +
+ + {isToolPlugin && ( + actions.map(action => ( + + )) + )} +
+
+ ) +} +export default React.memo(Tool) diff --git a/web/app/components/workflow/block-selector/tools.tsx b/web/app/components/workflow/block-selector/tools.tsx index a40d2e40c4..409a0c87f9 100644 --- a/web/app/components/workflow/block-selector/tools.tsx +++ b/web/app/components/workflow/block-selector/tools.tsx @@ -1,18 +1,17 @@ import { memo, - useCallback, + useMemo, useRef, } from 'react' import { useTranslation } from 'react-i18next' import type { BlockEnum, ToolWithProvider } from '../types' -import { CollectionType } from '../../tools/types' import IndexBar, { groupItems } from './index-bar' import type { ToolDefaultValue } from './types' -import ToolItem from './tool-item' import { ViewType } from './view-type-select' import Empty from '@/app/components/tools/add-tool-modal/empty' import { useGetLanguage } from '@/context/i18n' -import cn from '@/utils/classnames' +import ToolListTreeView from './tool/tool-list-tree-view/list' +import ToolListFlatView from './tool/tool-list-flat-view/list' type ToolsProps = { showWorkflowEmpty: boolean @@ -28,52 +27,41 @@ const Blocks = ({ }: ToolsProps) => { const { t } = useTranslation() const language = useGetLanguage() - const isListView = viewType === ViewType.list + const isFlatView = viewType === ViewType.flat const isTreeView = viewType === ViewType.tree + const isShowLetterIndex = isFlatView && tools.length > 10 - const { letters, groups: groupedTools } = groupItems(tools, tool => (tool as any).label[language][0]) - const toolRefs = useRef({}) - - const renderGroup = useCallback((toolWithProvider: ToolWithProvider) => { - const list = toolWithProvider.tools - - return ( -
- {isTreeView && ( -
- {toolWithProvider.label[language]} -
- )} - { - list.map(tool => ( - - )) - } -
- ) - }, [onSelect, language]) - - const renderLetterGroup = (letter: string) => { - const tools = groupedTools[letter] - return ( -
((toolRefs as any).current[letter] = el) as any} - > - {tools.map(renderGroup)} -
- ) + /* + treeViewToolsData: + { + A: { + 'google': [ // plugin organize name + ...tools + ], + 'custom': [ // custom tools + ...tools + ], + 'workflow': [ // workflow as tools + ...tools + ] + } } + */ + const { letters, groups: withLetterAndGroupViewToolsData } = groupItems(tools, tool => (tool as any).label[language][0]) + const treeViewToolsData = useMemo(() => { + const result: Record = {} + Object.keys(withLetterAndGroupViewToolsData).forEach((letter) => { + Object.keys(withLetterAndGroupViewToolsData[letter]).forEach((groupName) => { + if (!result[groupName]) + result[groupName] = [] + + result[groupName].push(...withLetterAndGroupViewToolsData[letter][groupName]) + }) + }) + return result + }, [withLetterAndGroupViewToolsData]) + + const toolRefs = useRef({}) return (
@@ -87,8 +75,19 @@ const Blocks = ({
)} - {!!tools.length && letters.map(renderLetterGroup)} - {isListView && tools.length > 10 && } + {!!tools.length && ( + isFlatView ? ( + + ) : ( + + ) + )} + + {isShowLetterIndex && } ) } diff --git a/web/app/components/workflow/block-selector/view-type-select.tsx b/web/app/components/workflow/block-selector/view-type-select.tsx index d7ba5b74a9..d76926e619 100644 --- a/web/app/components/workflow/block-selector/view-type-select.tsx +++ b/web/app/components/workflow/block-selector/view-type-select.tsx @@ -5,7 +5,7 @@ import { RiNodeTree, RiSortAlphabetAsc } from '@remixicon/react' import cn from '@/utils/classnames' export enum ViewType { - list = 'list', + flat = 'flat', tree = 'tree', } @@ -31,12 +31,12 @@ const ViewTypeSelect: FC = ({