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 8.0KB

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