switch version
This commit is contained in:
parent
f0e4885926
commit
66f0e1209a
@ -3,7 +3,7 @@ import cn from '@/utils/classnames'
|
|||||||
|
|
||||||
type BadgeProps = {
|
type BadgeProps = {
|
||||||
className?: string
|
className?: string
|
||||||
text: string
|
text: string | React.ReactNode
|
||||||
uppercase?: boolean
|
uppercase?: boolean
|
||||||
hasRedCornerMark?: boolean
|
hasRedCornerMark?: boolean
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useBoolean } from 'ahooks'
|
import { useBoolean } from 'ahooks'
|
||||||
import {
|
import {
|
||||||
|
RiArrowLeftRightLine,
|
||||||
RiBugLine,
|
RiBugLine,
|
||||||
RiCloseLine,
|
RiCloseLine,
|
||||||
RiHardDrive3Line,
|
RiHardDrive3Line,
|
||||||
@ -15,7 +16,9 @@ import Title from '../card/base/title'
|
|||||||
import OrgInfo from '../card/base/org-info'
|
import OrgInfo from '../card/base/org-info'
|
||||||
import { useGitHubReleases } from '../install-plugin/hooks'
|
import { useGitHubReleases } from '../install-plugin/hooks'
|
||||||
import { compareVersion, getLatestVersion } from '@/utils/semver'
|
import { compareVersion, getLatestVersion } from '@/utils/semver'
|
||||||
import OperationDropdown from './operation-dropdown'
|
import PluginVersionPicker from '@/app/components/plugins/update-plugin/plugin-version-picker'
|
||||||
|
import UpdateFromMarketplace from '@/app/components/plugins/update-plugin/from-market-place'
|
||||||
|
import OperationDropdown from '@/app/components/plugins/plugin-detail-panel/operation-dropdown'
|
||||||
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
|
import PluginInfo from '@/app/components/plugins/plugin-page/plugin-info'
|
||||||
import ActionButton from '@/app/components/base/action-button'
|
import ActionButton from '@/app/components/base/action-button'
|
||||||
import Button from '@/app/components/base/button'
|
import Button from '@/app/components/base/button'
|
||||||
@ -28,7 +31,6 @@ import { Github } from '@/app/components/base/icons/src/public/common'
|
|||||||
import { uninstallPlugin } from '@/service/plugins'
|
import { uninstallPlugin } from '@/service/plugins'
|
||||||
import { useGetLanguage } from '@/context/i18n'
|
import { useGetLanguage } from '@/context/i18n'
|
||||||
import { useModalContext } from '@/context/modal-context'
|
import { useModalContext } from '@/context/modal-context'
|
||||||
import UpdateFromMarketplace from '@/app/components/plugins/update-plugin/from-market-place'
|
|
||||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
@ -58,11 +60,17 @@ const DetailHeader = ({
|
|||||||
latest_unique_identifier,
|
latest_unique_identifier,
|
||||||
latest_version,
|
latest_version,
|
||||||
meta,
|
meta,
|
||||||
|
plugin_id,
|
||||||
} = detail
|
} = detail
|
||||||
const { author, name, label, description, icon, verified } = detail.declaration
|
const { author, name, label, description, icon, verified } = detail.declaration
|
||||||
const isFromGitHub = source === PluginSource.github
|
const isFromGitHub = source === PluginSource.github
|
||||||
const isFromMarketplace = source === PluginSource.marketplace
|
const isFromMarketplace = source === PluginSource.marketplace
|
||||||
|
|
||||||
|
const [isShow, setIsShow] = useState(false)
|
||||||
|
const [targetVersion, setTargetVersion] = useState({
|
||||||
|
version: latest_version,
|
||||||
|
unique_identifier: latest_unique_identifier,
|
||||||
|
})
|
||||||
const hasNewVersion = useMemo(() => {
|
const hasNewVersion = useMemo(() => {
|
||||||
if (isFromGitHub)
|
if (isFromGitHub)
|
||||||
return latest_version !== version
|
return latest_version !== version
|
||||||
@ -167,10 +175,33 @@ const DetailHeader = ({
|
|||||||
<div className="flex items-center h-5">
|
<div className="flex items-center h-5">
|
||||||
<Title title={label[locale]} />
|
<Title title={label[locale]} />
|
||||||
{verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />}
|
{verified && <RiVerifiedBadgeLine className="shrink-0 ml-0.5 w-4 h-4 text-text-accent" />}
|
||||||
<Badge
|
<PluginVersionPicker
|
||||||
className='mx-1'
|
disabled={!isFromMarketplace || !hasNewVersion}
|
||||||
text={version}
|
isShow={isShow}
|
||||||
hasRedCornerMark={hasNewVersion}
|
onShowChange={setIsShow}
|
||||||
|
pluginID={plugin_id}
|
||||||
|
currentVersion={version}
|
||||||
|
onSelect={(state) => {
|
||||||
|
setTargetVersion(state)
|
||||||
|
handleUpdate()
|
||||||
|
}}
|
||||||
|
trigger={
|
||||||
|
<Badge
|
||||||
|
className={cn(
|
||||||
|
'mx-1',
|
||||||
|
isShow && 'bg-state-base-hover',
|
||||||
|
(isShow || isFromMarketplace) && 'hover:bg-state-base-hover',
|
||||||
|
)}
|
||||||
|
uppercase={false}
|
||||||
|
text={
|
||||||
|
<>
|
||||||
|
<div>{version}</div>
|
||||||
|
{isFromMarketplace && <RiArrowLeftRightLine className='ml-1 w-3 h-3 text-text-tertiary' />}
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
hasRedCornerMark={hasNewVersion}
|
||||||
|
/>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
{hasNewVersion && (
|
{hasNewVersion && (
|
||||||
<Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button>
|
<Button variant='secondary-accent' size='small' className='!h-5' onClick={handleUpdate}>{t('plugin.detailPanel.operation.update')}</Button>
|
||||||
@ -254,8 +285,8 @@ const DetailHeader = ({
|
|||||||
payload: detail.declaration,
|
payload: detail.declaration,
|
||||||
},
|
},
|
||||||
targetPackageInfo: {
|
targetPackageInfo: {
|
||||||
id: latest_unique_identifier,
|
id: targetVersion.unique_identifier,
|
||||||
version: latest_version,
|
version: targetVersion.version,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
onCancel={hideUpdateModal}
|
onCancel={hideUpdateModal}
|
||||||
|
@ -65,6 +65,7 @@ const EndpointList = ({ showTopBorder }: Props) => {
|
|||||||
<div className='flex items-center gap-0.5'>
|
<div className='flex items-center gap-0.5'>
|
||||||
{t('plugin.detailPanel.endpoints')}
|
{t('plugin.detailPanel.endpoints')}
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
position='right'
|
||||||
needsDelay
|
needsDelay
|
||||||
popupClassName='w-[240px] p-4 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border'
|
popupClassName='w-[240px] p-4 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border'
|
||||||
popupContent={
|
popupContent={
|
||||||
|
@ -322,3 +322,17 @@ export type Dependency = {
|
|||||||
plugin_unique_identifier?: string
|
plugin_unique_identifier?: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Version = {
|
||||||
|
plugin_org: string
|
||||||
|
plugin_name: string
|
||||||
|
version: string
|
||||||
|
file_name: string
|
||||||
|
checksum: string
|
||||||
|
created_at: string
|
||||||
|
unique_identifier: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VersionListResponse = {
|
||||||
|
versions: Version[]
|
||||||
|
}
|
||||||
|
@ -0,0 +1,118 @@
|
|||||||
|
'use client'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import {
|
||||||
|
PortalToFollowElem,
|
||||||
|
PortalToFollowElemContent,
|
||||||
|
PortalToFollowElemTrigger,
|
||||||
|
} from '@/app/components/base/portal-to-follow-elem'
|
||||||
|
import Badge from '@/app/components/base/badge'
|
||||||
|
import type {
|
||||||
|
OffsetOptions,
|
||||||
|
Placement,
|
||||||
|
} from '@floating-ui/react'
|
||||||
|
import { useVersionListOfPlugin } from '@/service/use-plugins'
|
||||||
|
import useTimestamp from '@/hooks/use-timestamp'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
disabled?: boolean
|
||||||
|
isShow: boolean
|
||||||
|
onShowChange: (isShow: boolean) => void
|
||||||
|
pluginID: string
|
||||||
|
currentVersion: string
|
||||||
|
trigger: React.ReactNode
|
||||||
|
placement?: Placement
|
||||||
|
offset?: OffsetOptions
|
||||||
|
onSelect: ({
|
||||||
|
version,
|
||||||
|
unique_identifier,
|
||||||
|
}: {
|
||||||
|
version: string
|
||||||
|
unique_identifier: string
|
||||||
|
}) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const PluginVersionPicker: FC<Props> = ({
|
||||||
|
disabled = false,
|
||||||
|
isShow,
|
||||||
|
onShowChange,
|
||||||
|
pluginID,
|
||||||
|
currentVersion,
|
||||||
|
trigger,
|
||||||
|
placement = 'bottom-start',
|
||||||
|
offset = {
|
||||||
|
mainAxis: 4,
|
||||||
|
crossAxis: -16,
|
||||||
|
},
|
||||||
|
onSelect,
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const format = t('appLog.dateTimeFormat').split(' ')[0]
|
||||||
|
const { formatDate } = useTimestamp()
|
||||||
|
|
||||||
|
const handleTriggerClick = () => {
|
||||||
|
if (disabled) return
|
||||||
|
onShowChange(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: res } = useVersionListOfPlugin(pluginID)
|
||||||
|
|
||||||
|
const handleSelect = useCallback(({ version, unique_identifier }: {
|
||||||
|
version: string
|
||||||
|
unique_identifier: string
|
||||||
|
}) => {
|
||||||
|
if (currentVersion === version)
|
||||||
|
return
|
||||||
|
onSelect({ version, unique_identifier })
|
||||||
|
onShowChange(false)
|
||||||
|
}, [currentVersion, onSelect])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PortalToFollowElem
|
||||||
|
placement={placement}
|
||||||
|
offset={offset}
|
||||||
|
open={isShow}
|
||||||
|
onOpenChange={onShowChange}
|
||||||
|
>
|
||||||
|
<PortalToFollowElemTrigger
|
||||||
|
className={cn('inline-flex items-center cursor-pointer', disabled && 'cursor-default')}
|
||||||
|
onClick={handleTriggerClick}
|
||||||
|
>
|
||||||
|
{trigger}
|
||||||
|
</PortalToFollowElemTrigger>
|
||||||
|
|
||||||
|
<PortalToFollowElemContent className='z-[1000]'>
|
||||||
|
<div className="relative w-[209px] p-1 rounded-xl bg-components-panel-bg-blur border-[0.5px] border-components-panel-border shadow-lg">
|
||||||
|
<div className='px-3 pt-1 pb-0.5 text-text-tertiary system-xs-medium-uppercase'>
|
||||||
|
{t('plugin.detailPanel.switchVersion')}
|
||||||
|
</div>
|
||||||
|
<div className='relative'>
|
||||||
|
{res?.data.versions.map(version => (
|
||||||
|
<div
|
||||||
|
key={version.unique_identifier}
|
||||||
|
className={cn(
|
||||||
|
'h-7 px-3 py-1 flex items-center gap-1 rounded-lg hover:bg-state-base-hover cursor-pointer',
|
||||||
|
currentVersion === version.version && 'opacity-30 cursor-default hover:bg-transparent',
|
||||||
|
)}
|
||||||
|
onClick={() => handleSelect({
|
||||||
|
version: version.version,
|
||||||
|
unique_identifier: version.unique_identifier,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<div className='grow flex 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>
|
||||||
|
</div>
|
||||||
|
</PortalToFollowElemContent>
|
||||||
|
</PortalToFollowElem>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(PluginVersionPicker)
|
@ -15,7 +15,11 @@ const useTimestamp = () => {
|
|||||||
return dayjs.unix(value).tz(timezone).format(format)
|
return dayjs.unix(value).tz(timezone).format(format)
|
||||||
}, [timezone])
|
}, [timezone])
|
||||||
|
|
||||||
return { formatTime }
|
const formatDate = useCallback((value: string, format: string) => {
|
||||||
|
return dayjs(value).tz(timezone).format(format)
|
||||||
|
}, [timezone])
|
||||||
|
|
||||||
|
return { formatTime, formatDate }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useTimestamp
|
export default useTimestamp
|
||||||
|
@ -33,6 +33,7 @@ const translation = {
|
|||||||
local: 'Local Package File',
|
local: 'Local Package File',
|
||||||
},
|
},
|
||||||
detailPanel: {
|
detailPanel: {
|
||||||
|
switchVersion: 'Switch Version',
|
||||||
categoryTip: {
|
categoryTip: {
|
||||||
marketplace: 'Installed from Marketplace',
|
marketplace: 'Installed from Marketplace',
|
||||||
github: 'Installed from Github',
|
github: 'Installed from Github',
|
||||||
|
@ -33,6 +33,7 @@ const translation = {
|
|||||||
local: '本地插件',
|
local: '本地插件',
|
||||||
},
|
},
|
||||||
detailPanel: {
|
detailPanel: {
|
||||||
|
switchVersion: '切换版本',
|
||||||
categoryTip: {
|
categoryTip: {
|
||||||
marketplace: '从 Marketplace 安装',
|
marketplace: '从 Marketplace 安装',
|
||||||
github: '从 Github 安装',
|
github: '从 Github 安装',
|
||||||
|
@ -7,13 +7,14 @@ import type {
|
|||||||
Permissions,
|
Permissions,
|
||||||
PluginTask,
|
PluginTask,
|
||||||
PluginsFromMarketplaceResponse,
|
PluginsFromMarketplaceResponse,
|
||||||
|
VersionListResponse,
|
||||||
uploadGitHubResponse,
|
uploadGitHubResponse,
|
||||||
} from '@/app/components/plugins/types'
|
} from '@/app/components/plugins/types'
|
||||||
import { TaskStatus } from '@/app/components/plugins/types'
|
import { TaskStatus } from '@/app/components/plugins/types'
|
||||||
import type {
|
import type {
|
||||||
PluginsSearchParams,
|
PluginsSearchParams,
|
||||||
} from '@/app/components/plugins/marketplace/types'
|
} from '@/app/components/plugins/marketplace/types'
|
||||||
import { get, post, postMarketplace } from './base'
|
import { get, getMarketplace, post, postMarketplace } from './base'
|
||||||
import {
|
import {
|
||||||
useMutation,
|
useMutation,
|
||||||
useQuery,
|
useQuery,
|
||||||
@ -52,6 +53,19 @@ export const useInstallPackageFromMarketPlace = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const useVersionListOfPlugin = (pluginID: string) => {
|
||||||
|
return useQuery<{ data: VersionListResponse }>({
|
||||||
|
queryKey: [NAME_SPACE, 'versions', pluginID],
|
||||||
|
queryFn: () => getMarketplace<{ data: VersionListResponse }>(`/plugins/${pluginID}/versions`, { params: { page: 1, page_size: 100 } }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
export const useInvalidateVersionListOfPlugin = () => {
|
||||||
|
const queryClient = useQueryClient()
|
||||||
|
return (pluginID: string) => {
|
||||||
|
queryClient.invalidateQueries({ queryKey: [NAME_SPACE, 'versions', pluginID] })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const useInstallPackageFromLocal = () => {
|
export const useInstallPackageFromLocal = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: (uniqueIdentifier: string) => {
|
mutationFn: (uniqueIdentifier: string) => {
|
||||||
|
Loading…
Reference in New Issue
Block a user