diff --git a/web/app/components/header/plugins-nav/downloading-icon.module.css b/web/app/components/header/plugins-nav/downloading-icon.module.css new file mode 100644 index 0000000000..c11a9f2f2c --- /dev/null +++ b/web/app/components/header/plugins-nav/downloading-icon.module.css @@ -0,0 +1,44 @@ +@keyframes realistic-blink { + 0% { fill: #37ff37; opacity: 0.4; } + 15% { fill: #37ff37; opacity: 0.9; } + 25% { fill: #37ff37; opacity: 0.3; } + 38% { fill: #ff4444; opacity: 0.8; } + 42% { fill: #ff4444; opacity: 0.3; } + 58% { fill: #37ff37; opacity: 0.9; } + 65% { fill: #37ff37; opacity: 0.4; } + 79% { fill: #ff4444; opacity: 0.8; } + 84% { fill: #ff4444; opacity: 0.3; } + 92% { fill: #37ff37; opacity: 0.8; } + 100% { fill: #37ff37; opacity: 0.4; } +} + +@keyframes drop { + 0% { + transform: translateY(-4px); + opacity: 0; + } + 5% { + transform: translateY(-4px); + opacity: 1; + } + 65% { + transform: translateY(2px); + opacity: 1; + } + 80% { + transform: translateY(2px); + opacity: 0; + } + 100% { + transform: translateY(2px); + opacity: 0; + } +} + +#downloadingIconLight { + animation: realistic-blink 3s infinite ease-in-out; +} + +#downloadingIconArrow { + animation: drop 1.2s cubic-bezier(0.4, 0, 1, 1) infinite; +} \ No newline at end of file diff --git a/web/app/components/header/plugins-nav/downloading-icon.tsx b/web/app/components/header/plugins-nav/downloading-icon.tsx new file mode 100644 index 0000000000..d3fc486445 --- /dev/null +++ b/web/app/components/header/plugins-nav/downloading-icon.tsx @@ -0,0 +1,17 @@ +import s from './downloading-icon.module.css' + +const DownloadingIcon = () => { + return ( +
+ + + + + + + +
+ ) +} + +export default DownloadingIcon diff --git a/web/app/components/header/plugins-nav/index.tsx b/web/app/components/header/plugins-nav/index.tsx index aa57e652b2..b2c6eba1a6 100644 --- a/web/app/components/header/plugins-nav/index.tsx +++ b/web/app/components/header/plugins-nav/index.tsx @@ -5,6 +5,9 @@ import Link from 'next/link' import classNames from '@/utils/classnames' import { Group } from '@/app/components/base/icons/src/vender/other' import { useSelectedLayoutSegment } from 'next/navigation' +import DownloadingIcon from './downloading-icon' +import { usePluginTaskStatus } from '@/app/components/plugins/plugin-page/plugin-tasks/hooks' +import Indicator from '@/app/components/header/indicator' type PluginsNavProps = { className?: string @@ -16,17 +19,43 @@ const PluginsNav = ({ const { t } = useTranslation() const selectedSegment = useSelectedLayoutSegment() const activated = selectedSegment === 'plugins' + const { + isInstalling, + isInstallingWithError, + isFailed, + } = usePluginTaskStatus() return ( -
+
+ { + (isFailed || isInstallingWithError) && !activated && ( + + ) + }
- + { + (!(isInstalling || isInstallingWithError) || activated) && ( + + ) + } + { + (isInstalling || isInstallingWithError) && !activated && ( + + ) + }
{t('common.menus.plugins')}
diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts b/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts index e15f9b963a..f32a812c13 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts +++ b/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts @@ -64,14 +64,16 @@ export const usePluginTaskStatus = () => { const timerRef = useRef(null) useEffect(() => { - if (isSuccess && opacity > 0) { + if (isSuccess) { if (timerRef.current) { clearTimeout(timerRef.current) timerRef.current = null } - timerRef.current = setTimeout(() => { - setOpacity(v => v - 0.1) - }, 200) + if (opacity > 0) { + timerRef.current = setTimeout(() => { + setOpacity(v => v - 0.1) + }, 200) + } } if (!isSuccess)