feat: plugin tasks

This commit is contained in:
StyleZhang 2024-11-01 16:08:05 +08:00
parent 40e171c2c6
commit 8874837dc3
6 changed files with 138 additions and 57 deletions

View File

@ -10,6 +10,7 @@ import { RiLoader2Line } from '@remixicon/react'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { installPackageFromLocal } from '@/service/plugins'
import checkTaskStatus from '../../base/check-task-status'
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
const i18nPrefix = 'plugin.installModal'
@ -42,6 +43,7 @@ const Installed: FC<Props> = ({
onCancel()
}
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
const handleInstall = async () => {
if (isInstalling) return
setIsInstalling(true)
@ -56,6 +58,7 @@ const Installed: FC<Props> = ({
onInstalled()
return
}
setPluginTasksWithPolling()
await check({
taskId,
pluginUniqueIdentifier: uniqueIdentifier,

View File

@ -1,37 +0,0 @@
import {
useCallback,
useEffect,
useState,
} from 'react'
import { useRequest } from 'ahooks'
import type { PluginTask } from '../types'
import { fetchPluginTasks } from '@/service/plugins'
export const usePluginTasks = () => {
const [pluginTasks, setPluginTasks] = useState<PluginTask[]>([])
const handleUpdatePluginTasks = async (callback: (tasks: PluginTask[]) => void) => {
const { tasks } = await fetchPluginTasks()
setPluginTasks(tasks)
callback(tasks)
}
const { run, cancel } = useRequest(handleUpdatePluginTasks, {
manual: true,
pollingInterval: 3000,
pollingErrorRetryCount: 2,
})
const checkHasPluginTasks = useCallback((tasks: PluginTask[]) => {
if (!tasks.length)
cancel()
}, [cancel])
useEffect(() => {
run(checkHasPluginTasks)
}, [run, checkHasPluginTasks])
return {
pluginTasks,
}
}

View File

@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next'
import {
RiDragDropLine,
RiEqualizer2Line,
RiInstallFill,
} from '@remixicon/react'
import { useBoolean } from 'ahooks'
import InstallFromLocalPackage from '../install-plugin/install-from-local-package'
@ -17,7 +16,8 @@ import InstallPluginDropdown from './install-plugin-dropdown'
import { useUploader } from './use-uploader'
import usePermission from './use-permission'
import DebugInfo from './debug-info'
import { usePluginTasks } from './hooks'
import { usePluginTasksStore } from './store'
import InstallInfo from './install-info'
import { useTabSearchParams } from '@/hooks/use-tab-searchparams'
import Button from '@/app/components/base/button'
import TabSlider from '@/app/components/base/tab-slider'
@ -125,7 +125,11 @@ const PluginPage = ({
const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps
const { pluginTasks } = usePluginTasks()
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
useEffect(() => {
setPluginTasksWithPolling()
}, [setPluginTasksWithPolling])
return (
<div
@ -149,22 +153,7 @@ const PluginPage = ({
/>
</div>
<div className='flex flex-shrink-0 items-center gap-1'>
<div className='relative'>
<Button
className='relative overflow-hidden border !border-[rgba(178,202,255,1)] !bg-[rgba(255,255,255,0.95)] cursor-default'
>
<div
className='absolute left-0 top-0 h-full bg-state-accent-active'
style={{ width: `${progressPercentage}%` }}
></div>
<div className='relative z-10 flex items-center'>
<RiInstallFill className='w-4 h-4 text-text-accent' />
<div className='flex px-0.5 justify-center items-center gap-1'>
<span className='text-text-accent system-sm-medium'>{activeTab === 'plugins' ? `Installing ${installed}/${total} plugins` : `${installed}/${total}`}</span>
</div>
</div>
</Button>
</div>
<InstallInfo />
{canManagement && (
<InstallPluginDropdown
onSwitchToMarketplaceTab={() => setActiveTab('discover')}

View File

@ -0,0 +1,86 @@
import {
useState,
} from 'react'
import {
RiCheckboxCircleFill,
RiErrorWarningFill,
RiInstallLine,
} from '@remixicon/react'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import Tooltip from '@/app/components/base/tooltip'
import Button from '@/app/components/base/button'
// import ProgressCircle from '@/app/components/base/progress-bar/progress-circle'
import { useMemo } from 'react'
import cn from '@/utils/classnames'
const InstallInfo = () => {
const [open, setOpen] = useState(false)
const status = 'error'
const statusError = useMemo(() => status === 'error', [status])
return (
<div className='flex items-center'>
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={{
mainAxis: 4,
crossAxis: 79,
}}
>
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
<Tooltip popupContent='Installing 1/3 plugins...'>
<div
className={cn(
'relative flex items-center justify-center w-8 h-8 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg shadow-xs hover:bg-components-button-secondary-bg-hover',
statusError && 'border-components-button-destructive-secondary-border-hover bg-state-destructive-hover hover:bg-state-destructive-hover-alt',
)}
>
<RiInstallLine
className={cn(
'w-4 h-4 text-components-button-secondary-text',
statusError && 'text-components-button-destructive-secondary-text',
)}
/>
<div className='absolute -right-1 -top-1'>
{/* <ProgressCircle
percentage={33}
circleFillColor='fill-components-progress-brand-bg'
sectorFillColor='fill-components-progress-error-bg'
circleStrokeColor='stroke-components-progress-error-bg'
/> */}
<RiCheckboxCircleFill className='w-3.5 h-3.5 text-text-success' />
</div>
</div>
</Tooltip>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
<div className='p-1 pb-2 w-[320px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg'>
<div className='flex items-center px-2 pt-1 h-7 system-sm-semibold-uppercase'>3 plugins failed to install</div>
<div className='flex items-center p-1 pl-2 h-8 rounded-lg hover:bg-state-base-hover'>
<div className='relative flex items-center justify-center mr-2 w-6 h-6 rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge'>
<RiErrorWarningFill className='absolute -right-0.5 -bottom-0.5 w-3 h-3 text-text-destructive' />
</div>
<div className='grow system-md-regular text-text-secondary truncate'>
DuckDuckGo Search
</div>
<Button
size='small'
variant='ghost-accent'
>
Clear
</Button>
</div>
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
</div>
)
}
export default InstallInfo

View File

@ -119,7 +119,7 @@ const InstallPluginDropdown = ({
&& (<InstallFromLocalPackage
file={selectedFile}
onClose={() => setSelectedAction(null)}
onSuccess={() => { }}
onSuccess={() => {}}
/>
)
}

View File

@ -0,0 +1,40 @@
import { create } from 'zustand'
import type { PluginTask } from '../types'
import { fetchPluginTasks } from '@/service/plugins'
type PluginTasksStore = {
pluginTasks: PluginTask[]
setPluginTasks: (tasks: PluginTask[]) => void
setPluginTasksWithPolling: () => void
}
let pluginTasksTimer: NodeJS.Timeout | null = null
export const usePluginTasksStore = create<PluginTasksStore>(set => ({
pluginTasks: [],
setPluginTasks: (tasks: PluginTask[]) => set({ pluginTasks: tasks }),
setPluginTasksWithPolling: async () => {
if (pluginTasksTimer) {
clearTimeout(pluginTasksTimer)
pluginTasksTimer = null
}
const handleUpdatePluginTasks = async () => {
const { tasks } = await fetchPluginTasks()
set({ pluginTasks: tasks })
if (tasks.length && !tasks.every(task => task.status === 'success')) {
pluginTasksTimer = setTimeout(() => {
handleUpdatePluginTasks()
}, 5000)
}
else {
if (pluginTasksTimer) {
clearTimeout(pluginTasksTimer)
pluginTasksTimer = null
}
}
}
handleUpdatePluginTasks()
},
}))