From 39a6f0943d5cf45aa9853c63a9b6f2c0d4278eab Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Sat, 12 Oct 2024 18:02:24 +0800 Subject: [PATCH 1/2] marketplace --- .../plugins/marketplace/context.tsx | 45 +++++++++++++++++++ .../marketplace/description/wrapper.tsx | 39 ---------------- .../plugins/marketplace/header/index.tsx | 20 --------- .../plugins/marketplace/header/wrapper.tsx | 27 ----------- .../components/plugins/marketplace/index.tsx | 23 +++++----- .../marketplace/intersection-line/hooks.ts | 17 +++++-- .../marketplace/intersection-line/index.tsx | 17 ++----- .../plugins/marketplace/list/index.tsx | 4 +- .../plugins/marketplace/list/wrapper.tsx | 39 ---------------- .../marketplace/plugin-type-switch.tsx | 4 +- .../plugins/marketplace/search-box/index.tsx | 8 ++-- .../marketplace/search-box/wrapper.tsx | 14 ------ .../plugins/plugin-page/context.tsx | 8 ---- .../components/plugins/plugin-page/index.tsx | 4 +- 14 files changed, 83 insertions(+), 186 deletions(-) create mode 100644 web/app/components/plugins/marketplace/context.tsx delete mode 100644 web/app/components/plugins/marketplace/description/wrapper.tsx delete mode 100644 web/app/components/plugins/marketplace/header/index.tsx delete mode 100644 web/app/components/plugins/marketplace/header/wrapper.tsx delete mode 100644 web/app/components/plugins/marketplace/list/wrapper.tsx delete mode 100644 web/app/components/plugins/marketplace/search-box/wrapper.tsx diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx new file mode 100644 index 0000000000..bbadb4bf3a --- /dev/null +++ b/web/app/components/plugins/marketplace/context.tsx @@ -0,0 +1,45 @@ +'use client' + +import type { ReactNode } from 'react' +import { + useState, +} from 'react' +import { + createContext, + useContextSelector, +} from 'use-context-selector' + +export type MarketplaceContextValue = { + intersected: boolean + setIntersected: (intersected: boolean) => void +} + +export const MarketplaceContext = createContext({ + intersected: true, + setIntersected: () => {}, +}) + +type MarketplaceContextProviderProps = { + children: ReactNode +} + +export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) { + return useContextSelector(MarketplaceContext, selector) +} + +export const MarketplaceContextProvider = ({ + children, +}: MarketplaceContextProviderProps) => { + const [intersected, setIntersected] = useState(true) + + return ( + + {children} + + ) +} diff --git a/web/app/components/plugins/marketplace/description/wrapper.tsx b/web/app/components/plugins/marketplace/description/wrapper.tsx deleted file mode 100644 index 91dd3a2ba6..0000000000 --- a/web/app/components/plugins/marketplace/description/wrapper.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import { useCallback } from 'react' -import IntersectionLine from '../intersection-line' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' - -type DescriptionWrapperProps = { - children: ReactNode -} -const DescriptionWrapper = ({ - children, -}: DescriptionWrapperProps) => { - const containerRef = usePluginPageContext(v => v.containerRef) - const scrollDisabled = usePluginPageContext(v => v.scrollDisabled) - const setScrollDisabled = usePluginPageContext(v => v.setScrollDisabled) - - const handleScrollIntersectionChange = useCallback((isIntersecting: boolean) => { - if (!isIntersecting && !scrollDisabled) { - setScrollDisabled(true) - setTimeout(() => { - if (containerRef && containerRef.current) - containerRef.current.scrollTop = 0 - }, 100) - } - }, [containerRef, scrollDisabled, setScrollDisabled]) - - return !scrollDisabled && ( - <> - {children} - - - ) -} - -export default DescriptionWrapper diff --git a/web/app/components/plugins/marketplace/header/index.tsx b/web/app/components/plugins/marketplace/header/index.tsx deleted file mode 100644 index 3caeea6c7f..0000000000 --- a/web/app/components/plugins/marketplace/header/index.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import Description from '../description' -import DescriptionWrapper from '../description/wrapper' -import SearchBoxWrapper from '../search-box/wrapper' -import PluginTypeSwitch from '../plugin-type-switch' - -const Header = () => { - return ( - <> - - - -
- -
- - - ) -} - -export default Header diff --git a/web/app/components/plugins/marketplace/header/wrapper.tsx b/web/app/components/plugins/marketplace/header/wrapper.tsx deleted file mode 100644 index a0c45bbc1a..0000000000 --- a/web/app/components/plugins/marketplace/header/wrapper.tsx +++ /dev/null @@ -1,27 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' -import cn from '@/utils/classnames' - -type HeaderWrapperProps = { - children: ReactNode -} -const HeaderWrapper = ({ - children, -}: HeaderWrapperProps) => { - const scrollDisabled = usePluginPageContext(v => v.scrollDisabled) - - return ( -
- {children} -
- ) -} - -export default HeaderWrapper diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 5d737db448..e85411c1ed 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -1,18 +1,19 @@ -import Header from './header' -import HeaderWrapper from './header/wrapper' +import { MarketplaceContextProvider } from './context' +import Description from './description' +import IntersectionLine from './intersection-line' +import SearchBox from './search-box' +import PluginTypeSwitch from './plugin-type-switch' import List from './list' -import ListWrapper from './list/wrapper' const Marketplace = () => { return ( -
- -
- - - - -
+ + + + + + + ) } diff --git a/web/app/components/plugins/marketplace/intersection-line/hooks.ts b/web/app/components/plugins/marketplace/intersection-line/hooks.ts index a31d196179..2ebc7842df 100644 --- a/web/app/components/plugins/marketplace/intersection-line/hooks.ts +++ b/web/app/components/plugins/marketplace/intersection-line/hooks.ts @@ -1,21 +1,30 @@ import { useEffect } from 'react' +import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' +import { useMarketplaceContext } from '@/app/components/plugins/marketplace/context' export const useScrollIntersection = ( - containerRef: React.RefObject, anchorRef: React.RefObject, - callback: (isIntersecting: boolean) => void, ) => { + const containerRef = usePluginPageContext(v => v.containerRef) + const intersected = useMarketplaceContext(v => v.intersected) + const setIntersected = useMarketplaceContext(v => v.setIntersected) + useEffect(() => { let observer: IntersectionObserver | undefined if (containerRef?.current && anchorRef.current) { observer = new IntersectionObserver((entries) => { const isIntersecting = entries[0].isIntersecting - callback(isIntersecting) + + if (isIntersecting && !intersected) + setIntersected(true) + + if (!isIntersecting && intersected) + setIntersected(false) }, { root: containerRef.current, }) observer.observe(anchorRef.current) } return () => observer?.disconnect() - }, [containerRef, anchorRef, callback]) + }, [containerRef, anchorRef, intersected, setIntersected]) } diff --git a/web/app/components/plugins/marketplace/intersection-line/index.tsx b/web/app/components/plugins/marketplace/intersection-line/index.tsx index 3805ec6f13..e521d43f8c 100644 --- a/web/app/components/plugins/marketplace/intersection-line/index.tsx +++ b/web/app/components/plugins/marketplace/intersection-line/index.tsx @@ -3,24 +3,13 @@ import { useRef } from 'react' import { useScrollIntersection } from './hooks' -type IntersectionLineProps = { - containerRef: React.RefObject - intersectedCallback: (isIntersecting: boolean) => void -} -const IntersectionLine = ({ - containerRef, - intersectedCallback, -}: IntersectionLineProps) => { +const IntersectionLine = () => { const ref = useRef(null) - useScrollIntersection( - containerRef, - ref, - intersectedCallback, - ) + useScrollIntersection(ref) return ( -
+
) } diff --git a/web/app/components/plugins/marketplace/list/index.tsx b/web/app/components/plugins/marketplace/list/index.tsx index 7a8d6daa5d..fa5975bb5a 100644 --- a/web/app/components/plugins/marketplace/list/index.tsx +++ b/web/app/components/plugins/marketplace/list/index.tsx @@ -7,7 +7,7 @@ const List = () => { const locale = getLocaleOnServer() return ( - <> +
Featured
Our top picks to get you started
@@ -223,7 +223,7 @@ const List = () => { />
- + ) } diff --git a/web/app/components/plugins/marketplace/list/wrapper.tsx b/web/app/components/plugins/marketplace/list/wrapper.tsx deleted file mode 100644 index cf040c8d6b..0000000000 --- a/web/app/components/plugins/marketplace/list/wrapper.tsx +++ /dev/null @@ -1,39 +0,0 @@ -'use client' - -import type { ReactNode } from 'react' -import { usePluginPageContext } from '@/app/components/plugins/plugin-page/context' -import cn from '@/utils/classnames' - -type ListWrapperProps = { - children: ReactNode -} -const ListWrapper = ({ - children, -}: ListWrapperProps) => { - const scrollDisabled = usePluginPageContext(v => v.scrollDisabled) - const setScrollDisabled = usePluginPageContext(v => v.setScrollDisabled) - - return ( - <> - { - scrollDisabled && ( -
- ) - } -
{ - if ((e.target as HTMLElement).scrollTop <= 0) - setScrollDisabled(false) - }} - > - {children} -
- - ) -} - -export default ListWrapper diff --git a/web/app/components/plugins/marketplace/plugin-type-switch.tsx b/web/app/components/plugins/marketplace/plugin-type-switch.tsx index 9c9d73cdb7..16be79db26 100644 --- a/web/app/components/plugins/marketplace/plugin-type-switch.tsx +++ b/web/app/components/plugins/marketplace/plugin-type-switch.tsx @@ -43,7 +43,9 @@ const PluginTypeSwitch = ({ const [activeType, setActiveType] = useState('all') return ( -
+
{ options.map(option => (
void - widthShouldChange?: boolean } const SearchBox = ({ onChange, - widthShouldChange, }: SearchBoxProps) => { + const intersected = useMarketplaceContext(v => v.intersected) const [searchText, setSearchText] = useState('') const [selectedTags, setSelectedTags] = useState([]) @@ -28,8 +28,8 @@ const SearchBox = ({ return (
{ - const scrollDisabled = usePluginPageContext(v => v.scrollDisabled) - - return ( - - ) -} - -export default Wrapper diff --git a/web/app/components/plugins/plugin-page/context.tsx b/web/app/components/plugins/plugin-page/context.tsx index 57e00d9c5d..2d026c7cd1 100644 --- a/web/app/components/plugins/plugin-page/context.tsx +++ b/web/app/components/plugins/plugin-page/context.tsx @@ -3,7 +3,6 @@ import type { ReactNode } from 'react' import { useRef, - useState, } from 'react' import { createContext, @@ -12,14 +11,10 @@ import { export type PluginPageContextValue = { containerRef: React.RefObject - scrollDisabled: boolean - setScrollDisabled: (scrollDisabled: boolean) => void } export const PluginPageContext = createContext({ containerRef: { current: null }, - scrollDisabled: false, - setScrollDisabled: () => {}, }) type PluginPageContextProviderProps = { @@ -34,14 +29,11 @@ export const PluginPageContextProvider = ({ children, }: PluginPageContextProviderProps) => { const containerRef = useRef(null) - const [scrollDisabled, setScrollDisabled] = useState(false) return ( {children} diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index f79e0af275..b7c55f48e3 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -32,7 +32,6 @@ const PluginPage = ({ const { t } = useTranslation() const { setShowPluginSettingModal } = useModalContext() as any const containerRef = usePluginPageContext(v => v.containerRef) - const scrollDisabled = usePluginPageContext(v => v.scrollDisabled) const options = useMemo(() => { return [ @@ -51,12 +50,11 @@ const PluginPage = ({ className={cn('grow relative flex flex-col overflow-y-auto border-t border-divider-subtle', activeTab === 'plugins' ? 'rounded-t-xl bg-components-panel-bg' : 'bg-background-body', - activeTab === 'discover' && scrollDisabled && 'overflow-hidden', )} >
From e2fec587f8bdaf44eda913d7d3b6fe8e899ce026 Mon Sep 17 00:00:00 2001 From: Joel Date: Mon, 14 Oct 2024 18:35:01 +0800 Subject: [PATCH 2/2] feat: from marketplace --- .../workflow/block-selector/all-tools.tsx | 3 + .../market-place-plugin/action.tsx | 56 +++++++++++++++++++ .../market-place-plugin/item.tsx | 54 ++++++++++++++++++ .../market-place-plugin/list.tsx | 35 ++++++++++++ .../workflow/block-selector/tool-item.tsx | 4 +- web/i18n/en-US/plugin.ts | 3 + web/i18n/en-US/workflow.ts | 2 +- web/i18n/zh-Hans/plugin.ts | 3 + 8 files changed, 157 insertions(+), 3 deletions(-) create mode 100644 web/app/components/workflow/block-selector/market-place-plugin/action.tsx create mode 100644 web/app/components/workflow/block-selector/market-place-plugin/item.tsx create mode 100644 web/app/components/workflow/block-selector/market-place-plugin/list.tsx diff --git a/web/app/components/workflow/block-selector/all-tools.tsx b/web/app/components/workflow/block-selector/all-tools.tsx index f8947ad52a..2f75f90fc3 100644 --- a/web/app/components/workflow/block-selector/all-tools.tsx +++ b/web/app/components/workflow/block-selector/all-tools.tsx @@ -12,6 +12,8 @@ import { useToolTabs } from './hooks' import ViewTypeSelect, { ViewType } from './view-type-select' import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' +import PluginList from '@/app/components/workflow/block-selector/market-place-plugin/list' +import { extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock' type AllToolsProps = { searchText: string @@ -71,6 +73,7 @@ const AllTools = ({
+ = () => { + const { t } = useTranslation() + const [open, doSetOpen] = useState(false) + const openRef = useRef(open) + const setOpen = useCallback((v: boolean) => { + doSetOpen(v) + openRef.current = v + }, [doSetOpen]) + + const handleTrigger = useCallback(() => { + setOpen(!openRef.current) + }, [setOpen]) + + return ( + + + + + + + +
+
{t('common.operation.download')}
+ {/* Wait marketplace */} + {/*
{t('common.operation.viewDetail')}
*/} +
+
+
+ ) +} +export default React.memo(OperationDropdown) diff --git a/web/app/components/workflow/block-selector/market-place-plugin/item.tsx b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx new file mode 100644 index 0000000000..385cdbfd56 --- /dev/null +++ b/web/app/components/workflow/block-selector/market-place-plugin/item.tsx @@ -0,0 +1,54 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useContext } from 'use-context-selector' +import { useTranslation } from 'react-i18next' +import Action from './action' +import type { Plugin } from '@/app/components/plugins/types.ts' +import I18n from '@/context/i18n' + +import { formatNumber } from '@/utils/format' + +enum ActionType { + install = 'install', + download = 'download', + // viewDetail = 'viewDetail', // wait for marketplace api +} +type Props = { + payload: Plugin + onAction: (type: ActionType) => void +} + +const Item: FC = ({ + payload, +}) => { + const { t } = useTranslation() + const { locale } = useContext(I18n) + + return ( +
+
+
+
+
{payload.label[locale]}
+
{payload.brief[locale]}
+
+
{payload.org}
+
·
+
{t('plugin.install', { num: formatNumber(payload.install_count || 0) })}
+
+
+ {/* Action */} +
+
{t('plugin.installAction')}
+ +
+
+ +
+ ) +} +export default React.memo(Item) 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 new file mode 100644 index 0000000000..33c00081c6 --- /dev/null +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -0,0 +1,35 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { useTranslation } from 'react-i18next' +import Item from './item' +import type { Plugin } from '@/app/components/plugins/types.ts' + +type Props = { + list: Plugin[] + // onInstall: () => +} + +const List: FC = ({ + list, +}) => { + const { t } = useTranslation() + + return ( +
+
+ {t('plugin.fromMarketplace')} +
+
+ {list.map((item, index) => ( + { }} + /> + ))} +
+
+ ) +} +export default React.memo(List) diff --git a/web/app/components/workflow/block-selector/tool-item.tsx b/web/app/components/workflow/block-selector/tool-item.tsx index 5ee3b399a5..7b004d6198 100644 --- a/web/app/components/workflow/block-selector/tool-item.tsx +++ b/web/app/components/workflow/block-selector/tool-item.tsx @@ -75,13 +75,13 @@ const ToolItem: FC = ({ }) }} > -
+
-
{payload.label[language]}
+
{payload.label[language]}
{isToolPlugin && ( diff --git a/web/i18n/en-US/plugin.ts b/web/i18n/en-US/plugin.ts index c73c6a448e..a82cdf6707 100644 --- a/web/i18n/en-US/plugin.ts +++ b/web/i18n/en-US/plugin.ts @@ -1,5 +1,6 @@ const translation = { from: 'From', + fromMarketplace: 'From Marketplace', endpointsEnabled: '{{num}} sets of endpoints enabled', detailPanel: { operation: { @@ -18,6 +19,8 @@ const translation = { disabled: 'Disabled', modelNum: '{{num}} MODELS INCLUDED', }, + install: '{{num}} installs', + installAction: 'Install', } export default translation diff --git a/web/i18n/en-US/workflow.ts b/web/i18n/en-US/workflow.ts index d5ab6eb728..2e3576b5b8 100644 --- a/web/i18n/en-US/workflow.ts +++ b/web/i18n/en-US/workflow.ts @@ -198,7 +198,7 @@ const translation = { 'searchTool': 'Search tool', 'tools': 'Tools', 'allTool': 'All', - 'builtInTool': 'Built-in', + 'plugin': 'Plugin', 'customTool': 'Custom', 'workflowTool': 'Workflow', 'question-understand': 'Question Understand', diff --git a/web/i18n/zh-Hans/plugin.ts b/web/i18n/zh-Hans/plugin.ts index 9bf3fdeea8..5166ddedc3 100644 --- a/web/i18n/zh-Hans/plugin.ts +++ b/web/i18n/zh-Hans/plugin.ts @@ -1,5 +1,6 @@ const translation = { from: '来自', + fromMarketplace: '来自市场', endpointsEnabled: '{{num}} 组端点已启用', detailPanel: { operation: { @@ -18,6 +19,8 @@ const translation = { disabled: '停用', modelNum: '{{num}} 模型已包含', }, + install: '{{num}} 次安装', + installAction: '安装', } export default translation