mirror of
https://github.com/langgenius/dify.git
synced 2026-05-04 09:28:04 +08:00
fix(web): update tests for AlertDialog migration and component API changes
- Replace deprecated Confirm mock with real AlertDialog role-based queries - Add useInvalidateCheckInstalled mock for QueryClient dependency - Wrap model-list-item renders in QueryClientProvider - Migrate PluginVersionPicker from PortalToFollowElem to Popover - Migrate UpdatePluginModal from Modal to Dialog - Update version picker offset props (sideOffset/alignOffset)
This commit is contained in:
@ -1,9 +1,17 @@
|
||||
import type { ModelItem, ModelProvider } from '../declarations'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { disableModel, enableModel } from '@/service/common'
|
||||
import { ModelStatusEnum } from '../declarations'
|
||||
import ModelListItem from './model-list-item'
|
||||
|
||||
function createWrapper() {
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
let mockModelLoadBalancingEnabled = false
|
||||
|
||||
vi.mock('@/context/app-context', () => ({
|
||||
@ -69,6 +77,7 @@ describe('ModelListItem', () => {
|
||||
provider={mockProvider}
|
||||
isConfigurable={false}
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
expect(screen.getByTestId('model-icon')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('model-name')).toBeInTheDocument()
|
||||
@ -83,6 +92,7 @@ describe('ModelListItem', () => {
|
||||
isConfigurable={false}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
fireEvent.click(screen.getByRole('switch'))
|
||||
|
||||
@ -102,6 +112,7 @@ describe('ModelListItem', () => {
|
||||
isConfigurable={false}
|
||||
onChange={onChange}
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
fireEvent.click(screen.getByRole('switch'))
|
||||
|
||||
@ -122,6 +133,7 @@ describe('ModelListItem', () => {
|
||||
isConfigurable={false}
|
||||
onModifyLoadBalancing={onModifyLoadBalancing}
|
||||
/>,
|
||||
{ wrapper: createWrapper() },
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'modify load balancing' }))
|
||||
|
||||
@ -81,11 +81,10 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
|
||||
pluginID={detail.plugin_id}
|
||||
currentVersion={version}
|
||||
onSelect={handleVersionSelect}
|
||||
offset={{ mainAxis: 4, crossAxis: 0 }}
|
||||
sideOffset={4}
|
||||
alignOffset={0}
|
||||
trigger={(
|
||||
<button
|
||||
type="button"
|
||||
disabled={!isFromMarketplace}
|
||||
<span
|
||||
className={cn(
|
||||
'relative inline-flex min-w-5 items-center justify-center gap-[3px] rounded-md border border-divider-deep bg-state-base-hover px-[5px] py-[2px] text-text-tertiary system-xs-medium-uppercase',
|
||||
isFromMarketplace && 'cursor-pointer hover:bg-state-base-hover-alt',
|
||||
@ -96,7 +95,7 @@ const ProviderCardActions: FC<Props> = ({ detail, onUpdate }) => {
|
||||
{hasNewVersion && (
|
||||
<span className="absolute -right-0.5 -top-0.5 h-1.5 w-1.5 rounded-full bg-state-destructive-solid" />
|
||||
)}
|
||||
</button>
|
||||
</span>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -79,6 +79,10 @@ vi.mock('@/service/plugins', () => ({
|
||||
uninstallPlugin: mockUninstallPlugin,
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-plugins', () => ({
|
||||
useInvalidateCheckInstalled: () => vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/service/use-tools', () => ({
|
||||
useAllToolProviders: () => ({ data: [] }),
|
||||
useInvalidateAllToolProviders: () => mockInvalidateAllToolProviders,
|
||||
@ -218,23 +222,6 @@ vi.mock('../../plugin-auth', () => ({
|
||||
PluginAuth: () => <div data-testid="plugin-auth" />,
|
||||
}))
|
||||
|
||||
// Mock Confirm component
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, onCancel, onConfirm, isLoading }: {
|
||||
isShow: boolean
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
isLoading: boolean
|
||||
}) => isShow
|
||||
? (
|
||||
<div data-testid="delete-confirm">
|
||||
<button data-testid="confirm-cancel" onClick={onCancel}>Cancel</button>
|
||||
<button data-testid="confirm-ok" onClick={onConfirm} disabled={isLoading}>Confirm</button>
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
}))
|
||||
|
||||
const createPluginDetail = (overrides: Partial<PluginDetail> = {}): PluginDetail => ({
|
||||
id: 'test-id',
|
||||
created_at: '2024-01-01',
|
||||
@ -801,7 +788,7 @@ describe('DetailHeader', () => {
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -810,13 +797,13 @@ describe('DetailHeader', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-cancel'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('delete-confirm')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -825,10 +812,10 @@ describe('DetailHeader', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockUninstallPlugin).toHaveBeenCalledWith('test-id')
|
||||
@ -840,10 +827,10 @@ describe('DetailHeader', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnUpdate).toHaveBeenCalledWith(true)
|
||||
@ -861,10 +848,10 @@ describe('DetailHeader', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRefreshModelProviders).toHaveBeenCalled()
|
||||
@ -876,10 +863,10 @@ describe('DetailHeader', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockInvalidateAllToolProviders).toHaveBeenCalled()
|
||||
@ -891,10 +878,10 @@ describe('DetailHeader', () => {
|
||||
|
||||
fireEvent.click(screen.getByTestId('remove-btn'))
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(amplitude.trackEvent).toHaveBeenCalledWith('plugin_uninstalled', expect.any(Object))
|
||||
|
||||
@ -9,24 +9,6 @@ vi.mock('@/context/i18n', () => ({
|
||||
useGetLanguage: () => 'en_US',
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/base/confirm', () => ({
|
||||
default: ({ isShow, title, onCancel, onConfirm, isLoading }: {
|
||||
isShow: boolean
|
||||
title: string
|
||||
onCancel: () => void
|
||||
onConfirm: () => void
|
||||
isLoading: boolean
|
||||
}) => isShow
|
||||
? (
|
||||
<div data-testid="delete-confirm">
|
||||
<div data-testid="delete-title">{title}</div>
|
||||
<button data-testid="confirm-cancel" onClick={onCancel}>Cancel</button>
|
||||
<button data-testid="confirm-ok" onClick={onConfirm} disabled={isLoading}>Confirm</button>
|
||||
</div>
|
||||
)
|
||||
: null,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/plugins/plugin-page/plugin-info', () => ({
|
||||
default: ({ repository, release, packageName, onHide }: {
|
||||
repository: string
|
||||
@ -230,7 +212,7 @@ describe('HeaderModals', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId('delete-confirm')).not.toBeInTheDocument()
|
||||
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render delete confirm when isShowDeleteConfirm is true', () => {
|
||||
@ -247,7 +229,7 @@ describe('HeaderModals', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show correct delete title', () => {
|
||||
@ -264,7 +246,7 @@ describe('HeaderModals', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('delete-title')).toHaveTextContent('plugin.action.delete')
|
||||
expect(screen.getByRole('alertdialog')).toHaveTextContent('plugin.action.delete')
|
||||
})
|
||||
|
||||
it('should call hideDeleteConfirm when cancel is clicked', () => {
|
||||
@ -281,7 +263,7 @@ describe('HeaderModals', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-cancel'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.cancel' }))
|
||||
|
||||
expect(modalStates.hideDeleteConfirm).toHaveBeenCalled()
|
||||
})
|
||||
@ -300,7 +282,7 @@ describe('HeaderModals', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByTestId('confirm-ok'))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'common.operation.confirm' }))
|
||||
|
||||
expect(mockOnDelete).toHaveBeenCalled()
|
||||
})
|
||||
@ -319,7 +301,7 @@ describe('HeaderModals', () => {
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('confirm-ok')).toBeDisabled()
|
||||
expect(screen.getByRole('button', { name: /common\.operation\.confirm/ })).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
@ -485,7 +467,7 @@ describe('HeaderModals', () => {
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('plugin-info')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('delete-confirm')).toBeInTheDocument()
|
||||
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('update-modal')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -104,36 +104,6 @@ vi.mock('../../install-plugin/install-from-github', () => ({
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Portal components for PluginVersionPicker
|
||||
let mockPortalOpen = false
|
||||
vi.mock('@/app/components/base/portal-to-follow-elem', () => ({
|
||||
PortalToFollowElem: ({ children, open, onOpenChange: _onOpenChange }: {
|
||||
children: React.ReactNode
|
||||
open: boolean
|
||||
onOpenChange: (open: boolean) => void
|
||||
}) => {
|
||||
mockPortalOpen = open
|
||||
return <div data-testid="portal-elem" data-open={open}>{children}</div>
|
||||
},
|
||||
PortalToFollowElemTrigger: ({ children, onClick, className }: {
|
||||
children: React.ReactNode
|
||||
onClick: () => void
|
||||
className?: string
|
||||
}) => (
|
||||
<div data-testid="portal-trigger" onClick={onClick} className={className}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
PortalToFollowElemContent: ({ children, className }: {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
}) => {
|
||||
if (!mockPortalOpen)
|
||||
return null
|
||||
return <div data-testid="portal-content" className={className}>{children}</div>
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock semver
|
||||
vi.mock('semver', () => ({
|
||||
lt: (v1: string, v2: string) => {
|
||||
@ -247,7 +217,6 @@ const renderWithQueryClient = (ui: React.ReactElement) => {
|
||||
describe('update-plugin', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockPortalOpen = false
|
||||
mockCheck.mockResolvedValue({ status: TaskStatus.success })
|
||||
})
|
||||
|
||||
@ -964,7 +933,7 @@ describe('update-plugin', () => {
|
||||
render(<PluginVersionPicker {...defaultProps} isShow={false} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.queryByTestId('portal-content')).not.toBeInTheDocument()
|
||||
expect(screen.queryByText('plugin.detailPanel.switchVersion')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render version list when isShow is true', () => {
|
||||
@ -972,7 +941,6 @@ describe('update-plugin', () => {
|
||||
render(<PluginVersionPicker {...defaultProps} isShow={true} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('portal-content')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.detailPanel.switchVersion')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
@ -1002,7 +970,7 @@ describe('update-plugin', () => {
|
||||
|
||||
// Act
|
||||
render(<PluginVersionPicker {...defaultProps} onShowChange={onShowChange} />)
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
fireEvent.click(screen.getByText('Select Version'))
|
||||
|
||||
// Assert
|
||||
expect(onShowChange).toHaveBeenCalledWith(true)
|
||||
@ -1014,7 +982,7 @@ describe('update-plugin', () => {
|
||||
|
||||
// Act
|
||||
render(<PluginVersionPicker {...defaultProps} disabled={true} onShowChange={onShowChange} />)
|
||||
fireEvent.click(screen.getByTestId('portal-trigger'))
|
||||
fireEvent.click(screen.getByText('Select Version'))
|
||||
|
||||
// Assert
|
||||
expect(onShowChange).not.toHaveBeenCalled()
|
||||
@ -1116,7 +1084,7 @@ describe('update-plugin', () => {
|
||||
)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.detailPanel.switchVersion')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should support custom offset', () => {
|
||||
@ -1125,12 +1093,13 @@ describe('update-plugin', () => {
|
||||
<PluginVersionPicker
|
||||
{...defaultProps}
|
||||
isShow={true}
|
||||
offset={{ mainAxis: 10, crossAxis: 20 }}
|
||||
sideOffset={10}
|
||||
alignOffset={20}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
|
||||
expect(screen.getByText('plugin.detailPanel.switchVersion')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -6,7 +6,12 @@ import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Badge, { BadgeState } from '@/app/components/base/badge/index'
|
||||
import Button from '@/app/components/base/button'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import {
|
||||
Dialog,
|
||||
DialogCloseButton,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
} from '@/app/components/base/ui/dialog'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
import checkTaskStatus from '@/app/components/plugins/install-plugin/base/check-task-status'
|
||||
import { pluginManifestToCardPluginProps } from '@/app/components/plugins/install-plugin/utils'
|
||||
@ -125,63 +130,65 @@ const UpdatePluginModal: FC<Props> = ({
|
||||
const doShowDowngradeWarningModal = isShowDowngradeWarningModal && uploadStep === UploadStep.notStarted
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={onCancel}
|
||||
className={cn('min-w-[560px]', doShowDowngradeWarningModal && 'min-w-[640px]')}
|
||||
closable
|
||||
title={!doShowDowngradeWarningModal && t(`${i18nPrefix}.${uploadStep === UploadStep.installed ? 'successfulTitle' : 'title'}`, { ns: 'plugin' })}
|
||||
>
|
||||
{doShowDowngradeWarningModal && (
|
||||
<DowngradeWarningModal
|
||||
onCancel={onCancel}
|
||||
onJustDowngrade={handleConfirm}
|
||||
onExcludeAndDowngrade={handleExcludeAndDownload}
|
||||
/>
|
||||
)}
|
||||
{!doShowDowngradeWarningModal && (
|
||||
<>
|
||||
<div className="system-md-regular mb-2 mt-3 text-text-secondary">
|
||||
{t(`${i18nPrefix}.description`, { ns: 'plugin' })}
|
||||
</div>
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
installed={uploadStep === UploadStep.installed}
|
||||
payload={pluginManifestToCardPluginProps({
|
||||
...originalPackageInfo.payload,
|
||||
icon: icon!,
|
||||
})}
|
||||
className="w-full"
|
||||
titleLeft={(
|
||||
<>
|
||||
<Badge className="mx-1" size="s" state={BadgeState.Warning}>
|
||||
{`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`}
|
||||
</Badge>
|
||||
</>
|
||||
<Dialog open onOpenChange={() => onCancel()}>
|
||||
<DialogContent
|
||||
backdropProps={{ forceRender: true }}
|
||||
className={cn('min-w-[560px]', doShowDowngradeWarningModal && 'min-w-[640px]')}
|
||||
>
|
||||
<DialogCloseButton />
|
||||
{doShowDowngradeWarningModal && (
|
||||
<DowngradeWarningModal
|
||||
onCancel={onCancel}
|
||||
onJustDowngrade={handleConfirm}
|
||||
onExcludeAndDowngrade={handleExcludeAndDownload}
|
||||
/>
|
||||
)}
|
||||
{!doShowDowngradeWarningModal && (
|
||||
<>
|
||||
<DialogTitle className="text-text-primary title-2xl-semi-bold">
|
||||
{t(`${i18nPrefix}.${uploadStep === UploadStep.installed ? 'successfulTitle' : 'title'}`, { ns: 'plugin' })}
|
||||
</DialogTitle>
|
||||
<div className="mb-2 mt-3 text-text-secondary system-md-regular">
|
||||
{t(`${i18nPrefix}.description`, { ns: 'plugin' })}
|
||||
</div>
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
installed={uploadStep === UploadStep.installed}
|
||||
payload={pluginManifestToCardPluginProps({
|
||||
...originalPackageInfo.payload,
|
||||
icon: icon!,
|
||||
})}
|
||||
className="w-full"
|
||||
titleLeft={(
|
||||
<>
|
||||
<Badge className="mx-1" size="s" state={BadgeState.Warning}>
|
||||
{`${originalPackageInfo.payload.version} -> ${targetPackageInfo.version}`}
|
||||
</Badge>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||
{uploadStep === UploadStep.notStarted && (
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-end gap-2 self-stretch pt-5">
|
||||
{uploadStep === UploadStep.notStarted && (
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
variant="primary"
|
||||
loading={uploadStep === UploadStep.upgrading}
|
||||
onClick={handleConfirm}
|
||||
disabled={uploadStep === UploadStep.upgrading}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
{configBtnText}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={uploadStep === UploadStep.upgrading}
|
||||
onClick={handleConfirm}
|
||||
disabled={uploadStep === UploadStep.upgrading}
|
||||
>
|
||||
{configBtnText}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
</Modal>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(UpdatePluginModal)
|
||||
|
||||
@ -1,19 +1,16 @@
|
||||
'use client'
|
||||
import type {
|
||||
OffsetOptions,
|
||||
Placement,
|
||||
} from '@floating-ui/react'
|
||||
import type { FC } from 'react'
|
||||
import type { Placement } from '@/app/components/base/ui/placement'
|
||||
import * as React from 'react'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { lt } from 'semver'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import {
|
||||
PortalToFollowElem,
|
||||
PortalToFollowElemContent,
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/app/components/base/ui/popover'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { useVersionListOfPlugin } from '@/service/use-plugins'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -26,7 +23,8 @@ type Props = {
|
||||
currentVersion: string
|
||||
trigger: React.ReactNode
|
||||
placement?: Placement
|
||||
offset?: OffsetOptions
|
||||
sideOffset?: number
|
||||
alignOffset?: number
|
||||
onSelect: ({
|
||||
version,
|
||||
unique_identifier,
|
||||
@ -46,22 +44,14 @@ const PluginVersionPicker: FC<Props> = ({
|
||||
currentVersion,
|
||||
trigger,
|
||||
placement = 'bottom-start',
|
||||
offset = {
|
||||
mainAxis: 4,
|
||||
crossAxis: -16,
|
||||
},
|
||||
sideOffset = 4,
|
||||
alignOffset = -16,
|
||||
onSelect,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const format = t('dateTimeFormat', { ns: 'appLog' }).split(' ')[0]
|
||||
const { formatDate } = useTimestamp()
|
||||
|
||||
const handleTriggerClick = () => {
|
||||
if (disabled)
|
||||
return
|
||||
onShowChange(!isShow)
|
||||
}
|
||||
|
||||
const { data: res } = useVersionListOfPlugin(pluginID)
|
||||
|
||||
const handleSelect = useCallback(({ version, unique_identifier, isDowngrade }: {
|
||||
@ -76,49 +66,52 @@ const PluginVersionPicker: FC<Props> = ({
|
||||
}, [currentVersion, onSelect, onShowChange])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
placement={placement}
|
||||
offset={offset}
|
||||
<Popover
|
||||
open={isShow}
|
||||
onOpenChange={onShowChange}
|
||||
onOpenChange={(open) => {
|
||||
if (!disabled)
|
||||
onShowChange(open)
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger
|
||||
<PopoverTrigger
|
||||
className={cn('inline-flex cursor-pointer items-center', disabled && 'cursor-default')}
|
||||
onClick={handleTriggerClick}
|
||||
>
|
||||
{trigger}
|
||||
</PortalToFollowElemTrigger>
|
||||
</PopoverTrigger>
|
||||
|
||||
<PortalToFollowElemContent className="z-[1000]">
|
||||
<div className="relative w-[209px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg backdrop-blur-sm">
|
||||
<div className="system-xs-medium-uppercase px-3 pb-0.5 pt-1 text-text-tertiary">
|
||||
{t('detailPanel.switchVersion', { ns: 'plugin' })}
|
||||
</div>
|
||||
<div className="relative max-h-[224px] overflow-y-auto">
|
||||
{res?.data.versions.map(version => (
|
||||
<div
|
||||
key={version.unique_identifier}
|
||||
className={cn(
|
||||
'flex h-7 cursor-pointer items-center gap-1 rounded-lg px-3 py-1 hover:bg-state-base-hover',
|
||||
currentVersion === version.version && 'cursor-default opacity-30 hover:bg-transparent',
|
||||
)}
|
||||
onClick={() => handleSelect({
|
||||
version: version.version,
|
||||
unique_identifier: version.unique_identifier,
|
||||
isDowngrade: lt(version.version, currentVersion),
|
||||
})}
|
||||
>
|
||||
<div className="flex grow items-center">
|
||||
<div className="system-sm-medium text-text-secondary">{version.version}</div>
|
||||
{currentVersion === version.version && <Badge className="ml-1" text="CURRENT" />}
|
||||
</div>
|
||||
<div className="system-xs-regular shrink-0 text-text-tertiary">{formatDate(version.created_at, format)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<PopoverContent
|
||||
placement={placement}
|
||||
sideOffset={sideOffset}
|
||||
alignOffset={alignOffset}
|
||||
popupClassName="relative w-[209px] bg-components-panel-bg-blur p-1 backdrop-blur-sm"
|
||||
>
|
||||
<div className="px-3 pb-0.5 pt-1 text-text-tertiary system-xs-medium-uppercase">
|
||||
{t('detailPanel.switchVersion', { ns: 'plugin' })}
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
<div className="relative max-h-[224px] overflow-y-auto">
|
||||
{res?.data.versions.map(version => (
|
||||
<div
|
||||
key={version.unique_identifier}
|
||||
className={cn(
|
||||
'flex h-7 cursor-pointer items-center gap-1 rounded-lg px-3 py-1 hover:bg-state-base-hover',
|
||||
currentVersion === version.version && 'cursor-default opacity-30 hover:bg-transparent',
|
||||
)}
|
||||
onClick={() => handleSelect({
|
||||
version: version.version,
|
||||
unique_identifier: version.unique_identifier,
|
||||
isDowngrade: lt(version.version, currentVersion),
|
||||
})}
|
||||
>
|
||||
<div className="flex grow items-center">
|
||||
<div className="text-text-secondary system-sm-medium">{version.version}</div>
|
||||
{currentVersion === version.version && <Badge className="ml-1" text="CURRENT" />}
|
||||
</div>
|
||||
<div className="shrink-0 text-text-tertiary system-xs-regular">{formatDate(version.created_at, format)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user