Files
dify/web/app/components/plugins/update-plugin/plugin-version-picker.tsx
yyh 62631658e9 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)
2026-03-04 22:52:21 +08:00

119 lines
3.5 KiB
TypeScript

'use client'
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 {
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'
type Props = {
disabled?: boolean
isShow: boolean
onShowChange: (isShow: boolean) => void
pluginID: string
currentVersion: string
trigger: React.ReactNode
placement?: Placement
sideOffset?: number
alignOffset?: number
onSelect: ({
version,
unique_identifier,
isDowngrade,
}: {
version: string
unique_identifier: string
isDowngrade: boolean
}) => void
}
const PluginVersionPicker: FC<Props> = ({
disabled = false,
isShow,
onShowChange,
pluginID,
currentVersion,
trigger,
placement = 'bottom-start',
sideOffset = 4,
alignOffset = -16,
onSelect,
}) => {
const { t } = useTranslation()
const format = t('dateTimeFormat', { ns: 'appLog' }).split(' ')[0]
const { formatDate } = useTimestamp()
const { data: res } = useVersionListOfPlugin(pluginID)
const handleSelect = useCallback(({ version, unique_identifier, isDowngrade }: {
version: string
unique_identifier: string
isDowngrade: boolean
}) => {
if (currentVersion === version)
return
onSelect({ version, unique_identifier, isDowngrade })
onShowChange(false)
}, [currentVersion, onSelect, onShowChange])
return (
<Popover
open={isShow}
onOpenChange={(open) => {
if (!disabled)
onShowChange(open)
}}
>
<PopoverTrigger
className={cn('inline-flex cursor-pointer items-center', disabled && 'cursor-default')}
>
{trigger}
</PopoverTrigger>
<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>
<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>
)
}
export default React.memo(PluginVersionPicker)