'use client' import type { ForwardRefRenderFunction } from 'react' import { useImperativeHandle } from 'react' import React, { useCallback, useEffect, useMemo, useState } from 'react' import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types' import MarketplaceItem from '../item/marketplace-item' import GithubItem from '../item/github-item' import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins' import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed' import produce from 'immer' import PackageItem from '../item/package-item' import LoadingError from '../../base/loading-error' import { useGlobalPublicStore } from '@/context/global-public-context' import { pluginInstallLimit } from '../../hooks/use-install-plugin-limit' type Props = { allPlugins: Dependency[] selectedPlugins: Plugin[] onSelect: (plugin: Plugin, selectedIndex: number, allCanInstallPluginsLength: number) => void onSelectAll: (plugins: Plugin[], selectedIndexes: number[]) => void onDeSelectAll: () => void onLoadedAllPlugin: (installedInfo: Record) => void isFromMarketPlace?: boolean } export type ExposeRefs = { selectAllPlugins: () => void deSelectAllPlugins: () => void } const InstallByDSLList: ForwardRefRenderFunction = ({ allPlugins, selectedPlugins, onSelect, onSelectAll, onDeSelectAll, onLoadedAllPlugin, isFromMarketPlace, }, ref) => { const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) // DSL has id, to get plugin info to show more info const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => { const dependecy = (d as GitHubItemAndMarketPlaceDependency).value // split org, name, version by / and : // and remove @ and its suffix const [orgPart, nameAndVersionPart] = dependecy.marketplace_plugin_unique_identifier!.split('@')[0].split('/') const [name, version] = nameAndVersionPart.split(':') return { organization: orgPart, plugin: name, version, } })) // has meta(org,name,version), to get id const { isLoading: isFetchingDataByMeta, data: infoByMeta, error: infoByMetaError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!)) const [plugins, doSetPlugins] = useState<(Plugin | undefined)[]>((() => { const hasLocalPackage = allPlugins.some(d => d.type === 'package') if (!hasLocalPackage) return [] const _plugins = allPlugins.map((d) => { if (d.type === 'package') { return { ...(d as any).value.manifest, plugin_id: (d as any).value.unique_identifier, } } return undefined }) return _plugins })()) const pluginsRef = React.useRef<(Plugin | undefined)[]>(plugins) const setPlugins = useCallback((p: (Plugin | undefined)[]) => { doSetPlugins(p) pluginsRef.current = p }, []) const [errorIndexes, setErrorIndexes] = useState([]) const handleGitHubPluginFetched = useCallback((index: number) => { return (p: Plugin) => { const nextPlugins = produce(pluginsRef.current, (draft) => { draft[index] = p }) setPlugins(nextPlugins) } }, [setPlugins]) const handleGitHubPluginFetchError = useCallback((index: number) => { return () => { setErrorIndexes([...errorIndexes, index]) } }, [errorIndexes]) const marketPlaceInDSLIndex = useMemo(() => { const res: number[] = [] allPlugins.forEach((d, index) => { if (d.type === 'marketplace') res.push(index) }) return res }, [allPlugins]) useEffect(() => { if (!isFetchingMarketplaceDataById && infoGetById?.data.list) { const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => { const p = d as GitHubItemAndMarketPlaceDependency const id = p.value.marketplace_plugin_unique_identifier?.split(':')[0] const retPluginInfo = infoGetById.data.list.find(item => item.plugin.plugin_id === id)?.plugin return { ...retPluginInfo, from: d.type } as Plugin }) const payloads = sortedList const failedIndex: number[] = [] const nextPlugins = produce(pluginsRef.current, (draft) => { marketPlaceInDSLIndex.forEach((index, i) => { if (payloads[i]) { draft[index] = { ...payloads[i], version: payloads[i]!.version || payloads[i]!.latest_version, } } else { failedIndex.push(index) } }) }) setPlugins(nextPlugins) if (failedIndex.length > 0) setErrorIndexes([...errorIndexes, ...failedIndex]) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isFetchingMarketplaceDataById]) useEffect(() => { if (!isFetchingDataByMeta && infoByMeta?.data.list) { const payloads = infoByMeta?.data.list const failedIndex: number[] = [] const nextPlugins = produce(pluginsRef.current, (draft) => { marketPlaceInDSLIndex.forEach((index, i) => { if (payloads[i]) { const item = payloads[i] draft[index] = { ...item.plugin, plugin_id: item.version.unique_identifier, } } else { failedIndex.push(index) } }) }) setPlugins(nextPlugins) if (failedIndex.length > 0) setErrorIndexes([...errorIndexes, ...failedIndex]) } // eslint-disable-next-line react-hooks/exhaustive-deps }, [isFetchingDataByMeta]) useEffect(() => { // get info all failed if (infoByMetaError || infoByIdError) setErrorIndexes([...errorIndexes, ...marketPlaceInDSLIndex]) // eslint-disable-next-line react-hooks/exhaustive-deps }, [infoByMetaError, infoByIdError]) const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length const { installedInfo } = useCheckInstalled({ pluginIds: plugins?.filter(p => !!p).map((d) => { return `${d?.org || d?.author}/${d?.name}` }) || [], enabled: isLoadedAllData, }) const getVersionInfo = useCallback((pluginId: string) => { const pluginDetail = installedInfo?.[pluginId] const hasInstalled = !!pluginDetail return { hasInstalled, installedVersion: pluginDetail?.installedVersion, toInstallVersion: '', } }, [installedInfo]) useEffect(() => { if (isLoadedAllData && installedInfo) onLoadedAllPlugin(installedInfo!) // eslint-disable-next-line react-hooks/exhaustive-deps }, [isLoadedAllData, installedInfo]) const handleSelect = useCallback((index: number) => { return () => { const canSelectPlugins = plugins.filter((p) => { const { canInstall } = pluginInstallLimit(p!, systemFeatures) return canInstall }) onSelect(plugins[index]!, index, canSelectPlugins.length) } }, [onSelect, plugins, systemFeatures]) useImperativeHandle(ref, () => ({ selectAllPlugins: () => { const selectedIndexes: number[] = [] const selectedPlugins: Plugin[] = [] allPlugins.forEach((d, index) => { const p = plugins[index] if (!p) return const { canInstall } = pluginInstallLimit(p, systemFeatures) if (canInstall) { selectedIndexes.push(index) selectedPlugins.push(p) } }) onSelectAll(selectedPlugins, selectedIndexes) }, deSelectAllPlugins: () => { onDeSelectAll() }, })) return ( <> {allPlugins.map((d, index) => { if (errorIndexes.includes(index)) { return ( ) } const plugin = plugins[index] if (d.type === 'github') { return ( p.plugin_id === plugins[index]?.plugin_id)} onCheckedChange={handleSelect(index)} dependency={d as GitHubItemAndMarketPlaceDependency} onFetchedPayload={handleGitHubPluginFetched(index)} onFetchError={handleGitHubPluginFetchError(index)} versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)} />) } if (d.type === 'marketplace') { return ( p.plugin_id === plugins[index]?.plugin_id)} onCheckedChange={handleSelect(index)} payload={{ ...plugin, from: d.type } as Plugin} version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''} versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)} /> ) } // Local package return ( p.plugin_id === plugins[index]?.plugin_id)} onCheckedChange={handleSelect(index)} payload={d as PackageDependency} isFromMarketPlace={isFromMarketPlace} versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)} /> ) }) } ) } export default React.forwardRef(InstallByDSLList)