You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

install-multi.tsx 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. 'use client'
  2. import type { ForwardRefRenderFunction } from 'react'
  3. import { useImperativeHandle } from 'react'
  4. import React, { useCallback, useEffect, useMemo, useState } from 'react'
  5. import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types'
  6. import MarketplaceItem from '../item/marketplace-item'
  7. import GithubItem from '../item/github-item'
  8. import { useFetchPluginsInMarketPlaceByInfo } from '@/service/use-plugins'
  9. import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use-check-installed'
  10. import produce from 'immer'
  11. import PackageItem from '../item/package-item'
  12. import LoadingError from '../../base/loading-error'
  13. import { useGlobalPublicStore } from '@/context/global-public-context'
  14. import { pluginInstallLimit } from '../../hooks/use-install-plugin-limit'
  15. type Props = {
  16. allPlugins: Dependency[]
  17. selectedPlugins: Plugin[]
  18. onSelect: (plugin: Plugin, selectedIndex: number, allCanInstallPluginsLength: number) => void
  19. onSelectAll: (plugins: Plugin[], selectedIndexes: number[]) => void
  20. onDeSelectAll: () => void
  21. onLoadedAllPlugin: (installedInfo: Record<string, VersionInfo>) => void
  22. isFromMarketPlace?: boolean
  23. }
  24. export type ExposeRefs = {
  25. selectAllPlugins: () => void
  26. deSelectAllPlugins: () => void
  27. }
  28. const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({
  29. allPlugins,
  30. selectedPlugins,
  31. onSelect,
  32. onSelectAll,
  33. onDeSelectAll,
  34. onLoadedAllPlugin,
  35. isFromMarketPlace,
  36. }, ref) => {
  37. const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
  38. // DSL has id, to get plugin info to show more info
  39. const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => {
  40. const dependecy = (d as GitHubItemAndMarketPlaceDependency).value
  41. // split org, name, version by / and :
  42. // and remove @ and its suffix
  43. const [orgPart, nameAndVersionPart] = dependecy.marketplace_plugin_unique_identifier!.split('@')[0].split('/')
  44. const [name, version] = nameAndVersionPart.split(':')
  45. return {
  46. organization: orgPart,
  47. plugin: name,
  48. version,
  49. }
  50. }))
  51. // has meta(org,name,version), to get id
  52. const { isLoading: isFetchingDataByMeta, data: infoByMeta, error: infoByMetaError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map(d => (d as GitHubItemAndMarketPlaceDependency).value!))
  53. const [plugins, doSetPlugins] = useState<(Plugin | undefined)[]>((() => {
  54. const hasLocalPackage = allPlugins.some(d => d.type === 'package')
  55. if (!hasLocalPackage)
  56. return []
  57. const _plugins = allPlugins.map((d) => {
  58. if (d.type === 'package') {
  59. return {
  60. ...(d as any).value.manifest,
  61. plugin_id: (d as any).value.unique_identifier,
  62. }
  63. }
  64. return undefined
  65. })
  66. return _plugins
  67. })())
  68. const pluginsRef = React.useRef<(Plugin | undefined)[]>(plugins)
  69. const setPlugins = useCallback((p: (Plugin | undefined)[]) => {
  70. doSetPlugins(p)
  71. pluginsRef.current = p
  72. }, [])
  73. const [errorIndexes, setErrorIndexes] = useState<number[]>([])
  74. const handleGitHubPluginFetched = useCallback((index: number) => {
  75. return (p: Plugin) => {
  76. const nextPlugins = produce(pluginsRef.current, (draft) => {
  77. draft[index] = p
  78. })
  79. setPlugins(nextPlugins)
  80. }
  81. }, [setPlugins])
  82. const handleGitHubPluginFetchError = useCallback((index: number) => {
  83. return () => {
  84. setErrorIndexes([...errorIndexes, index])
  85. }
  86. }, [errorIndexes])
  87. const marketPlaceInDSLIndex = useMemo(() => {
  88. const res: number[] = []
  89. allPlugins.forEach((d, index) => {
  90. if (d.type === 'marketplace')
  91. res.push(index)
  92. })
  93. return res
  94. }, [allPlugins])
  95. useEffect(() => {
  96. if (!isFetchingMarketplaceDataById && infoGetById?.data.list) {
  97. const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => {
  98. const p = d as GitHubItemAndMarketPlaceDependency
  99. const id = p.value.marketplace_plugin_unique_identifier?.split(':')[0]
  100. const retPluginInfo = infoGetById.data.list.find(item => item.plugin.plugin_id === id)?.plugin
  101. return { ...retPluginInfo, from: d.type } as Plugin
  102. })
  103. const payloads = sortedList
  104. const failedIndex: number[] = []
  105. const nextPlugins = produce(pluginsRef.current, (draft) => {
  106. marketPlaceInDSLIndex.forEach((index, i) => {
  107. if (payloads[i]) {
  108. draft[index] = {
  109. ...payloads[i],
  110. version: payloads[i]!.version || payloads[i]!.latest_version,
  111. }
  112. }
  113. else { failedIndex.push(index) }
  114. })
  115. })
  116. setPlugins(nextPlugins)
  117. if (failedIndex.length > 0)
  118. setErrorIndexes([...errorIndexes, ...failedIndex])
  119. }
  120. // eslint-disable-next-line react-hooks/exhaustive-deps
  121. }, [isFetchingMarketplaceDataById])
  122. useEffect(() => {
  123. if (!isFetchingDataByMeta && infoByMeta?.data.list) {
  124. const payloads = infoByMeta?.data.list
  125. const failedIndex: number[] = []
  126. const nextPlugins = produce(pluginsRef.current, (draft) => {
  127. marketPlaceInDSLIndex.forEach((index, i) => {
  128. if (payloads[i]) {
  129. const item = payloads[i]
  130. draft[index] = {
  131. ...item.plugin,
  132. plugin_id: item.version.unique_identifier,
  133. }
  134. }
  135. else {
  136. failedIndex.push(index)
  137. }
  138. })
  139. })
  140. setPlugins(nextPlugins)
  141. if (failedIndex.length > 0)
  142. setErrorIndexes([...errorIndexes, ...failedIndex])
  143. }
  144. // eslint-disable-next-line react-hooks/exhaustive-deps
  145. }, [isFetchingDataByMeta])
  146. useEffect(() => {
  147. // get info all failed
  148. if (infoByMetaError || infoByIdError)
  149. setErrorIndexes([...errorIndexes, ...marketPlaceInDSLIndex])
  150. // eslint-disable-next-line react-hooks/exhaustive-deps
  151. }, [infoByMetaError, infoByIdError])
  152. const isLoadedAllData = (plugins.filter(p => !!p).length + errorIndexes.length) === allPlugins.length
  153. const { installedInfo } = useCheckInstalled({
  154. pluginIds: plugins?.filter(p => !!p).map((d) => {
  155. return `${d?.org || d?.author}/${d?.name}`
  156. }) || [],
  157. enabled: isLoadedAllData,
  158. })
  159. const getVersionInfo = useCallback((pluginId: string) => {
  160. const pluginDetail = installedInfo?.[pluginId]
  161. const hasInstalled = !!pluginDetail
  162. return {
  163. hasInstalled,
  164. installedVersion: pluginDetail?.installedVersion,
  165. toInstallVersion: '',
  166. }
  167. }, [installedInfo])
  168. useEffect(() => {
  169. if (isLoadedAllData && installedInfo)
  170. onLoadedAllPlugin(installedInfo!)
  171. // eslint-disable-next-line react-hooks/exhaustive-deps
  172. }, [isLoadedAllData, installedInfo])
  173. const handleSelect = useCallback((index: number) => {
  174. return () => {
  175. const canSelectPlugins = plugins.filter((p) => {
  176. const { canInstall } = pluginInstallLimit(p!, systemFeatures)
  177. return canInstall
  178. })
  179. onSelect(plugins[index]!, index, canSelectPlugins.length)
  180. }
  181. }, [onSelect, plugins, systemFeatures])
  182. useImperativeHandle(ref, () => ({
  183. selectAllPlugins: () => {
  184. const selectedIndexes: number[] = []
  185. const selectedPlugins: Plugin[] = []
  186. allPlugins.forEach((d, index) => {
  187. const p = plugins[index]
  188. if (!p)
  189. return
  190. const { canInstall } = pluginInstallLimit(p, systemFeatures)
  191. if (canInstall) {
  192. selectedIndexes.push(index)
  193. selectedPlugins.push(p)
  194. }
  195. })
  196. onSelectAll(selectedPlugins, selectedIndexes)
  197. },
  198. deSelectAllPlugins: () => {
  199. onDeSelectAll()
  200. },
  201. }))
  202. return (
  203. <>
  204. {allPlugins.map((d, index) => {
  205. if (errorIndexes.includes(index)) {
  206. return (
  207. <LoadingError key={index} />
  208. )
  209. }
  210. const plugin = plugins[index]
  211. if (d.type === 'github') {
  212. return (<GithubItem
  213. key={index}
  214. checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
  215. onCheckedChange={handleSelect(index)}
  216. dependency={d as GitHubItemAndMarketPlaceDependency}
  217. onFetchedPayload={handleGitHubPluginFetched(index)}
  218. onFetchError={handleGitHubPluginFetchError(index)}
  219. versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
  220. />)
  221. }
  222. if (d.type === 'marketplace') {
  223. return (
  224. <MarketplaceItem
  225. key={index}
  226. checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
  227. onCheckedChange={handleSelect(index)}
  228. payload={{ ...plugin, from: d.type } as Plugin}
  229. version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''}
  230. versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
  231. />
  232. )
  233. }
  234. // Local package
  235. return (
  236. <PackageItem
  237. key={index}
  238. checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
  239. onCheckedChange={handleSelect(index)}
  240. payload={d as PackageDependency}
  241. isFromMarketPlace={isFromMarketPlace}
  242. versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
  243. />
  244. )
  245. })
  246. }
  247. </>
  248. )
  249. }
  250. export default React.forwardRef(InstallByDSLList)