chore: update installFromGitHub component

This commit is contained in:
Yi 2024-11-05 17:52:47 +08:00
parent 8533ded335
commit bde1261b8c
8 changed files with 220 additions and 194 deletions

View File

@ -49,7 +49,7 @@ export const useGitHubUpload = () => {
repoUrl: string,
selectedVersion: string,
selectedPackage: string,
onSuccess?: (GitHubPackage: { manifest: any; uniqueIdentifier: string }) => void,
onSuccess?: (GitHubPackage: { manifest: any; unique_identifier: string }) => void,
) => {
setIsLoading(true)
setError(null)
@ -58,7 +58,7 @@ export const useGitHubUpload = () => {
const response = await uploadGitHub(repoUrl, selectedVersion, selectedPackage)
const GitHubPackage = {
manifest: response.manifest,
uniqueIdentifier: response.plugin_unique_identifier,
unique_identifier: response.unique_identifier,
}
if (onSuccess) onSuccess(GitHubPackage)
return GitHubPackage

View File

@ -4,21 +4,20 @@ import React, { useCallback, useState } from 'react'
import Modal from '@/app/components/base/modal'
import type { Item } from '@/app/components/base/select'
import type { InstallState } from '@/app/components/plugins/types'
import { useGitHubReleases, useGitHubUpload } from '../hooks'
import { useGitHubReleases } from '../hooks'
import { parseGitHubUrl } from '../utils'
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types'
import { InstallStepFromGitHub } from '../../types'
import checkTaskStatus from '../base/check-task-status'
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
import Toast from '@/app/components/base/toast'
import SetURL from './steps/setURL'
import SelectPackage from './steps/selectPackage'
import Installed from './steps/installed'
import Installed from '../base/installed'
import Loaded from './steps/loaded'
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
import { useTranslation } from 'react-i18next'
import { usePluginPageContext } from '../../plugin-page/context'
import { installPackageFromGitHub } from '@/service/plugins'
const i18nPrefix = 'plugin.installModal'
type InstallFromGitHubProps = {
updatePayload?: UpdateFromGitHubPayload
@ -30,17 +29,15 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
const { t } = useTranslation()
const [state, setState] = useState<InstallState>({
step: updatePayload ? InstallStepFromGitHub.selectPackage : InstallStepFromGitHub.setUrl,
repoUrl: updatePayload?.url || '',
selectedVersion: updatePayload?.currVersion || '',
selectedPackage: updatePayload?.currPackage || '',
repoUrl: updatePayload?.originalPackageInfo.repo || '',
selectedVersion: updatePayload?.originalPackageInfo.version || '',
selectedPackage: updatePayload?.originalPackageInfo.package || '',
releases: [],
})
const { getIconUrl } = useGetIcon()
const [uniqueIdentifier, setUniqueIdentifier] = useState<string | null>(null)
const [manifest, setManifest] = useState<PluginDeclaration | null>(null)
const [errorMsg, setErrorMsg] = useState<string | null>(null)
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
const { check } = checkTaskStatus()
const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList)
const versions: Item[] = state.releases.map(release => ({
@ -58,13 +55,39 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
})) || [])
: []
const { isLoading, handleUpload, error } = useGitHubUpload()
const { fetchReleases } = useGitHubReleases()
const handleError = (e: any) => {
const message = e?.response?.message || t('plugin.error.installFailed')
const getTitle = useCallback(() => {
if (state.step === InstallStepFromGitHub.installed)
return t(`${i18nPrefix}.installedSuccessfully`)
if (state.step === InstallStepFromGitHub.installFailed)
return t(`${i18nPrefix}.installFailed`)
return t(`${i18nPrefix}.installPlugin`)
}, [state.step])
const handleUrlSubmit = async () => {
const { isValid, owner, repo } = parseGitHubUrl(state.repoUrl)
if (!isValid || !owner || !repo) {
Toast.notify({
type: 'error',
message: t('plugin.error.inValidGitHubUrl'),
})
return
}
await fetchReleases(owner, repo).then((fetchedReleases) => {
setState(prevState => ({
...prevState,
releases: fetchedReleases,
step: InstallStepFromGitHub.selectPackage,
}))
})
}
const handleError = (e: any, isInstall: boolean) => {
const message = e?.response?.message || t('plugin.installModal.installFailedDesc')
setErrorMsg(message)
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.failed }))
setState(prevState => ({ ...prevState, step: isInstall ? InstallStepFromGitHub.installFailed : InstallStepFromGitHub.uploadFailed }))
}
const handleUploaded = async (GitHubPackage: any) => {
@ -78,72 +101,25 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.readyToInstall }))
}
catch (e) {
handleError(e)
handleError(e, false)
}
}
const handleInstall = async () => {
try {
const { all_installed: isInstalled, task_id: taskId } = await installPackageFromGitHub(uniqueIdentifier!)
if (isInstalled) {
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
return
}
setPluginTasksWithPolling()
await check({
taskId,
pluginUniqueIdentifier: uniqueIdentifier!,
})
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
}
catch (e) {
handleError(e)
}
}
const handleUploadFail = useCallback((errorMsg: string) => {
setErrorMsg(errorMsg)
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.uploadFailed }))
}, [])
const handleInstalled = useCallback(() => {
mutateInstalledPluginList()
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed }))
}, [mutateInstalledPluginList])
const handleNext = async () => {
switch (state.step) {
case InstallStepFromGitHub.setUrl: {
const { isValid, owner, repo } = parseGitHubUrl(state.repoUrl)
if (!isValid || !owner || !repo) {
Toast.notify({
type: 'error',
message: t('plugin.error.inValidGitHubUrl'),
})
break
}
const fetchedReleases = await fetchReleases(owner, repo)
setState(prevState => ({
...prevState,
releases: fetchedReleases,
step: InstallStepFromGitHub.selectPackage,
}))
break
}
case InstallStepFromGitHub.selectPackage: {
const repo = state.repoUrl.replace('https://github.com/', '')
if (error)
handleError(error)
else
await handleUpload(repo, state.selectedVersion, state.selectedPackage, handleUploaded)
break
}
case InstallStepFromGitHub.readyToInstall:
await handleInstall()
break
case InstallStepFromGitHub.installed:
break
}
}
const handleFailed = useCallback((errorMsg?: string) => {
setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installFailed }))
if (errorMsg)
setErrorMsg(errorMsg)
}, [])
const handleBack = () => {
setState((prevState) => {
@ -157,63 +133,69 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
}
})
}
return (
<Modal
isShow={true}
onClose={onClose}
className='flex min-w-[480px] p-0 flex-col items-start rounded-2xl border-[0.5px]
className='flex min-w-[560px] p-0 flex-col items-start rounded-2xl border-[0.5px]
border-components-panel-border bg-components-panel-bg shadows-shadow-xl'
closable
>
<div className='flex pt-6 pl-6 pb-3 pr-14 items-start gap-2 self-stretch'>
<div className='flex flex-col items-start gap-1 flex-grow'>
<div className='self-stretch text-text-primary title-2xl-semi-bold'>
{t('plugin.installFromGitHub.installPlugin')}
{getTitle()}
</div>
<div className='self-stretch text-text-tertiary system-xs-regular'>
{state.step !== InstallStepFromGitHub.installed && t('plugin.installFromGitHub.installNote')}
{!([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step)) && t('plugin.installFromGitHub.installNote')}
</div>
</div>
</div>
<div className={`flex px-6 py-3 flex-col justify-center items-start self-stretch ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
{state.step === InstallStepFromGitHub.setUrl && (
<SetURL
repoUrl={state.repoUrl}
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
onNext={handleNext}
onCancel={onClose}
/>
)}
{state.step === InstallStepFromGitHub.selectPackage && (
<SelectPackage
updatePayload={updatePayload!}
selectedVersion={state.selectedVersion}
versions={versions}
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))}
selectedPackage={state.selectedPackage}
packages={packages}
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: item.value as string }))}
onNext={handleNext}
onBack={handleBack}
/>
)}
{state.step === InstallStepFromGitHub.readyToInstall && (
<Loaded
isLoading={isLoading}
payload={manifest as any}
onBack={handleBack}
onInstall={handleNext}
/>
)}
{state.step === InstallStepFromGitHub.installed && (
<Installed
repoUrl={state.repoUrl}
selectedVersion={state.selectedVersion}
selectedPackage={state.selectedPackage}
onClose={onClose}
/>
)}
</div>
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
? <Installed
payload={manifest}
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
errMsg={errorMsg}
onCancel={onClose}
/>
: <div className={`flex px-6 py-3 flex-col justify-center items-start self-stretch ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
{state.step === InstallStepFromGitHub.setUrl && (
<SetURL
repoUrl={state.repoUrl}
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
onNext={handleUrlSubmit}
onCancel={onClose}
/>
)}
{state.step === InstallStepFromGitHub.selectPackage && (
<SelectPackage
updatePayload={updatePayload!}
repoUrl={state.repoUrl}
selectedVersion={state.selectedVersion}
versions={versions}
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))}
selectedPackage={state.selectedPackage}
packages={packages}
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: item.value as string }))}
onUploaded={handleUploaded}
onFailed={handleUploadFail}
onBack={handleBack}
/>
)}
{state.step === InstallStepFromGitHub.readyToInstall && (
<Loaded
uniqueIdentifier={uniqueIdentifier!}
payload={manifest as any}
repoUrl={state.repoUrl}
selectedVersion={state.selectedVersion}
selectedPackage={state.selectedPackage}
onBack={handleBack}
onInstalled={handleInstalled}
onFailed={handleFailed}
/>
)}
</div>}
</Modal>
)
}

View File

@ -1,54 +0,0 @@
import React from 'react'
import Button from '@/app/components/base/button'
import { useTranslation } from 'react-i18next'
type InstalledProps = {
repoUrl: string
selectedVersion: string
selectedPackage: string
onClose: () => void
}
const InfoRow = ({ label, value }: { label: string; value: string }) => (
<div className='flex items-center gap-3'>
<div className='flex-shrink-0 w-[72px] items-center gap-2'>
<div className='text-text-tertiary system-sm-medium truncate'>
{label}
</div>
</div>
<div className='flex-grow overflow-hidden'>
<div className='text-text-secondary text-ellipsis system-sm-medium'>
{value}
</div>
</div>
</div>
)
const Installed: React.FC<InstalledProps> = ({ repoUrl, selectedVersion, selectedPackage, onClose }) => {
const { t } = useTranslation()
return (
<>
<div className='text-text-secondary system-md-regular'>The plugin has been installed successfully.</div>
<div className='flex w-full p-4 flex-col justify-center items-start gap-2 rounded-2xl bg-background-section-burn'>
{[
{ label: t('plugin.installModal.labels.repository'), value: repoUrl },
{ label: t('plugin.installModal.labels.version'), value: selectedVersion },
{ label: t('plugin.installModal.labels.package'), value: selectedPackage },
].map(({ label, value }) => (
<InfoRow key={label} label={label} value={value} />
))}
</div>
<div className='flex justify-end items-center gap-2 self-stretch mt-4'>
<Button
variant='primary'
className='min-w-[72px]'
onClick={onClose}
>
{t('plugin.installModal.close')}
</Button>
</div>
</>
)
}
export default Installed

View File

@ -7,18 +7,73 @@ import Card from '../../../card'
import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { pluginManifestToCardPluginProps } from '../../utils'
import { useTranslation } from 'react-i18next'
import { installPackageFromGitHub } from '@/service/plugins'
import { RiLoader2Line } from '@remixicon/react'
import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store'
import checkTaskStatus from '../../base/check-task-status'
import { parseGitHubUrl } from '../../utils'
type LoadedProps = {
isLoading: boolean
uniqueIdentifier: string
payload: PluginDeclaration
repoUrl: string
selectedVersion: string
selectedPackage: string
onBack: () => void
onInstall: () => void
onInstalled: () => void
onFailed: (message?: string) => void
}
const i18nPrefix = 'plugin.installModal'
const Loaded: React.FC<LoadedProps> = ({ isLoading, payload, onBack, onInstall }) => {
const Loaded: React.FC<LoadedProps> = ({
uniqueIdentifier,
payload,
repoUrl,
selectedVersion,
selectedPackage,
onBack,
onInstalled,
onFailed,
}) => {
const { t } = useTranslation()
const [isInstalling, setIsInstalling] = React.useState(false)
const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling)
const { check } = checkTaskStatus()
const handleInstall = async () => {
if (isInstalling) return
setIsInstalling(true)
try {
const { owner, repo } = parseGitHubUrl(repoUrl)
const { all_installed: isInstalled, task_id: taskId } = await installPackageFromGitHub(
`${owner}/${repo}`,
selectedVersion,
selectedPackage,
uniqueIdentifier,
)
if (isInstalled) {
onInstalled()
return
}
setPluginTasksWithPolling()
await check({
taskId,
pluginUniqueIdentifier: uniqueIdentifier,
})
onInstalled()
}
catch (e) {
onFailed(e instanceof Error ? e.message : String(e))
}
finally {
setIsInstalling(false)
}
}
return (
<>
<div className='text-text-secondary system-md-regular'>
@ -32,20 +87,19 @@ const Loaded: React.FC<LoadedProps> = ({ isLoading, payload, onBack, onInstall }
/>
</div>
<div className='flex justify-end items-center gap-2 self-stretch mt-4'>
<Button
variant='secondary'
className='min-w-[72px]'
onClick={onBack}
>
{t('plugin.installModal.back')}
</Button>
{!isInstalling && (
<Button variant='secondary' className='min-w-[72px]' onClick={onBack}>
{t('plugin.installModal.back')}
</Button>
)}
<Button
variant='primary'
className='min-w-[72px]'
onClick={onInstall}
disabled={isLoading}
className='min-w-[72px] flex space-x-0.5'
onClick={handleInstall}
disabled={isInstalling}
>
{t('plugin.installModal.next')}
{isInstalling && <RiLoader2Line className='w-4 h-4 animate-spin-slow' />}
<span>{t(`${i18nPrefix}.${isInstalling ? 'installing' : 'install'}`)}</span>
</Button>
</div>
</>

View File

@ -4,34 +4,70 @@ import React from 'react'
import type { Item } from '@/app/components/base/select'
import { PortalSelect } from '@/app/components/base/select'
import Button from '@/app/components/base/button'
import type { UpdatePluginPayload } from '../../../types'
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
import { useTranslation } from 'react-i18next'
import { useGitHubUpload } from '../../hooks'
type SelectPackageProps = {
updatePayload: UpdatePluginPayload
updatePayload: UpdateFromGitHubPayload
repoUrl: string
selectedVersion: string
versions: Item[]
onSelectVersion: (item: Item) => void
selectedPackage: string
packages: Item[]
onSelectPackage: (item: Item) => void
onNext: () => void
onUploaded: (result: {
uniqueIdentifier: string
manifest: PluginDeclaration
}) => void
onFailed: (errorMsg: string) => void
onBack: () => void
}
const SelectPackage: React.FC<SelectPackageProps> = ({
updatePayload,
repoUrl,
selectedVersion,
versions,
onSelectVersion,
selectedPackage,
packages,
onSelectPackage,
onNext,
onUploaded,
onFailed,
onBack,
}) => {
const { t } = useTranslation()
const isEdit = Boolean(updatePayload)
const [isUploading, setIsUploading] = React.useState(false)
const { handleUpload, error } = useGitHubUpload()
const handleUploadPackage = async () => {
if (isUploading) return
setIsUploading(true)
try {
const repo = repoUrl.replace('https://github.com/', '')
await handleUpload(repo, selectedVersion, selectedPackage, (GitHubPackage) => {
onUploaded({
uniqueIdentifier: GitHubPackage.unique_identifier,
manifest: GitHubPackage.manifest,
})
})
}
catch (e: any) {
if (e.response?.message)
onFailed(e.response?.message)
else
onFailed(t('plugin.error.uploadFailed'))
}
finally {
setIsUploading(false)
}
}
return (
<>
<label
@ -68,6 +104,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
variant='secondary'
className='min-w-[72px]'
onClick={onBack}
disabled={isUploading}
>
{t('plugin.installModal.back')}
</Button>
@ -75,8 +112,8 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
<Button
variant='primary'
className='min-w-[72px]'
onClick={onNext}
disabled={!selectedVersion || !selectedPackage}
onClick={handleUploadPackage}
disabled={!selectedVersion || !selectedPackage || isUploading}
>
{t('plugin.installModal.next')}
</Button>

View File

@ -30,7 +30,7 @@ const Uploading: FC<Props> = ({
const handleUpload = async () => {
try {
const res = await uploadPackageFile(file)
// onUploaded(res)
onUploaded(res)
}
catch (e: any) {
if (e.response?.message) {

View File

@ -152,6 +152,7 @@ export type UpdateFromGitHubPayload = {
id: string
repo: string
version: string
package: string
}
}
@ -170,8 +171,9 @@ export enum InstallStepFromGitHub {
setUrl = 'url',
selectPackage = 'selecting',
readyToInstall = 'readyToInstall',
failed = 'failed',
uploadFailed = 'uploadFailed',
installed = 'installed',
installFailed = 'failed',
}
export type InstallState = {
@ -242,7 +244,7 @@ export type InstallPackageResponse = {
}
export type uploadGitHubResponse = {
plugin_unique_identifier: string
unique_identifier: string
manifest: PluginDeclaration
}

View File

@ -81,9 +81,14 @@ export const uploadGitHub = async (repoUrl: string, selectedVersion: string, sel
})
}
export const installPackageFromGitHub = async (uniqueIdentifier: string) => {
export const installPackageFromGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string, uniqueIdentifier: string) => {
return post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
body: { plugin_unique_identifiers: [uniqueIdentifier] },
body: {
repo: repoUrl,
version: selectedVersion,
package: selectedPackage,
plugin_unique_identifier: uniqueIdentifier,
},
})
}