mirror of
https://github.com/langgenius/dify.git
synced 2026-09-08 11:04:27 +08:00
fix(web): unify tool plugin selection state (#41470)
This commit is contained in:
parent
17ae89ff22
commit
94dff4dddc
@ -32,13 +32,13 @@ vi.mock('../../hooks', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
const mockCurrentPluginID = vi.fn((): string | undefined => undefined)
|
||||
const mockSetCurrentPluginID = vi.fn()
|
||||
const mockSelectedItem = vi.fn((): { type: 'plugin'; id: string } | undefined => undefined)
|
||||
const mockSetSelectedItem = vi.fn()
|
||||
vi.mock('../../plugin-page/context', () => ({
|
||||
usePluginPageContext: (selector: (v: Record<string, unknown>) => unknown) => {
|
||||
const context = {
|
||||
currentPluginID: mockCurrentPluginID(),
|
||||
setCurrentPluginID: mockSetCurrentPluginID,
|
||||
selectedItem: mockSelectedItem(),
|
||||
setSelectedItem: mockSetSelectedItem,
|
||||
}
|
||||
return selector(context)
|
||||
},
|
||||
@ -174,7 +174,7 @@ describe('PluginItem', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockTheme.mockReturnValue('light')
|
||||
mockCurrentPluginID.mockReturnValue(undefined)
|
||||
mockSelectedItem.mockReturnValue(undefined)
|
||||
mockEnableMarketplace.mockReturnValue(true)
|
||||
mockLangGeniusVersionInfo.mockReturnValue(createLangGeniusVersionInfo('1.0.0'))
|
||||
mockGetValueFromI18nObject.mockImplementation((obj: Record<string, string>) => obj?.en_US || '')
|
||||
@ -588,7 +588,7 @@ describe('PluginItem', () => {
|
||||
|
||||
// ==================== User Interactions Tests ====================
|
||||
describe('User Interactions', () => {
|
||||
it('should call setCurrentPluginID when plugin is clicked', () => {
|
||||
it('should select the plugin when its card is clicked', () => {
|
||||
// Arrange
|
||||
const plugin = createPluginDetail({ plugin_id: 'test-plugin-id' })
|
||||
|
||||
@ -598,12 +598,15 @@ describe('PluginItem', () => {
|
||||
fireEvent.click(pluginContainer)
|
||||
|
||||
// Assert
|
||||
expect(mockSetCurrentPluginID).toHaveBeenCalledWith('test-plugin-id')
|
||||
expect(mockSetSelectedItem).toHaveBeenCalledWith({
|
||||
type: 'plugin',
|
||||
id: 'test-plugin-id',
|
||||
})
|
||||
})
|
||||
|
||||
it('should highlight selected plugin', () => {
|
||||
// Arrange
|
||||
mockCurrentPluginID.mockReturnValue('test-plugin-id')
|
||||
mockSelectedItem.mockReturnValue({ type: 'plugin', id: 'test-plugin-id' })
|
||||
const plugin = createPluginDetail({ plugin_id: 'test-plugin-id' })
|
||||
|
||||
// Act
|
||||
@ -616,7 +619,7 @@ describe('PluginItem', () => {
|
||||
|
||||
it('should not highlight unselected plugin', () => {
|
||||
// Arrange
|
||||
mockCurrentPluginID.mockReturnValue('other-plugin-id')
|
||||
mockSelectedItem.mockReturnValue({ type: 'plugin', id: 'other-plugin-id' })
|
||||
const plugin = createPluginDetail({ plugin_id: 'test-plugin-id' })
|
||||
|
||||
// Act
|
||||
@ -638,8 +641,8 @@ describe('PluginItem', () => {
|
||||
const actionArea = screen.getByTestId('plugin-action').parentElement
|
||||
fireEvent.click(actionArea!)
|
||||
|
||||
// Assert - setCurrentPluginID should not be called
|
||||
expect(mockSetCurrentPluginID).not.toHaveBeenCalled()
|
||||
// Assert - selecting the plugin should not be triggered
|
||||
expect(mockSetSelectedItem).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should only reveal actions on card hover or focus', () => {
|
||||
|
||||
@ -47,8 +47,10 @@ const PluginItem: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const currentPluginID = usePluginPageContext((v) => v.currentPluginID)
|
||||
const setCurrentPluginID = usePluginPageContext((v) => v.setCurrentPluginID)
|
||||
const selectedPluginID = usePluginPageContext((v) =>
|
||||
v.selectedItem?.type === 'plugin' ? v.selectedItem.id : undefined,
|
||||
)
|
||||
const setSelectedItem = usePluginPageContext((v) => v.setSelectedItem)
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
|
||||
const {
|
||||
@ -119,13 +121,13 @@ const PluginItem: FC<Props> = ({
|
||||
<div
|
||||
className={cn(
|
||||
'group/plugin-item relative overflow-hidden rounded-xl border-[1.5px] border-background-section-burn p-1',
|
||||
currentPluginID === plugin_id && 'border-components-option-card-option-selected-border',
|
||||
selectedPluginID === plugin_id && 'border-components-option-card-option-selected-border',
|
||||
source === PluginSource.debugging
|
||||
? 'bg-[repeating-linear-gradient(-45deg,rgba(16,24,40,0.04),rgba(16,24,40,0.04)_5px,rgba(0,0,0,0.02)_5px,rgba(0,0,0,0.02)_10px)]'
|
||||
: 'bg-background-section-burn',
|
||||
)}
|
||||
onClick={() => {
|
||||
setCurrentPluginID(plugin.plugin_id)
|
||||
setSelectedItem({ type: 'plugin', id: plugin.plugin_id })
|
||||
}}
|
||||
>
|
||||
<div
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ReactElement, ReactNode } from 'react'
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { NuqsTestingAdapter } from 'nuqs/adapters/testing'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { createConsoleQueryWrapper } from '@/test/console/query-data'
|
||||
@ -33,15 +34,22 @@ const renderWithProviders = (
|
||||
}
|
||||
|
||||
const Consumer = () => {
|
||||
const currentPluginID = usePluginPageContext((v) => v.currentPluginID)
|
||||
const setCurrentPluginID = usePluginPageContext((v) => v.setCurrentPluginID)
|
||||
const selectedItem = usePluginPageContext((v) => v.selectedItem)
|
||||
const setSelectedItem = usePluginPageContext((v) => v.setSelectedItem)
|
||||
const options = usePluginPageContext((v) => v.options)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<output aria-label="Current plugin">{currentPluginID ?? 'none'}</output>
|
||||
<output aria-label="Selected item">
|
||||
{selectedItem ? `${selectedItem.type}:${selectedItem.id}` : 'none'}
|
||||
</output>
|
||||
<output aria-label="Available tabs">{options.length}</output>
|
||||
<button onClick={() => setCurrentPluginID('plugin-1')}>select plugin</button>
|
||||
<button onClick={() => setSelectedItem({ type: 'builtinTool', id: 'builtin-1' })}>
|
||||
select builtin tool
|
||||
</button>
|
||||
<button onClick={() => setSelectedItem({ type: 'plugin', id: 'plugin-1' })}>
|
||||
select plugin
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -62,7 +70,9 @@ describe('PluginPageContextProvider', () => {
|
||||
expect(screen.getByRole('status', { name: 'Available tabs' })).toHaveTextContent('1')
|
||||
})
|
||||
|
||||
it('keeps the query-state tab and updates the current plugin id', () => {
|
||||
it('keeps the query-state tab and replaces the selected item', async () => {
|
||||
const user = userEvent.setup()
|
||||
|
||||
renderWithProviders(
|
||||
<PluginPageContextProvider>
|
||||
<Consumer />
|
||||
@ -70,9 +80,17 @@ describe('PluginPageContextProvider', () => {
|
||||
{ enableMarketplace: true, searchParams: '?tab=discover' },
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('select plugin'))
|
||||
await user.click(screen.getByRole('button', { name: 'select builtin tool' }))
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Current plugin' })).toHaveTextContent('plugin-1')
|
||||
expect(screen.getByRole('status', { name: 'Selected item' })).toHaveTextContent(
|
||||
'builtinTool:builtin-1',
|
||||
)
|
||||
|
||||
await user.click(screen.getByRole('button', { name: 'select plugin' }))
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Selected item' })).toHaveTextContent(
|
||||
'plugin:plugin-1',
|
||||
)
|
||||
expect(screen.getByRole('status', { name: 'Available tabs' })).toHaveTextContent('2')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import type { PluginDetail } from '../../types'
|
||||
import type { PluginPageSelection } from '../context'
|
||||
import type { Collection } from '@/app/components/tools/types'
|
||||
import { act, fireEvent, render, screen } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import {
|
||||
getStepByStepTourTargetSelector,
|
||||
@ -15,14 +17,18 @@ const mockState = vi.hoisted(() => ({
|
||||
tags: [] as string[],
|
||||
searchQuery: '',
|
||||
},
|
||||
currentPluginID: undefined as string | undefined,
|
||||
selectedItem: undefined as PluginPageSelection | undefined,
|
||||
}))
|
||||
const mockContextSubscribers = vi.hoisted(() => new Set<() => void>())
|
||||
const mockSystemFeatures = vi.hoisted(() => ({
|
||||
enableMarketplace: true,
|
||||
}))
|
||||
|
||||
const mockSetFilters = vi.fn()
|
||||
const mockSetCurrentPluginID = vi.fn()
|
||||
const mockSetSelectedItem = vi.fn((item?: PluginPageSelection) => {
|
||||
mockState.selectedItem = item
|
||||
mockContextSubscribers.forEach((subscriber) => subscriber())
|
||||
})
|
||||
const mockLoadNextPage = vi.fn()
|
||||
const mockInvalidateInstalledPluginList = vi.fn()
|
||||
const mockRemoveFilteredInstalledPluginPageOnUnmount = vi.fn()
|
||||
@ -55,22 +61,40 @@ vi.mock('../../hooks', () => ({
|
||||
}),
|
||||
}))
|
||||
|
||||
vi.mock('../context', () => ({
|
||||
usePluginPageContext: (
|
||||
selector: (value: {
|
||||
filters: typeof mockState.filters
|
||||
setFilters: typeof mockSetFilters
|
||||
currentPluginID: string | undefined
|
||||
setCurrentPluginID: typeof mockSetCurrentPluginID
|
||||
}) => unknown,
|
||||
) =>
|
||||
selector({
|
||||
filters: mockState.filters,
|
||||
setFilters: mockSetFilters,
|
||||
currentPluginID: mockState.currentPluginID,
|
||||
setCurrentPluginID: mockSetCurrentPluginID,
|
||||
}),
|
||||
}))
|
||||
vi.mock('../context', async () => {
|
||||
const { useSyncExternalStore } = await import('react')
|
||||
|
||||
return {
|
||||
usePluginPageContext: (
|
||||
selector: (value: {
|
||||
filters: typeof mockState.filters
|
||||
setFilters: typeof mockSetFilters
|
||||
selectedItem: PluginPageSelection | undefined
|
||||
setSelectedItem: typeof mockSetSelectedItem
|
||||
}) => unknown,
|
||||
) =>
|
||||
useSyncExternalStore(
|
||||
(subscriber) => {
|
||||
mockContextSubscribers.add(subscriber)
|
||||
return () => mockContextSubscribers.delete(subscriber)
|
||||
},
|
||||
() =>
|
||||
selector({
|
||||
filters: mockState.filters,
|
||||
setFilters: mockSetFilters,
|
||||
selectedItem: mockState.selectedItem,
|
||||
setSelectedItem: mockSetSelectedItem,
|
||||
}),
|
||||
() =>
|
||||
selector({
|
||||
filters: mockState.filters,
|
||||
setFilters: mockSetFilters,
|
||||
selectedItem: mockState.selectedItem,
|
||||
setSelectedItem: mockSetSelectedItem,
|
||||
}),
|
||||
),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock('../filter-management', () => ({
|
||||
default: ({
|
||||
@ -140,13 +164,19 @@ vi.mock('../list', () => ({
|
||||
}) => (
|
||||
<div data-testid="plugin-list">
|
||||
{pluginList.map((plugin, index) => (
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
key={plugin.plugin_id}
|
||||
aria-pressed={
|
||||
mockState.selectedItem?.type === 'plugin' &&
|
||||
mockState.selectedItem.id === plugin.plugin_id
|
||||
}
|
||||
data-step-by-step-tour-target={index === 0 ? firstPluginTarget : undefined}
|
||||
data-testid="plugin-list-item"
|
||||
onClick={() => mockSetSelectedItem({ type: 'plugin', id: plugin.plugin_id })}
|
||||
>
|
||||
{plugin.plugin_id}
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
{children}
|
||||
</div>
|
||||
@ -250,13 +280,14 @@ vi.mock('@/app/components/plugins/plugin-detail-panel', () => ({
|
||||
detail?: PluginDetail
|
||||
onHide: () => void
|
||||
onUpdate: () => void
|
||||
}) => (
|
||||
<div data-testid="plugin-detail-panel">
|
||||
<span>{detail?.plugin_id ?? 'none'}</span>
|
||||
<button onClick={onHide}>hide detail</button>
|
||||
<button onClick={onUpdate}>refresh detail</button>
|
||||
</div>
|
||||
),
|
||||
}) =>
|
||||
detail ? (
|
||||
<div data-testid="plugin-detail-panel">
|
||||
<span>{detail.plugin_id}</span>
|
||||
<button onClick={onHide}>hide detail</button>
|
||||
<button onClick={onUpdate}>refresh detail</button>
|
||||
</div>
|
||||
) : null,
|
||||
}))
|
||||
|
||||
const createPlugin = (
|
||||
@ -324,7 +355,7 @@ describe('PluginsPanel', () => {
|
||||
},
|
||||
)
|
||||
mockState.filters = { categories: [], tags: [], searchQuery: '' }
|
||||
mockState.currentPluginID = undefined
|
||||
mockState.selectedItem = undefined
|
||||
mockUseInstalledPluginList.mockReturnValue({
|
||||
data: { plugins: [] },
|
||||
isLoading: false,
|
||||
@ -544,6 +575,43 @@ describe('PluginsPanel', () => {
|
||||
expect(screen.queryByTestId('builtin-tool-detail')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('replaces the builtin tool detail when an installed plugin is selected', async () => {
|
||||
vi.useRealTimers()
|
||||
const user = userEvent.setup()
|
||||
mockPluginListWithLatestVersion.mockReturnValue([
|
||||
createPlugin('tool-plugin', 'Tool Plugin', [], PluginCategoryEnum.tool),
|
||||
])
|
||||
mockUseInstalledPluginList.mockReturnValue({
|
||||
data: {
|
||||
plugins: [],
|
||||
builtin_tools: [createBuiltinTool('builtin-tool', 'Builtin Tool')],
|
||||
},
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
isLastPage: true,
|
||||
loadNextPage: mockLoadNextPage,
|
||||
})
|
||||
|
||||
render(<PluginsPanel contentInset="compact" fixedCategory={PluginCategoryEnum.tool} />)
|
||||
|
||||
const builtinToolCard = screen.getByRole('button', { name: 'builtin-tool' })
|
||||
const pluginCard = screen.getByRole('button', { name: 'tool-plugin' })
|
||||
|
||||
await user.click(builtinToolCard)
|
||||
|
||||
expect(builtinToolCard).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(pluginCard).toHaveAttribute('aria-pressed', 'false')
|
||||
expect(screen.getByTestId('builtin-tool-detail')).toHaveTextContent('builtin-tool')
|
||||
expect(screen.queryByTestId('plugin-detail-panel')).not.toBeInTheDocument()
|
||||
|
||||
await user.click(pluginCard)
|
||||
|
||||
expect(pluginCard).toHaveAttribute('aria-pressed', 'true')
|
||||
expect(builtinToolCard).toHaveAttribute('aria-pressed', 'false')
|
||||
expect(screen.getByTestId('plugin-detail-panel')).toHaveTextContent('tool-plugin')
|
||||
expect(screen.queryByTestId('builtin-tool-detail')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('filters builtin tools with the tool integrations search query', () => {
|
||||
mockState.filters.searchQuery = 'alpha'
|
||||
mockUseInstalledPluginList.mockReturnValue({
|
||||
@ -898,7 +966,7 @@ describe('PluginsPanel', () => {
|
||||
})
|
||||
|
||||
it('renders the empty state and keeps the current plugin detail in sync', () => {
|
||||
mockState.currentPluginID = 'beta-tool'
|
||||
mockState.selectedItem = { type: 'plugin', id: 'beta-tool' }
|
||||
mockState.filters.searchQuery = 'missing'
|
||||
mockPluginListWithLatestVersion.mockReturnValue([createPlugin('beta-tool', 'Beta Tool')])
|
||||
|
||||
@ -907,10 +975,10 @@ describe('PluginsPanel', () => {
|
||||
expect(screen.getByTestId('empty-state')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('plugin-detail-panel')).toHaveTextContent('beta-tool')
|
||||
|
||||
fireEvent.click(screen.getByText('hide detail'))
|
||||
fireEvent.click(screen.getByText('refresh detail'))
|
||||
fireEvent.click(screen.getByText('hide detail'))
|
||||
|
||||
expect(mockSetCurrentPluginID).toHaveBeenCalledWith(undefined)
|
||||
expect(mockSetSelectedItem).toHaveBeenCalledWith(undefined)
|
||||
expect(mockInvalidateInstalledPluginList).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import type { PluginPageTab } from './context'
|
||||
import type { PluginPageSelection, PluginPageTab } from './context'
|
||||
import type { FilterState } from './filter-management'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { parseAsStringEnum, useQueryState } from 'nuqs'
|
||||
@ -38,7 +38,7 @@ export const PluginPageContextProvider = ({
|
||||
searchQuery: '',
|
||||
},
|
||||
)
|
||||
const [currentPluginID, setCurrentPluginID] = useState<string | undefined>()
|
||||
const [selectedItem, setSelectedItem] = useState<PluginPageSelection | undefined>()
|
||||
|
||||
const { data: enable_marketplace } = useSuspenseQuery({
|
||||
...systemFeaturesQueryOptions(),
|
||||
@ -56,8 +56,8 @@ export const PluginPageContextProvider = ({
|
||||
<PluginPageContext.Provider
|
||||
value={{
|
||||
containerRef,
|
||||
currentPluginID,
|
||||
setCurrentPluginID,
|
||||
selectedItem,
|
||||
setSelectedItem,
|
||||
filters,
|
||||
setFilters,
|
||||
activeTab,
|
||||
|
||||
@ -11,10 +11,14 @@ export type PluginPageTab =
|
||||
| (typeof PLUGIN_PAGE_TABS_MAP)[keyof typeof PLUGIN_PAGE_TABS_MAP]
|
||||
| (typeof PLUGIN_TYPE_SEARCH_MAP)[keyof typeof PLUGIN_TYPE_SEARCH_MAP]
|
||||
|
||||
export type PluginPageSelection =
|
||||
| { type: 'builtinTool'; id: string }
|
||||
| { type: 'plugin'; id: string }
|
||||
|
||||
type PluginPageContextValue = {
|
||||
containerRef: RefObject<HTMLDivElement | null>
|
||||
currentPluginID: string | undefined
|
||||
setCurrentPluginID: (pluginID?: string) => void
|
||||
selectedItem: PluginPageSelection | undefined
|
||||
setSelectedItem: (item?: PluginPageSelection) => void
|
||||
filters: FilterState
|
||||
setFilters: (filter: FilterState) => void
|
||||
activeTab: PluginPageTab
|
||||
@ -26,8 +30,8 @@ const emptyContainerRef: RefObject<HTMLDivElement | null> = { current: null }
|
||||
|
||||
export const PluginPageContext = createContext<PluginPageContextValue>({
|
||||
containerRef: emptyContainerRef,
|
||||
currentPluginID: undefined,
|
||||
setCurrentPluginID: noop,
|
||||
selectedItem: undefined,
|
||||
setSelectedItem: noop,
|
||||
filters: {
|
||||
categories: [],
|
||||
tags: [],
|
||||
|
||||
@ -43,8 +43,8 @@ type PluginsPanelResultsProps = {
|
||||
isLastPage: boolean
|
||||
keywords: string
|
||||
loadNextPage: () => void
|
||||
onSelectBuiltinTool: (id: string) => void
|
||||
scrollAreaLabel?: string
|
||||
setCurrentBuiltinToolID: (id: string) => void
|
||||
showCategoryEmptyState: boolean
|
||||
tagFilterValue: string[]
|
||||
}
|
||||
@ -71,8 +71,8 @@ const PluginsPanelResults = ({
|
||||
isLastPage,
|
||||
keywords,
|
||||
loadNextPage,
|
||||
onSelectBuiltinTool,
|
||||
scrollAreaLabel,
|
||||
setCurrentBuiltinToolID,
|
||||
showCategoryEmptyState,
|
||||
tagFilterValue,
|
||||
}: PluginsPanelResultsProps) => {
|
||||
@ -152,7 +152,7 @@ const PluginsPanelResults = ({
|
||||
data-step-by-step-tour-target={
|
||||
filteredList.length === 0 && index === 0 ? firstBuiltinToolTarget : undefined
|
||||
}
|
||||
onClick={() => setCurrentBuiltinToolID(collection.id)}
|
||||
onClick={() => onSelectBuiltinTool(collection.id)}
|
||||
>
|
||||
<IntegrationsToolProviderCard
|
||||
collection={collection}
|
||||
|
||||
@ -6,7 +6,7 @@ import type { FilterState } from './filter-management'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useDebounceFn } from 'ahooks'
|
||||
import { useMemo, useRef, useState } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { isSearchResultEmpty } from '@/app/components/base/search-input/search-state'
|
||||
import PluginDetailPanel from '@/app/components/plugins/plugin-detail-panel'
|
||||
@ -130,9 +130,8 @@ const PluginsPanel = ({
|
||||
INTEGRATION_PLUGIN_PAGE_SIZE,
|
||||
installedPluginFilters,
|
||||
)
|
||||
const currentPluginID = usePluginPageContext((v) => v.currentPluginID)
|
||||
const setCurrentPluginID = usePluginPageContext((v) => v.setCurrentPluginID)
|
||||
const [currentBuiltinToolID, setCurrentBuiltinToolID] = useState<string | undefined>()
|
||||
const selectedItem = usePluginPageContext((v) => v.selectedItem)
|
||||
const setSelectedItem = usePluginPageContext((v) => v.setSelectedItem)
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const { run: handleFilterChange } = useDebounceFn(
|
||||
@ -186,18 +185,17 @@ const PluginsPanel = ({
|
||||
sourceCount: categoryList.length + builtinTools.length,
|
||||
})
|
||||
|
||||
const currentPluginDetail = useMemo(() => {
|
||||
const detail = pluginListWithLatestVersion.find(
|
||||
(plugin) => plugin.plugin_id === currentPluginID,
|
||||
)
|
||||
return detail
|
||||
}, [currentPluginID, pluginListWithLatestVersion])
|
||||
const currentPluginID = selectedItem?.type === 'plugin' ? selectedItem.id : undefined
|
||||
const currentBuiltinToolID = selectedItem?.type === 'builtinTool' ? selectedItem.id : undefined
|
||||
const currentPluginDetail = useMemo(
|
||||
() => pluginListWithLatestVersion.find((plugin) => plugin.plugin_id === currentPluginID),
|
||||
[currentPluginID, pluginListWithLatestVersion],
|
||||
)
|
||||
const currentBuiltinTool = useMemo(() => {
|
||||
return filteredBuiltinTools.find((collection) => collection.id === currentBuiltinToolID)
|
||||
}, [currentBuiltinToolID, filteredBuiltinTools])
|
||||
|
||||
const handleHide = () => setCurrentPluginID(undefined)
|
||||
const handleBuiltinToolHide = () => setCurrentBuiltinToolID(undefined)
|
||||
const handleDetailHide = () => setSelectedItem(undefined)
|
||||
const hasToolMarketplacePanel = enableMarketplace && isToolIntegrationPage
|
||||
const categoryMarketplace =
|
||||
enableMarketplace && hasEmbeddedMarketplace ? fixedCategory : undefined
|
||||
@ -284,7 +282,7 @@ const PluginsPanel = ({
|
||||
keywords={filters.searchQuery}
|
||||
loadNextPage={loadNextPage}
|
||||
scrollAreaLabel={scrollAreaLabel}
|
||||
setCurrentBuiltinToolID={setCurrentBuiltinToolID}
|
||||
onSelectBuiltinTool={(id) => setSelectedItem({ type: 'builtinTool', id })}
|
||||
tagFilterValue={filters.tags}
|
||||
canDeletePlugin={canDeletePlugin}
|
||||
canUpdatePlugin={canUpdatePlugin}
|
||||
@ -327,14 +325,14 @@ const PluginsPanel = ({
|
||||
onUpdate={() => {
|
||||
invalidateInstalledPluginList(fixedCategory)
|
||||
}}
|
||||
onHide={handleHide}
|
||||
onHide={handleDetailHide}
|
||||
canDeletePlugin={canDeletePlugin}
|
||||
canUpdatePlugin={canUpdatePlugin}
|
||||
/>
|
||||
{currentBuiltinTool && !currentBuiltinTool.plugin_id && (
|
||||
<ProviderDetail
|
||||
collection={currentBuiltinTool}
|
||||
onHide={handleBuiltinToolHide}
|
||||
onHide={handleDetailHide}
|
||||
onRefreshData={invalidateInstalledPluginList}
|
||||
/>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user