'use client' import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { BlockEnum } from '../types' import type { TriggerDefaultValue, TriggerWithProvider } from './types' import type { Plugin } from '@/app/components/plugins/types' import { useGetLanguage } from '@/context/i18n' import BlockIcon from '../block-icon' import Tooltip from '@/app/components/base/tooltip' import { RiMoreLine } from '@remixicon/react' import Loading from '@/app/components/base/loading' import Link from 'next/link' import { getMarketplaceUrl } from '@/utils/var' import TriggerPluginItem from './trigger-plugin/item' import { formatNumber } from '@/utils/format' import Action from '@/app/components/workflow/block-selector/market-place-plugin/action' import { ArrowDownDoubleLine, ArrowDownRoundFill, ArrowUpDoubleLine } from '@/app/components/base/icons/src/vender/solid/arrows' import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace' const MAX_RECOMMENDED_COUNT = 15 const INITIAL_VISIBLE_COUNT = 5 type FeaturedTriggersProps = { plugins: Plugin[] providerMap: Map onSelect: (type: BlockEnum, trigger?: TriggerDefaultValue) => void isLoading?: boolean onInstallSuccess?: () => void | Promise } const STORAGE_KEY = 'workflow_triggers_featured_collapsed' const FeaturedTriggers = ({ plugins, providerMap, onSelect, isLoading = false, onInstallSuccess, }: FeaturedTriggersProps) => { const { t } = useTranslation() const language = useGetLanguage() const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_COUNT) const [isCollapsed, setIsCollapsed] = useState(() => { if (typeof window === 'undefined') return false const stored = window.localStorage.getItem(STORAGE_KEY) return stored === 'true' }) useEffect(() => { if (typeof window === 'undefined') return const stored = window.localStorage.getItem(STORAGE_KEY) if (stored !== null) setIsCollapsed(stored === 'true') }, []) useEffect(() => { if (typeof window === 'undefined') return window.localStorage.setItem(STORAGE_KEY, String(isCollapsed)) }, [isCollapsed]) useEffect(() => { setVisibleCount(INITIAL_VISIBLE_COUNT) }, [plugins]) const limitedPlugins = useMemo( () => plugins.slice(0, MAX_RECOMMENDED_COUNT), [plugins], ) const { installedProviders, uninstalledPlugins, } = useMemo(() => { const installed: TriggerWithProvider[] = [] const uninstalled: Plugin[] = [] const visitedProviderIds = new Set() limitedPlugins.forEach((plugin) => { const provider = providerMap.get(plugin.plugin_id) || providerMap.get(plugin.latest_package_identifier) if (provider) { if (!visitedProviderIds.has(provider.id)) { installed.push(provider) visitedProviderIds.add(provider.id) } } else { uninstalled.push(plugin) } }) return { installedProviders: installed, uninstalledPlugins: uninstalled, } }, [limitedPlugins, providerMap]) const totalQuota = Math.min(visibleCount, MAX_RECOMMENDED_COUNT) const visibleInstalledProviders = useMemo( () => installedProviders.slice(0, totalQuota), [installedProviders, totalQuota], ) const remainingSlots = Math.max(totalQuota - visibleInstalledProviders.length, 0) const visibleUninstalledPlugins = useMemo( () => (remainingSlots > 0 ? uninstalledPlugins.slice(0, remainingSlots) : []), [uninstalledPlugins, remainingSlots], ) const totalVisible = visibleInstalledProviders.length + visibleUninstalledPlugins.length const maxAvailable = Math.min(MAX_RECOMMENDED_COUNT, installedProviders.length + uninstalledPlugins.length) const hasMoreToShow = totalVisible < maxAvailable const canToggleVisibility = maxAvailable > INITIAL_VISIBLE_COUNT const isExpanded = canToggleVisibility && !hasMoreToShow const showEmptyState = !isLoading && totalVisible === 0 return (
{!isCollapsed && ( <> {isLoading && (
)} {showEmptyState && (

{t('workflow.tabs.noFeaturedTriggers')}

)} {!showEmptyState && !isLoading && ( <> {visibleInstalledProviders.length > 0 && (
{visibleInstalledProviders.map(provider => ( ))}
)} {visibleUninstalledPlugins.length > 0 && (
{visibleUninstalledPlugins.map(plugin => ( { await onInstallSuccess?.() }} t={t} /> ))}
)} )} {!isLoading && totalVisible > 0 && canToggleVisibility && (
{ setVisibleCount((count) => { if (count >= maxAvailable) return INITIAL_VISIBLE_COUNT return Math.min(count + INITIAL_VISIBLE_COUNT, maxAvailable) }) }} >
{isExpanded ? ( ) : ( )}
{t(isExpanded ? 'workflow.tabs.showLessFeatured' : 'workflow.tabs.showMoreFeatured')}
)} )}
) } type FeaturedTriggerUninstalledItemProps = { plugin: Plugin language: string onInstallSuccess?: () => Promise | void t: (key: string, options?: Record) => string } function FeaturedTriggerUninstalledItem({ plugin, language, onInstallSuccess, t, }: FeaturedTriggerUninstalledItemProps) { const label = plugin.label?.[language] || plugin.name const description = typeof plugin.brief === 'object' ? plugin.brief[language] : plugin.brief const installCountLabel = t('plugin.install', { num: formatNumber(plugin.install_count || 0) }) const [actionOpen, setActionOpen] = useState(false) const [isActionHovered, setIsActionHovered] = useState(false) const [isInstallModalOpen, setIsInstallModalOpen] = useState(false) useEffect(() => { if (!actionOpen) return const handleScroll = () => { setActionOpen(false) setIsActionHovered(false) } window.addEventListener('scroll', handleScroll, true) return () => { window.removeEventListener('scroll', handleScroll, true) } }, [actionOpen]) return ( <>
{label}
{description}
)} disabled={!description || isActionHovered || actionOpen || isInstallModalOpen} >
{label}
{installCountLabel}
setIsActionHovered(true)} onMouseLeave={() => { if (!actionOpen) setIsActionHovered(false) }} > { setActionOpen(value) setIsActionHovered(value) }} author={plugin.org} name={plugin.name} version={plugin.latest_version} />
{isInstallModalOpen && ( { setIsInstallModalOpen(false) setIsActionHovered(false) await onInstallSuccess?.() }} onClose={() => { setIsInstallModalOpen(false) setIsActionHovered(false) }} /> )} ) } export default FeaturedTriggers