mirror of
https://github.com/langgenius/dify.git
synced 2026-10-28 03:35:04 +08:00
refactor(web): drop headless-ui, migrate overlay to dify-ui (#35963)
Co-authored-by: CodingOnStar <hanxujiang@dify.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
This commit is contained in:
co-authored by
CodingOnStar
autofix-ci[bot]
yyh
yyh
parent
f720a3bed2
commit
8581a68174
@@ -2,10 +2,10 @@
|
||||
import type { FC } from 'react'
|
||||
import type { Dependency } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { InstallStep } from '../../types'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
import ReadyToInstall from './ready-to-install'
|
||||
@@ -50,26 +50,31 @@ const InstallBundle: FC<Props> = ({
|
||||
}, [step, t])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
className={cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0')}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
<DialogContent className={cn('relative w-full max-w-[480px] overflow-hidden! text-left align-middle', cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ReadyToInstall
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
allPlugins={fromDSLPayload}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</Modal>
|
||||
<ReadyToInstall
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
allPlugins={fromDSLPayload}
|
||||
onClose={onClose}
|
||||
/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import type { PluginDeclaration, UpdateFromGitHubPayload } from '../../types'
|
||||
import type { InstallState } from '@/app/components/plugins/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { toast } from '@langgenius/dify-ui/toast'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
import { InstallStepFromGitHub } from '../../types'
|
||||
import Installed from '../base/installed'
|
||||
@@ -160,74 +160,80 @@ const InstallFromGitHub: React.FC<InstallFromGitHubProps> = ({ updatePayload, on
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
className={cn(modalClassName, `shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px]
|
||||
border-components-panel-border bg-components-panel-bg p-0`)}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="flex grow flex-col items-start gap-1">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
</div>
|
||||
<div className="self-stretch system-xs-regular text-text-tertiary">
|
||||
{!([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step)) && t('installFromGitHub.installNote', { ns: 'plugin' })}
|
||||
<DialogContent className={cn('w-[560px] max-w-none! overflow-hidden! text-left align-middle', cn(modalClassName, `shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px]
|
||||
border-components-panel-border bg-components-panel-bg p-0`))}
|
||||
>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="flex grow flex-col items-start gap-1">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
</div>
|
||||
<div className="self-stretch system-xs-regular text-text-tertiary">
|
||||
{!([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step)) && t('installFromGitHub.installNote', { ns: 'plugin' })}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
|
||||
? (
|
||||
<Installed
|
||||
payload={manifest}
|
||||
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
|
||||
errMsg={errorMsg}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={`flex flex-col items-start justify-center self-stretch px-6 py-3 ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
|
||||
{state.step === InstallStepFromGitHub.setUrl && (
|
||||
<SetURL
|
||||
repoUrl={state.repoUrl}
|
||||
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
||||
onNext={handleUrlSubmit}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.selectPackage && (
|
||||
<SelectPackage
|
||||
updatePayload={updatePayload!}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
versions={versions}
|
||||
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: String(item.value) }))}
|
||||
selectedPackage={state.selectedPackage}
|
||||
packages={packages}
|
||||
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: String(item.value) }))}
|
||||
onUploaded={handleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.readyToInstall && (
|
||||
<Loaded
|
||||
updatePayload={updatePayload!}
|
||||
uniqueIdentifier={uniqueIdentifier!}
|
||||
payload={manifest as any}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
selectedPackage={state.selectedPackage}
|
||||
onBack={handleBack}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
{([InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installed, InstallStepFromGitHub.installFailed].includes(state.step))
|
||||
? (
|
||||
<Installed
|
||||
payload={manifest}
|
||||
isFailed={[InstallStepFromGitHub.uploadFailed, InstallStepFromGitHub.installFailed].includes(state.step)}
|
||||
errMsg={errorMsg}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div className={`flex flex-col items-start justify-center self-stretch px-6 py-3 ${state.step === InstallStepFromGitHub.installed ? 'gap-2' : 'gap-4'}`}>
|
||||
{state.step === InstallStepFromGitHub.setUrl && (
|
||||
<SetURL
|
||||
repoUrl={state.repoUrl}
|
||||
onChange={value => setState(prevState => ({ ...prevState, repoUrl: value }))}
|
||||
onNext={handleUrlSubmit}
|
||||
onCancel={onClose}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.selectPackage && (
|
||||
<SelectPackage
|
||||
updatePayload={updatePayload!}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
versions={versions}
|
||||
onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: String(item.value) }))}
|
||||
selectedPackage={state.selectedPackage}
|
||||
packages={packages}
|
||||
onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: String(item.value) }))}
|
||||
onUploaded={handleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
)}
|
||||
{state.step === InstallStepFromGitHub.readyToInstall && (
|
||||
<Loaded
|
||||
updatePayload={updatePayload!}
|
||||
uniqueIdentifier={uniqueIdentifier!}
|
||||
payload={manifest as any}
|
||||
repoUrl={state.repoUrl}
|
||||
selectedVersion={state.selectedVersion}
|
||||
selectedPackage={state.selectedPackage}
|
||||
onBack={handleBack}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import type { Dependency, PluginDeclaration } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon'
|
||||
import { InstallStep } from '../../types'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
@@ -86,52 +86,57 @@ const InstallFromLocalPackage: React.FC<InstallFromLocalPackageProps> = ({
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
className={cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0')}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
<DialogContent className={cn('w-[560px] max-w-none! overflow-hidden! text-left align-middle', cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{step === InstallStep.uploading && (
|
||||
<Uploading
|
||||
isBundle={isBundle}
|
||||
file={file}
|
||||
onCancel={onClose}
|
||||
onPackageUploaded={handlePackageUploaded}
|
||||
onBundleUploaded={handleBundleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
/>
|
||||
)}
|
||||
{isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ReadyToInstallPackage
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
manifest={manifest}
|
||||
errorMsg={errorMsg}
|
||||
onError={setErrorMsg}
|
||||
/>
|
||||
)}
|
||||
</Modal>
|
||||
{step === InstallStep.uploading && (
|
||||
<Uploading
|
||||
isBundle={isBundle}
|
||||
file={file}
|
||||
onCancel={onClose}
|
||||
onPackageUploaded={handlePackageUploaded}
|
||||
onBundleUploaded={handleBundleUploaded}
|
||||
onFailed={handleUploadFail}
|
||||
/>
|
||||
)}
|
||||
{isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<ReadyToInstallPackage
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
manifest={manifest}
|
||||
errorMsg={errorMsg}
|
||||
onError={setErrorMsg}
|
||||
/>
|
||||
)}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+70
-17
@@ -161,11 +161,9 @@ describe('Uploading', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// NOTE: The uploadFile API has an unconventional contract where it always rejects.
|
||||
// Success vs failure is determined by whether response.message exists:
|
||||
// - If response.message exists → treated as failure (calls onFailed)
|
||||
// - If response.message is absent → treated as success (calls onPackageUploaded/onBundleUploaded)
|
||||
// This explains why we use mockRejectedValue for "success" scenarios below.
|
||||
// NOTE: Some upload endpoints have historically returned successful plugin upload
|
||||
// payloads through rejected XHR objects, so the component accepts both resolved
|
||||
// responses and rejected responses without an error message.
|
||||
|
||||
it('should call onPackageUploaded when upload rejects without error message (success case)', async () => {
|
||||
const mockResult = {
|
||||
@@ -193,6 +191,30 @@ describe('Uploading', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onPackageUploaded when upload resolves with package response', async () => {
|
||||
const mockResult = {
|
||||
unique_identifier: 'test-uid',
|
||||
manifest: createMockManifest(),
|
||||
}
|
||||
mockUploadFile.mockResolvedValue(mockResult)
|
||||
|
||||
const onPackageUploaded = vi.fn()
|
||||
render(
|
||||
<Uploading
|
||||
{...defaultProps}
|
||||
isBundle={false}
|
||||
onPackageUploaded={onPackageUploaded}
|
||||
/>,
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPackageUploaded).toHaveBeenCalledWith({
|
||||
uniqueIdentifier: mockResult.unique_identifier,
|
||||
manifest: mockResult.manifest,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onBundleUploaded when upload rejects without error message (success case)', async () => {
|
||||
const mockDependencies = createMockDependencies()
|
||||
mockUploadFile.mockRejectedValue({
|
||||
@@ -212,6 +234,24 @@ describe('Uploading', () => {
|
||||
expect(onBundleUploaded).toHaveBeenCalledWith(mockDependencies)
|
||||
})
|
||||
})
|
||||
|
||||
it('should call onBundleUploaded when upload resolves with bundle response', async () => {
|
||||
const mockDependencies = createMockDependencies()
|
||||
mockUploadFile.mockResolvedValue(mockDependencies)
|
||||
|
||||
const onBundleUploaded = vi.fn()
|
||||
render(
|
||||
<Uploading
|
||||
{...defaultProps}
|
||||
isBundle
|
||||
onBundleUploaded={onBundleUploaded}
|
||||
/>,
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onBundleUploaded).toHaveBeenCalledWith(mockDependencies)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
@@ -260,35 +300,48 @@ describe('Uploading', () => {
|
||||
// Edge Cases Tests
|
||||
// ================================
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty response gracefully', async () => {
|
||||
it('should fail gracefully when upload response is empty', async () => {
|
||||
mockUploadFile.mockRejectedValue({
|
||||
response: {},
|
||||
})
|
||||
|
||||
const onPackageUploaded = vi.fn()
|
||||
render(<Uploading {...defaultProps} onPackageUploaded={onPackageUploaded} />)
|
||||
const onFailed = vi.fn()
|
||||
render(<Uploading {...defaultProps} onPackageUploaded={onPackageUploaded} onFailed={onFailed} />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPackageUploaded).toHaveBeenCalledWith({
|
||||
uniqueIdentifier: undefined,
|
||||
manifest: undefined,
|
||||
})
|
||||
expect(onPackageUploaded).not.toHaveBeenCalled()
|
||||
expect(onFailed).toHaveBeenCalledWith('plugin.installModal.uploadFailed')
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle response with only unique_identifier', async () => {
|
||||
it('should fail gracefully when upload response has no manifest', async () => {
|
||||
mockUploadFile.mockRejectedValue({
|
||||
response: { unique_identifier: 'only-uid' },
|
||||
})
|
||||
|
||||
const onPackageUploaded = vi.fn()
|
||||
render(<Uploading {...defaultProps} onPackageUploaded={onPackageUploaded} />)
|
||||
const onFailed = vi.fn()
|
||||
render(<Uploading {...defaultProps} onPackageUploaded={onPackageUploaded} onFailed={onFailed} />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPackageUploaded).toHaveBeenCalledWith({
|
||||
uniqueIdentifier: 'only-uid',
|
||||
manifest: undefined,
|
||||
})
|
||||
expect(onPackageUploaded).not.toHaveBeenCalled()
|
||||
expect(onFailed).toHaveBeenCalledWith('plugin.installModal.uploadFailed')
|
||||
})
|
||||
})
|
||||
|
||||
it('should fail gracefully when upload response is null', async () => {
|
||||
mockUploadFile.mockRejectedValue({
|
||||
response: null,
|
||||
})
|
||||
|
||||
const onPackageUploaded = vi.fn()
|
||||
const onFailed = vi.fn()
|
||||
render(<Uploading {...defaultProps} onPackageUploaded={onPackageUploaded} onFailed={onFailed} />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onPackageUploaded).not.toHaveBeenCalled()
|
||||
expect(onFailed).toHaveBeenCalledWith('plugin.installModal.uploadFailed')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+69
-22
@@ -1,8 +1,7 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { Dependency, PluginDeclaration } from '../../../types'
|
||||
import type { Dependency, Plugin, PluginDeclaration } from '../../../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { uploadFile } from '@/service/plugins'
|
||||
@@ -10,6 +9,40 @@ import Card from '../../../card'
|
||||
|
||||
const i18nPrefix = 'installModal'
|
||||
|
||||
type PackageUploadResponse = {
|
||||
unique_identifier: string
|
||||
manifest: PluginDeclaration
|
||||
}
|
||||
|
||||
type UploadFailureResponse = {
|
||||
message?: string
|
||||
}
|
||||
|
||||
function isObject(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null
|
||||
}
|
||||
|
||||
function isPackageUploadResponse(value: unknown): value is PackageUploadResponse {
|
||||
if (!isObject(value))
|
||||
return false
|
||||
|
||||
return typeof value.unique_identifier === 'string' && isObject(value.manifest)
|
||||
}
|
||||
|
||||
function getRejectedResponse(error: unknown): unknown {
|
||||
if (!isObject(error) || !('response' in error))
|
||||
return undefined
|
||||
|
||||
return error.response
|
||||
}
|
||||
|
||||
function getUploadFailureMessage(response: unknown): string | undefined {
|
||||
if (!isObject(response))
|
||||
return undefined
|
||||
|
||||
return (response as UploadFailureResponse).message
|
||||
}
|
||||
|
||||
type Props = {
|
||||
isBundle: boolean
|
||||
file: File
|
||||
@@ -32,36 +65,50 @@ const Uploading: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const fileName = file.name
|
||||
const handleUpload = async () => {
|
||||
const handleUploadedResponse = React.useCallback((response: unknown) => {
|
||||
if (isBundle) {
|
||||
if (Array.isArray(response)) {
|
||||
onBundleUploaded(response as Dependency[])
|
||||
return
|
||||
}
|
||||
onFailed(t(`${i18nPrefix}.uploadFailed`, { ns: 'plugin' }))
|
||||
return
|
||||
}
|
||||
|
||||
if (!isPackageUploadResponse(response)) {
|
||||
onFailed(t(`${i18nPrefix}.uploadFailed`, { ns: 'plugin' }))
|
||||
return
|
||||
}
|
||||
|
||||
onPackageUploaded({
|
||||
uniqueIdentifier: response.unique_identifier,
|
||||
manifest: response.manifest,
|
||||
})
|
||||
}, [isBundle, onBundleUploaded, onFailed, onPackageUploaded, t])
|
||||
|
||||
const handleUpload = React.useCallback(async () => {
|
||||
try {
|
||||
await uploadFile(file, isBundle)
|
||||
handleUploadedResponse(await uploadFile(file, isBundle))
|
||||
}
|
||||
catch (e: any) {
|
||||
if (e.response?.message) {
|
||||
onFailed(e.response?.message)
|
||||
}
|
||||
else { // Why it would into this branch?
|
||||
const res = e.response
|
||||
if (isBundle) {
|
||||
onBundleUploaded(res)
|
||||
return
|
||||
}
|
||||
onPackageUploaded({
|
||||
uniqueIdentifier: res.unique_identifier,
|
||||
manifest: res.manifest,
|
||||
})
|
||||
catch (error: unknown) {
|
||||
const response = getRejectedResponse(error)
|
||||
const message = getUploadFailureMessage(response)
|
||||
if (message) {
|
||||
onFailed(message)
|
||||
return
|
||||
}
|
||||
handleUploadedResponse(response)
|
||||
}
|
||||
}
|
||||
}, [file, handleUploadedResponse, isBundle, onFailed])
|
||||
|
||||
React.useEffect(() => {
|
||||
handleUpload()
|
||||
}, [])
|
||||
}, [handleUpload])
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
||||
<div className="flex items-center gap-1 self-stretch">
|
||||
<RiLoader2Line className="h-4 w-4 animate-spin-slow text-text-accent" />
|
||||
<span className="i-ri-loader-2-line h-4 w-4 animate-spin-slow text-text-accent" />
|
||||
<div className="system-md-regular text-text-secondary">
|
||||
{t(`${i18nPrefix}.uploadingPackage`, {
|
||||
ns: 'plugin',
|
||||
@@ -72,7 +119,7 @@ const Uploading: FC<Props> = ({
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
className="w-full"
|
||||
payload={{ name: fileName } as any}
|
||||
payload={{ name: fileName } as Plugin}
|
||||
isLoading
|
||||
loadingFileName={fileName}
|
||||
installed={false}
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
import type { Dependency, Plugin, PluginManifestInMarket } from '../../types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { InstallStep } from '../../types'
|
||||
import Installed from '../base/installed'
|
||||
import useHideLogic from '../hooks/use-hide-logic'
|
||||
@@ -70,60 +70,64 @@ const InstallFromMarketplace: React.FC<InstallFromMarketplaceProps> = ({
|
||||
}, [setIsInstalling])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={foldAnimInto}
|
||||
wrapperClassName="z-9999"
|
||||
className={cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0')}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
foldAnimInto()
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
<DialogContent className={cn('w-[560px] max-w-none! overflow-hidden! text-left align-middle', cn(modalClassName, 'shadows-shadow-xl flex min-w-[560px] flex-col items-start rounded-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg p-0'))}>
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<div className="self-stretch title-2xl-semi-bold text-text-primary">
|
||||
{getTitle()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies!}
|
||||
isFromMarketPlace
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{
|
||||
step === InstallStep.readyToInstall && (
|
||||
<Install
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
payload={manifest!}
|
||||
onCancel={onClose}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
|
||||
<Installed
|
||||
payload={manifest!}
|
||||
isMarketPayload
|
||||
isFailed={step === InstallStep.installFailed}
|
||||
errMsg={errorMsg}
|
||||
onCancel={onSuccess}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</Modal>
|
||||
{
|
||||
isBundle
|
||||
? (
|
||||
<ReadyToInstallBundle
|
||||
step={step}
|
||||
onStepChange={setStep}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
setIsInstalling={setIsInstalling}
|
||||
onClose={onClose}
|
||||
allPlugins={dependencies!}
|
||||
isFromMarketPlace
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<>
|
||||
{
|
||||
step === InstallStep.readyToInstall && (
|
||||
<Install
|
||||
uniqueIdentifier={uniqueIdentifier}
|
||||
payload={manifest!}
|
||||
onCancel={onClose}
|
||||
onInstalled={handleInstalled}
|
||||
onFailed={handleFailed}
|
||||
onStartToInstall={handleStartToInstall}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
[InstallStep.installed, InstallStep.installFailed].includes(step) && (
|
||||
<Installed
|
||||
payload={manifest!}
|
||||
isMarketPayload
|
||||
isFailed={step === InstallStep.installFailed}
|
||||
errMsg={errorMsg}
|
||||
onCancel={onSuccess}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+5
-8
@@ -2,14 +2,11 @@ import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import SchemaModal from '../schema-modal'
|
||||
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({
|
||||
children,
|
||||
isShow,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
isShow: boolean
|
||||
}) => isShow ? <div data-testid="modal">{children}</div> : null,
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) =>
|
||||
open === false ? null : <>{children}</>,
|
||||
DialogContent: ({ children }: { children: React.ReactNode }) => <div data-testid="modal">{children}</div>,
|
||||
DialogTitle: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor', () => ({
|
||||
|
||||
@@ -2,9 +2,9 @@ import type { UseMutationResult } from '@tanstack/react-query'
|
||||
import type { FC, ReactNode } from 'react'
|
||||
import type { Plugin } from '../types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { memo } from 'react'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import Card from '@/app/components/plugins/card'
|
||||
|
||||
type Props = {
|
||||
@@ -33,45 +33,52 @@ const PluginMutationModal: FC<Props> = ({
|
||||
modalBottomLeft,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isShow={true}
|
||||
onClose={onCancel}
|
||||
className="min-w-[560px]"
|
||||
closable
|
||||
title={modelTitle}
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onCancel()
|
||||
}}
|
||||
>
|
||||
<div className="mt-3 mb-2 system-md-regular text-text-secondary">
|
||||
{description}
|
||||
</div>
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
installed={mutation.isSuccess}
|
||||
payload={plugin}
|
||||
className="w-full"
|
||||
titleLeft={cardTitleLeft}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 self-stretch pt-5">
|
||||
<div>
|
||||
{modalBottomLeft}
|
||||
<DialogContent className="w-full min-w-[560px] overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{modelTitle}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-3 mb-2 system-md-regular text-text-secondary">
|
||||
{description}
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
{!mutation.isPending && (
|
||||
<Button onClick={onCancel}>
|
||||
{cancelButtonText}
|
||||
<div className="flex flex-wrap content-start items-start gap-1 self-stretch rounded-2xl bg-background-section-burn p-2">
|
||||
<Card
|
||||
installed={mutation.isSuccess}
|
||||
payload={plugin}
|
||||
className="w-full"
|
||||
titleLeft={cardTitleLeft}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 self-stretch pt-5">
|
||||
<div>
|
||||
{modalBottomLeft}
|
||||
</div>
|
||||
<div className="ml-auto flex gap-2">
|
||||
{!mutation.isPending && (
|
||||
<Button onClick={onCancel}>
|
||||
{cancelButtonText}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={mutation.isPending}
|
||||
onClick={mutate}
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{confirmButtonText}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="primary"
|
||||
loading={mutation.isPending}
|
||||
onClick={mutate}
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{confirmButtonText}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,19 @@ import { render, screen } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
vi.mock('../../../base/modal', () => ({
|
||||
default: ({ children, title, isShow }: { children: React.ReactNode, title: string, isShow: boolean }) => (
|
||||
isShow
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open }: { children: React.ReactNode, open?: boolean }) => (
|
||||
open !== false
|
||||
? (
|
||||
<div data-testid="modal">
|
||||
<div data-testid="modal-title">{title}</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
: null
|
||||
),
|
||||
DialogContent: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
DialogTitle: ({ children }: { children: React.ReactNode }) => <div data-testid="modal-title">{children}</div>,
|
||||
DialogCloseButton: () => <button type="button">Close</button>,
|
||||
}))
|
||||
|
||||
vi.mock('../../base/key-value-item', () => ({
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgenius/dify-ui/dialog'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '../../base/modal'
|
||||
import KeyValueItem from '../base/key-value-item'
|
||||
import { convertRepoToUrl } from '../install-plugin/utils'
|
||||
|
||||
@@ -23,19 +23,26 @@ const PlugInfo: FC<Props> = ({
|
||||
const { t } = useTranslation()
|
||||
const labelWidthClassName = 'w-[96px]'
|
||||
return (
|
||||
<Modal
|
||||
title={t(`${i18nPrefix}.title`, { ns: 'plugin' })}
|
||||
className="w-[480px]"
|
||||
isShow
|
||||
onClose={onHide}
|
||||
closable
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div className="mt-5 space-y-3">
|
||||
{repository && <KeyValueItem label={t(`${i18nPrefix}.repository`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={`${convertRepoToUrl(repository)}`} valueMaxWidthClassName="max-w-[190px]" />}
|
||||
{release && <KeyValueItem label={t(`${i18nPrefix}.release`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={release} />}
|
||||
{packageName && <KeyValueItem label={t(`${i18nPrefix}.packageName`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={packageName} />}
|
||||
</div>
|
||||
</Modal>
|
||||
<DialogContent className="w-full max-w-[480px]! overflow-hidden! border-none text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
<DialogTitle className="title-2xl-semi-bold text-text-primary">
|
||||
{t(`${i18nPrefix}.title`, { ns: 'plugin' })}
|
||||
</DialogTitle>
|
||||
|
||||
<div className="mt-5 space-y-3">
|
||||
{repository && <KeyValueItem label={t(`${i18nPrefix}.repository`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={`${convertRepoToUrl(repository)}`} valueMaxWidthClassName="max-w-[190px]" />}
|
||||
{release && <KeyValueItem label={t(`${i18nPrefix}.release`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={release} />}
|
||||
{packageName && <KeyValueItem label={t(`${i18nPrefix}.packageName`, { ns: 'plugin' })} labelWidthClassName={labelWidthClassName} value={packageName} />}
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
export default React.memo(PlugInfo)
|
||||
|
||||
@@ -14,28 +14,25 @@ const mockSystemFeatures = { enable_marketplace: true }
|
||||
const render = (ui: ReactElement) =>
|
||||
renderWithSystemFeatures(ui, { systemFeatures: mockSystemFeatures })
|
||||
|
||||
// Mock Modal component
|
||||
vi.mock('@/app/components/base/modal', () => ({
|
||||
default: ({ children, isShow, onClose, closable, className }: {
|
||||
let mockDialogOnOpenChange: ((open: boolean) => void) | undefined
|
||||
|
||||
vi.mock('@langgenius/dify-ui/dialog', () => ({
|
||||
Dialog: ({ children, open, onOpenChange }: {
|
||||
children: React.ReactNode
|
||||
isShow: boolean
|
||||
onClose: () => void
|
||||
closable?: boolean
|
||||
className?: string
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}) => {
|
||||
if (!isShow)
|
||||
return null
|
||||
return (
|
||||
<div data-testid="modal" className={className}>
|
||||
{closable && (
|
||||
<button data-testid="modal-close" onClick={onClose}>
|
||||
Close
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
mockDialogOnOpenChange = onOpenChange
|
||||
return open === false ? null : <>{children}</>
|
||||
},
|
||||
DialogContent: ({ children, className }: { children: React.ReactNode, className?: string }) => (
|
||||
<div data-testid="modal" className={className}>{children}</div>
|
||||
),
|
||||
DialogCloseButton: () => (
|
||||
<button data-testid="modal-close" onClick={() => mockDialogOnOpenChange?.(false)}>
|
||||
Close
|
||||
</button>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock OptionCard component
|
||||
|
||||
@@ -143,7 +143,6 @@ const AutoUpdateSetting: FC<Props> = ({
|
||||
timezone={timezone}
|
||||
onChange={v => handleChange('upgrade_time_of_day')(convertLocalSecondsToUTCDaySeconds(dayjsToTimeOfDay(v), timezone!))}
|
||||
onClear={() => handleChange('upgrade_time_of_day')(convertLocalSecondsToUTCDaySeconds(0, timezone!))}
|
||||
popupClassName="z-99"
|
||||
title={t(`${i18nPrefix}.updateTime`, { ns: 'plugin' })}
|
||||
minuteFilter={minuteFilter}
|
||||
renderTrigger={renderTimePickerTrigger}
|
||||
|
||||
@@ -3,11 +3,11 @@ import type { FC } from 'react'
|
||||
import type { AutoUpdateConfig } from './auto-update-setting/types'
|
||||
import type { Permissions, ReferenceSetting } from '@/app/components/plugins/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { Dialog, DialogCloseButton, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Modal from '@/app/components/base/modal'
|
||||
import { PermissionType } from '@/app/components/plugins/types'
|
||||
import OptionCard from '@/app/components/workflow/nodes/_base/components/option-card'
|
||||
import { systemFeaturesQueryOptions } from '@/service/system-features'
|
||||
@@ -53,59 +53,64 @@ const PluginSettingModal: FC<Props> = ({
|
||||
}, [onHide, onSave, tempAutoUpdateConfig, tempPrivilege])
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isShow
|
||||
onClose={onHide}
|
||||
closable
|
||||
className="w-[620px] max-w-[620px] p-0!"
|
||||
<Dialog
|
||||
open
|
||||
onOpenChange={(open) => {
|
||||
if (!open)
|
||||
onHide()
|
||||
}}
|
||||
>
|
||||
<div className="shadows-shadow-xl flex w-full flex-col items-start rounded-2xl border border-components-panel-border bg-components-panel-bg">
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<span className="self-stretch title-2xl-semi-bold text-text-primary">{t(`${i18nPrefix}.title`, { ns: 'plugin' })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
||||
{[
|
||||
{ title: t(`${i18nPrefix}.whoCanInstall`, { ns: 'plugin' }), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
|
||||
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
||||
].map(({ title, key, value }) => (
|
||||
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
||||
<Label label={title} />
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
||||
<OptionCard
|
||||
key={option}
|
||||
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
||||
onSelect={() => handlePrivilegeChange(key)(option)}
|
||||
selected={value === option}
|
||||
className="flex-1"
|
||||
/>
|
||||
))}
|
||||
<DialogContent className="w-[620px] max-w-[620px] overflow-hidden! border-none p-0! text-left align-middle">
|
||||
<DialogCloseButton data-testid="modal-close-button" />
|
||||
|
||||
<div className="shadows-shadow-xl flex w-full flex-col items-start rounded-2xl border border-components-panel-border bg-components-panel-bg">
|
||||
<div className="flex items-start gap-2 self-stretch pt-6 pr-14 pb-3 pl-6">
|
||||
<span className="self-stretch title-2xl-semi-bold text-text-primary">{t(`${i18nPrefix}.title`, { ns: 'plugin' })}</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3">
|
||||
{[
|
||||
{ title: t(`${i18nPrefix}.whoCanInstall`, { ns: 'plugin' }), key: 'install_permission', value: tempPrivilege?.install_permission || PermissionType.noOne },
|
||||
{ title: t(`${i18nPrefix}.whoCanDebug`, { ns: 'plugin' }), key: 'debug_permission', value: tempPrivilege?.debug_permission || PermissionType.noOne },
|
||||
].map(({ title, key, value }) => (
|
||||
<div key={key} className="flex flex-col items-start gap-1 self-stretch">
|
||||
<Label label={title} />
|
||||
<div className="flex w-full items-start justify-between gap-2">
|
||||
{[PermissionType.everyone, PermissionType.admin, PermissionType.noOne].map(option => (
|
||||
<OptionCard
|
||||
key={option}
|
||||
title={t(`${i18nPrefix}.${option}`, { ns: 'plugin' })}
|
||||
onSelect={() => handlePrivilegeChange(key)(option)}
|
||||
selected={value === option}
|
||||
className="flex-1"
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
{
|
||||
enable_marketplace && (
|
||||
<AutoUpdateSetting payload={tempAutoUpdateConfig} onChange={setTempAutoUpdateConfig} />
|
||||
)
|
||||
}
|
||||
<div className="flex h-[76px] items-center justify-end gap-2 self-stretch p-6 pt-5">
|
||||
<Button
|
||||
className="min-w-[72px]"
|
||||
onClick={onHide}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
className="min-w-[72px]"
|
||||
variant="primary"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
enable_marketplace && (
|
||||
<AutoUpdateSetting payload={tempAutoUpdateConfig} onChange={setTempAutoUpdateConfig} />
|
||||
)
|
||||
}
|
||||
<div className="flex h-[76px] items-center justify-end gap-2 self-stretch p-6 pt-5">
|
||||
<Button
|
||||
className="min-w-[72px]"
|
||||
onClick={onHide}
|
||||
>
|
||||
{t('operation.cancel', { ns: 'common' })}
|
||||
</Button>
|
||||
<Button
|
||||
className="min-w-[72px]"
|
||||
variant="primary"
|
||||
onClick={handleSave}
|
||||
>
|
||||
{t('operation.save', { ns: 'common' })}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user