diff --git a/web/app/components/plugins/install-plugin/hooks/use-fold-anim-into.ts b/web/app/components/plugins/install-plugin/hooks/use-fold-anim-into.ts index f1c2a612d6..5bf17bb917 100644 --- a/web/app/components/plugins/install-plugin/hooks/use-fold-anim-into.ts +++ b/web/app/components/plugins/install-plugin/hooks/use-fold-anim-into.ts @@ -1,7 +1,8 @@ import { sleep } from '@/utils' -// modalElem fold into plugin install task btn const animTime = 2000 +const modalClassName = 'install-modal' +const COUNT_DOWN_TIME = 15000 // 15s function getElemCenter(elem: HTMLElement) { const rect = elem.getBoundingClientRect() @@ -12,7 +13,13 @@ function getElemCenter(elem: HTMLElement) { } const useFoldAnimInto = (onClose: () => void) => { - return async function foldIntoAnim(modalClassName: string) { + let countDownRunId: number + const clearCountDown = () => { + clearTimeout(countDownRunId) + } + // modalElem fold into plugin install task btn + const foldIntoAnim = async () => { + clearCountDown() const modalElem = document.querySelector(`.${modalClassName}`) as HTMLElement const pluginTaskTriggerElem = document.getElementById('plugin-task-trigger') @@ -32,6 +39,19 @@ const useFoldAnimInto = (onClose: () => void) => { await sleep(animTime) onClose() } + + const countDownFoldIntoAnim = async () => { + countDownRunId = window.setTimeout(() => { + foldIntoAnim() + }, COUNT_DOWN_TIME) + } + + return { + modalClassName, + foldIntoAnim, + clearCountDown, + countDownFoldIntoAnim, + } } export default useFoldAnimInto diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx index 67ed8ee547..72262894db 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx @@ -1,6 +1,6 @@ 'use client' -import React, { useCallback, useRef, useState } from 'react' +import React, { useCallback, useState } from 'react' import Modal from '@/app/components/base/modal' import type { Dependency, Plugin, PluginManifestInMarket } from '../../types' import { InstallStep } from '../../types' @@ -37,8 +37,27 @@ const InstallFromMarketplace: React.FC = ({ const [errorMsg, setErrorMsg] = useState(null) const { refreshPluginList } = useRefreshPluginList() - const modalRef = useRef(null) - const foldAnimInto = useFoldAnimInto(onClose) + const { + modalClassName, + foldIntoAnim: doFoldAnimInto, + clearCountDown, + countDownFoldIntoAnim, + } = useFoldAnimInto(onClose) + + const [isInstalling, setIsInstalling] = useState(false) + + const foldAnimInto = useCallback(() => { + if (isInstalling) { + doFoldAnimInto() + return + } + onClose() + }, [doFoldAnimInto, isInstalling, onClose]) + + const handleStartToInstall = useCallback(() => { + setIsInstalling(true) + countDownFoldIntoAnim() + }, [countDownFoldIntoAnim]) const getTitle = useCallback(() => { if (isBundle && step === InstallStep.installed) @@ -53,20 +72,20 @@ const InstallFromMarketplace: React.FC = ({ const handleInstalled = useCallback(() => { setStep(InstallStep.installed) refreshPluginList(manifest) + setIsInstalling(false) }, [manifest, refreshPluginList]) const handleFailed = useCallback((errorMsg?: string) => { setStep(InstallStep.installFailed) + setIsInstalling(false) if (errorMsg) setErrorMsg(errorMsg) }, []) - const modalClassName = 'install-modal' - return ( step === InstallStep.readyToInstall ? foldAnimInto(modalClassName) : onClose()} + onClose={foldAnimInto} wrapperClassName='z-[9999]' className={cn(modalClassName, '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 @@ -94,6 +113,7 @@ const InstallFromMarketplace: React.FC = ({ onCancel={onClose} onInstalled={handleInstalled} onFailed={handleFailed} + onStartToInstall={handleStartToInstall} /> )} { diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx index 01c4d44bad..0779f27ef6 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx @@ -56,6 +56,7 @@ const Installed: FC = ({ useEffect(() => { if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) onInstalled() + // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasInstalled]) const handleCancel = () => { @@ -67,7 +68,6 @@ const Installed: FC = ({ if (isInstalling) return onStartToInstall?.() setIsInstalling(true) - try { let taskId let isInstalled diff --git a/web/service/fetch.ts b/web/service/fetch.ts index 5458d78b33..10643173bc 100644 --- a/web/service/fetch.ts +++ b/web/service/fetch.ts @@ -108,10 +108,6 @@ const beforeRequestAuthorization: BeforeRequestHook = (request) => { request.headers.set('Authorization', `Bearer ${accessToken}`) } -const beforeRequestDeleteContentType: BeforeRequestHook = (request) => { - request.headers.delete('Content-Type') -} - const baseHooks: Hooks = { afterResponse: [ afterResponse204, @@ -134,7 +130,7 @@ export const baseOptions: RequestInit = { } async function base(url: string, options: FetchOptionType = {}, otherOptions: IOtherOptions = {}): Promise { - const { params, body, ...init } = Object.assign({}, baseOptions, options) + const { params, body, headers, ...init } = Object.assign({}, baseOptions, options) const { isPublicAPI = false, isMarketplaceAPI = false, @@ -159,6 +155,9 @@ async function base(url: string, options: FetchOptionType = {}, otherOptions: const fetchPathname = `${base}${url.startsWith('/') ? url : `/${url}`}` + if (deleteContentType) + (headers as any).delete('Content-Type') + const client = baseClient.extend({ hooks: { ...baseHooks, @@ -170,8 +169,7 @@ async function base(url: string, options: FetchOptionType = {}, otherOptions: ...baseHooks.beforeRequest || [], isPublicAPI && beforeRequestPublicAuthorization, !isPublicAPI && !isMarketplaceAPI && beforeRequestAuthorization, - deleteContentType && beforeRequestDeleteContentType, - ].filter(i => !!i), + ].filter(Boolean), afterResponse: [ ...baseHooks.afterResponse || [], afterResponseErrorCode(otherOptions), @@ -181,6 +179,7 @@ async function base(url: string, options: FetchOptionType = {}, otherOptions: const res = await client(fetchPathname, { ...init, + headers, credentials: isMarketplaceAPI ? 'omit' : (options.credentials || 'include'),