chore: update installFromGitHub component

pull/12372/head
Yi 2 years ago
parent 8533ded335
commit bde1261b8c

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

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

@ -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

@ -7,18 +7,73 @@ import Card from '../../../card'
import Badge, { BadgeState } from '@/app/components/base/badge/index' import Badge, { BadgeState } from '@/app/components/base/badge/index'
import { pluginManifestToCardPluginProps } from '../../utils' import { pluginManifestToCardPluginProps } from '../../utils'
import { useTranslation } from 'react-i18next' 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 = { type LoadedProps = {
isLoading: boolean uniqueIdentifier: string
payload: PluginDeclaration payload: PluginDeclaration
repoUrl: string
selectedVersion: string
selectedPackage: string
onBack: () => void onBack: () => void
onInstall: () => void onInstalled: () => void
onFailed: (message?: string) => void
} }
const i18nPrefix = 'plugin.installModal' 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 { 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 ( return (
<> <>
<div className='text-text-secondary system-md-regular'> <div className='text-text-secondary system-md-regular'>
@ -32,20 +87,19 @@ const Loaded: React.FC<LoadedProps> = ({ isLoading, payload, onBack, onInstall }
/> />
</div> </div>
<div className='flex justify-end items-center gap-2 self-stretch mt-4'> <div className='flex justify-end items-center gap-2 self-stretch mt-4'>
<Button {!isInstalling && (
variant='secondary' <Button variant='secondary' className='min-w-[72px]' onClick={onBack}>
className='min-w-[72px]'
onClick={onBack}
>
{t('plugin.installModal.back')} {t('plugin.installModal.back')}
</Button> </Button>
)}
<Button <Button
variant='primary' variant='primary'
className='min-w-[72px]' className='min-w-[72px] flex space-x-0.5'
onClick={onInstall} onClick={handleInstall}
disabled={isLoading} disabled={isInstalling}
> >
{t('plugin.installModal.next')} {isInstalling && <RiLoader2Line className='w-4 h-4 animate-spin-slow' />}
<span>{t(`${i18nPrefix}.${isInstalling ? 'installing' : 'install'}`)}</span>
</Button> </Button>
</div> </div>
</> </>

@ -4,34 +4,70 @@ import React from 'react'
import type { Item } from '@/app/components/base/select' import type { Item } from '@/app/components/base/select'
import { PortalSelect } from '@/app/components/base/select' import { PortalSelect } from '@/app/components/base/select'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import type { UpdatePluginPayload } from '../../../types' import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../../types'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useGitHubUpload } from '../../hooks'
type SelectPackageProps = { type SelectPackageProps = {
updatePayload: UpdatePluginPayload updatePayload: UpdateFromGitHubPayload
repoUrl: string
selectedVersion: string selectedVersion: string
versions: Item[] versions: Item[]
onSelectVersion: (item: Item) => void onSelectVersion: (item: Item) => void
selectedPackage: string selectedPackage: string
packages: Item[] packages: Item[]
onSelectPackage: (item: Item) => void onSelectPackage: (item: Item) => void
onNext: () => void onUploaded: (result: {
uniqueIdentifier: string
manifest: PluginDeclaration
}) => void
onFailed: (errorMsg: string) => void
onBack: () => void onBack: () => void
} }
const SelectPackage: React.FC<SelectPackageProps> = ({ const SelectPackage: React.FC<SelectPackageProps> = ({
updatePayload, updatePayload,
repoUrl,
selectedVersion, selectedVersion,
versions, versions,
onSelectVersion, onSelectVersion,
selectedPackage, selectedPackage,
packages, packages,
onSelectPackage, onSelectPackage,
onNext, onUploaded,
onFailed,
onBack, onBack,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const isEdit = Boolean(updatePayload) 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 ( return (
<> <>
<label <label
@ -68,6 +104,7 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
variant='secondary' variant='secondary'
className='min-w-[72px]' className='min-w-[72px]'
onClick={onBack} onClick={onBack}
disabled={isUploading}
> >
{t('plugin.installModal.back')} {t('plugin.installModal.back')}
</Button> </Button>
@ -75,8 +112,8 @@ const SelectPackage: React.FC<SelectPackageProps> = ({
<Button <Button
variant='primary' variant='primary'
className='min-w-[72px]' className='min-w-[72px]'
onClick={onNext} onClick={handleUploadPackage}
disabled={!selectedVersion || !selectedPackage} disabled={!selectedVersion || !selectedPackage || isUploading}
> >
{t('plugin.installModal.next')} {t('plugin.installModal.next')}
</Button> </Button>

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

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

@ -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', { return post<InstallPackageResponse>('/workspaces/current/plugin/install/github', {
body: { plugin_unique_identifiers: [uniqueIdentifier] }, body: {
repo: repoUrl,
version: selectedVersion,
package: selectedPackage,
plugin_unique_identifier: uniqueIdentifier,
},
}) })
} }

Loading…
Cancel
Save