diff --git a/web/app/components/base/select/index.tsx b/web/app/components/base/select/index.tsx index 690d068f0d..0b8ff096ff 100644 --- a/web/app/components/base/select/index.tsx +++ b/web/app/components/base/select/index.tsx @@ -3,6 +3,7 @@ import type { FC } from 'react' import React, { Fragment, useEffect, useState } from 'react' import { Combobox, Listbox, Transition } from '@headlessui/react' import { CheckIcon, ChevronDownIcon, ChevronUpIcon, XMarkIcon } from '@heroicons/react/20/solid' +import Badge from '../badge/index' import { useTranslation } from 'react-i18next' import classNames from '@/utils/classnames' import { @@ -26,7 +27,7 @@ export type Item = { name: string } & Record -export interface ISelectProps { +export type ISelectProps = { className?: string wrapperClassName?: string renderTrigger?: (value: Item | null) => JSX.Element | null @@ -284,11 +285,12 @@ const SimpleSelect: FC = ({ ) } -interface PortalSelectProps { +type PortalSelectProps = { value: string | number onSelect: (value: Item) => void items: Item[] placeholder?: string + installedValue?: string | number renderTrigger?: (value?: Item) => JSX.Element | null triggerClassName?: string triggerClassNameFn?: (open: boolean) => string @@ -302,6 +304,7 @@ const PortalSelect: FC = ({ onSelect, items, placeholder, + installedValue, renderTrigger, triggerClassName, triggerClassNameFn, @@ -366,7 +369,10 @@ const PortalSelect: FC = ({ className='w-0 grow truncate' title={item.name} > - {item.name} + {item.name} + {item.value === installedValue && ( + INSTALLED + )} {!hideChecked && item.value === value && ( diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index 22c25ab1ab..52b381d002 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useState } from 'react' +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' @@ -8,12 +8,17 @@ import { useGitHubReleases, useGitHubUpload } from '../hooks' import { parseGitHubUrl } from '../utils' import type { PluginDeclaration, UpdatePluginPayload } 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 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' type InstallFromGitHubProps = { updatePayload?: UpdatePluginPayload @@ -29,8 +34,13 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on selectedPackage: updatePayload?.currPackage || '', releases: [], }) + const { getIconUrl } = useGetIcon() const [uniqueIdentifier, setUniqueIdentifier] = useState(null) const [manifest, setManifest] = useState(null) + const [errorMsg, setErrorMsg] = useState(null) + const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling) + const { check } = checkTaskStatus() + const mutateInstalledPluginList = usePluginPageContext(v => v.mutateInstalledPluginList) const versions: Item[] = state.releases.map(release => ({ value: release.tag_name, @@ -50,10 +60,53 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on const { isLoading, handleUpload, error } = useGitHubUpload() const { fetchReleases } = useGitHubReleases() - const handleInstall = async () => { - + const handleError = (e: any) => { + const message = e?.response?.message || t('plugin.error.installFailed') + setErrorMsg(message) + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.failed })) } + const handleUploaded = async (GitHubPackage: any) => { + try { + const icon = await getIconUrl(GitHubPackage.manifest.icon) + setManifest({ + ...GitHubPackage.manifest, + icon, + }) + setUniqueIdentifier(GitHubPackage.uniqueIdentifier) + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.readyToInstall })) + } + catch (e) { + handleError(e) + } + } + + 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 handleInstalled = useCallback(() => { + mutateInstalledPluginList() + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) + }, [mutateInstalledPluginList]) + const handleNext = async () => { switch (state.step) { case InstallStepFromGitHub.setUrl: { @@ -76,24 +129,16 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on } case InstallStepFromGitHub.selectPackage: { const repo = state.repoUrl.replace('https://github.com/', '') - if (error) { - Toast.notify({ - type: 'error', - message: error, - }) - } - else { - await handleUpload(repo, state.selectedVersion, state.selectedPackage, (GitHubPackage) => { - setManifest(GitHubPackage.manifest) - setUniqueIdentifier(GitHubPackage.uniqueIdentifier) - setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.loaded })) - }) - } + if (error) + handleError(error) + + else + await handleUpload(repo, state.selectedVersion, state.selectedPackage, handleUploaded) + break } - case InstallStepFromGitHub.loaded: - setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) - handleInstall() + case InstallStepFromGitHub.readyToInstall: + await handleInstall() break case InstallStepFromGitHub.installed: break @@ -105,7 +150,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on switch (prevState.step) { case InstallStepFromGitHub.selectPackage: return { ...prevState, step: InstallStepFromGitHub.setUrl } - case InstallStepFromGitHub.loaded: + case InstallStepFromGitHub.readyToInstall: return { ...prevState, step: InstallStepFromGitHub.selectPackage } default: return prevState @@ -141,7 +186,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on )} {state.step === InstallStepFromGitHub.selectPackage && ( setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))} @@ -152,7 +197,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on onBack={handleBack} /> )} - {state.step === InstallStepFromGitHub.loaded && ( + {state.step === InstallStepFromGitHub.readyToInstall && ( void @@ -19,7 +20,7 @@ type SelectPackageProps = { } const SelectPackage: React.FC = ({ - isEdit = false, + updatePayload, selectedVersion, versions, onSelectVersion, @@ -30,6 +31,7 @@ const SelectPackage: React.FC = ({ onBack, }) => { const { t } = useTranslation() + const isEdit = Boolean(updatePayload) return ( <>