install.tsx 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. 'use client'
  2. import type { FC } from 'react'
  3. import React, { useCallback, useState } from 'react'
  4. import type { Dependency, InstallStatusResponse, Plugin, VersionInfo } from '../../../types'
  5. import Button from '@/app/components/base/button'
  6. import { RiLoader2Line } from '@remixicon/react'
  7. import { useTranslation } from 'react-i18next'
  8. import InstallMulti from './install-multi'
  9. import { useInstallOrUpdate } from '@/service/use-plugins'
  10. import useRefreshPluginList from '../../hooks/use-refresh-plugin-list'
  11. const i18nPrefix = 'plugin.installModal'
  12. type Props = {
  13. allPlugins: Dependency[]
  14. onStartToInstall?: () => void
  15. onInstalled: (plugins: Plugin[], installStatus: InstallStatusResponse[]) => void
  16. onCancel: () => void
  17. isFromMarketPlace?: boolean
  18. isHideButton?: boolean
  19. }
  20. const Install: FC<Props> = ({
  21. allPlugins,
  22. onStartToInstall,
  23. onInstalled,
  24. onCancel,
  25. isFromMarketPlace,
  26. isHideButton,
  27. }) => {
  28. const { t } = useTranslation()
  29. const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([])
  30. const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([])
  31. const selectedPluginsNum = selectedPlugins.length
  32. const { refreshPluginList } = useRefreshPluginList()
  33. const handleSelect = (plugin: Plugin, selectedIndex: number) => {
  34. const isSelected = !!selectedPlugins.find(p => p.plugin_id === plugin.plugin_id)
  35. let nextSelectedPlugins
  36. if (isSelected)
  37. nextSelectedPlugins = selectedPlugins.filter(p => p.plugin_id !== plugin.plugin_id)
  38. else
  39. nextSelectedPlugins = [...selectedPlugins, plugin]
  40. setSelectedPlugins(nextSelectedPlugins)
  41. const nextSelectedIndexes = isSelected ? selectedIndexes.filter(i => i !== selectedIndex) : [...selectedIndexes, selectedIndex]
  42. setSelectedIndexes(nextSelectedIndexes)
  43. }
  44. const [canInstall, setCanInstall] = React.useState(false)
  45. const [installedInfo, setInstalledInfo] = useState<Record<string, VersionInfo> | undefined>(undefined)
  46. const handleLoadedAllPlugin = useCallback((installedInfo: Record<string, VersionInfo> | undefined) => {
  47. setInstalledInfo(installedInfo)
  48. setCanInstall(true)
  49. }, [])
  50. // Install from marketplace and github
  51. const { mutate: installOrUpdate, isPending: isInstalling } = useInstallOrUpdate({
  52. onSuccess: (res: InstallStatusResponse[]) => {
  53. onInstalled(selectedPlugins, res.map((r, i) => {
  54. return ({
  55. ...r,
  56. isFromMarketPlace: allPlugins[selectedIndexes[i]].type === 'marketplace',
  57. })
  58. }))
  59. const hasInstallSuccess = res.some(r => r.success)
  60. if (hasInstallSuccess)
  61. refreshPluginList(undefined, true)
  62. },
  63. })
  64. const handleInstall = () => {
  65. onStartToInstall?.()
  66. installOrUpdate({
  67. payload: allPlugins.filter((_d, index) => selectedIndexes.includes(index)),
  68. plugin: selectedPlugins,
  69. installedInfo: installedInfo!,
  70. })
  71. }
  72. return (
  73. <>
  74. <div className='flex flex-col px-6 py-3 justify-center items-start gap-4 self-stretch'>
  75. <div className='text-text-secondary system-md-regular'>
  76. <p>{t(`${i18nPrefix}.${selectedPluginsNum > 1 ? 'readyToInstallPackages' : 'readyToInstallPackage'}`, { num: selectedPluginsNum })}</p>
  77. </div>
  78. <div className='w-full p-2 rounded-2xl bg-background-section-burn space-y-1'>
  79. <InstallMulti
  80. allPlugins={allPlugins}
  81. selectedPlugins={selectedPlugins}
  82. onSelect={handleSelect}
  83. onLoadedAllPlugin={handleLoadedAllPlugin}
  84. isFromMarketPlace={isFromMarketPlace}
  85. />
  86. </div>
  87. </div>
  88. {/* Action Buttons */}
  89. {!isHideButton && (
  90. <div className='flex p-6 pt-5 justify-end items-center gap-2 self-stretch'>
  91. {!canInstall && (
  92. <Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
  93. {t('common.operation.cancel')}
  94. </Button>
  95. )}
  96. <Button
  97. variant='primary'
  98. className='min-w-[72px] flex space-x-0.5'
  99. disabled={!canInstall || isInstalling || selectedPlugins.length === 0}
  100. onClick={handleInstall}
  101. >
  102. {isInstalling && <RiLoader2Line className='w-4 h-4 animate-spin-slow' />}
  103. <span>{t(`${i18nPrefix}.${isInstalling ? 'installing' : 'install'}`)}</span>
  104. </Button>
  105. </div>
  106. )}
  107. </>
  108. )
  109. }
  110. export default React.memo(Install)