This commit is contained in:
2025-12-01 17:21:38 +08:00
parent 32fee2b8ab
commit fab8c13cb3
7511 changed files with 996300 additions and 0 deletions

View File

@@ -0,0 +1,69 @@
import React, { useCallback, useEffect, useMemo } from 'react'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type { DataSourceCredential } from '@/types/pipeline'
import { useBoolean } from 'ahooks'
import Trigger from './trigger'
import List from './list'
export type CredentialSelectorProps = {
pluginName: string
currentCredentialId: string
onCredentialChange: (credentialId: string) => void
credentials: Array<DataSourceCredential>
}
const CredentialSelector = ({
pluginName,
currentCredentialId,
onCredentialChange,
credentials,
}: CredentialSelectorProps) => {
const [open, { toggle }] = useBoolean(false)
const currentCredential = useMemo(() => {
return credentials.find(cred => cred.id === currentCredentialId)
}, [credentials, currentCredentialId])
useEffect(() => {
if (!currentCredential && credentials.length)
onCredentialChange(credentials[0].id)
}, [currentCredential, credentials])
const handleCredentialChange = useCallback((credentialId: string) => {
onCredentialChange(credentialId)
toggle()
}, [onCredentialChange, toggle])
return (
<PortalToFollowElem
open={open}
onOpenChange={toggle}
placement='bottom-start'
offset={{
mainAxis: 4,
}}
>
<PortalToFollowElemTrigger onClick={toggle} className='grow overflow-hidden'>
<Trigger
currentCredential={currentCredential}
pluginName={pluginName}
isOpen={open}
/>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-10'>
<List
currentCredentialId={currentCredentialId}
credentials={credentials}
pluginName={pluginName}
onCredentialChange={handleCredentialChange}
/>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default React.memo(CredentialSelector)

View File

@@ -0,0 +1,52 @@
import { CredentialIcon } from '@/app/components/datasets/common/credential-icon'
import type { DataSourceCredential } from '@/types/pipeline'
import { RiCheckLine } from '@remixicon/react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
type ItemProps = {
credential: DataSourceCredential
pluginName: string
isSelected: boolean
onCredentialChange: (credentialId: string) => void
}
const Item = ({
credential,
pluginName,
isSelected,
onCredentialChange,
}: ItemProps) => {
const { t } = useTranslation()
const { avatar_url, name } = credential
const handleCredentialChange = useCallback(() => {
onCredentialChange(credential.id)
}, [credential.id, onCredentialChange])
return (
<div
className='flex cursor-pointer items-center gap-x-2 rounded-lg p-2 hover:bg-state-base-hover'
onClick={handleCredentialChange}
>
<CredentialIcon
avatar_url={avatar_url}
name={name}
size={20}
/>
<span className='system-sm-medium grow truncate text-text-secondary'>
{t('datasetPipeline.credentialSelector.name', {
credentialName: name,
pluginName,
})}
</span>
{
isSelected && (
<RiCheckLine className='size-4 shrink-0 text-text-accent' />
)
}
</div>
)
}
export default React.memo(Item)

View File

@@ -0,0 +1,38 @@
import type { DataSourceCredential } from '@/types/pipeline'
import React from 'react'
import Item from './item'
type ListProps = {
currentCredentialId: string
credentials: Array<DataSourceCredential>
pluginName: string
onCredentialChange: (credentialId: string) => void
}
const List = ({
currentCredentialId,
credentials,
pluginName,
onCredentialChange,
}: ListProps) => {
return (
<div className='flex w-[280px] flex-col gap-y-1 rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg shadow-shadow-shadow-5 backdrop-blur-[5px]'>
{
credentials.map((credential) => {
const isSelected = credential.id === currentCredentialId
return (
<Item
key={credential.id}
credential={credential}
pluginName={pluginName}
isSelected={isSelected}
onCredentialChange={onCredentialChange}
/>
)
})
}
</div>
)
}
export default React.memo(List)

View File

@@ -0,0 +1,51 @@
import React from 'react'
import type { DataSourceCredential } from '@/types/pipeline'
import { useTranslation } from 'react-i18next'
import { RiArrowDownSLine } from '@remixicon/react'
import cn from '@/utils/classnames'
import { CredentialIcon } from '@/app/components/datasets/common/credential-icon'
type TriggerProps = {
currentCredential: DataSourceCredential | undefined
pluginName: string
isOpen: boolean
}
const Trigger = ({
currentCredential,
pluginName,
isOpen,
}: TriggerProps) => {
const { t } = useTranslation()
const {
avatar_url,
name = '',
} = currentCredential || {}
return (
<div
className={cn(
'flex cursor-pointer items-center gap-x-2 rounded-md p-1 pr-2',
isOpen ? 'bg-state-base-hover' : 'hover:bg-state-base-hover',
)}
>
<CredentialIcon
avatar_url={avatar_url}
name={name}
size={20}
/>
<div className='flex grow items-center gap-x-1 overflow-hidden'>
<span className='system-md-semibold grow truncate text-text-secondary'>
{t('datasetPipeline.credentialSelector.name', {
credentialName: name,
pluginName,
})}
</span>
<RiArrowDownSLine className='size-4 shrink-0 text-text-secondary' />
</div>
</div>
)
}
export default React.memo(Trigger)

View File

@@ -0,0 +1,60 @@
import React from 'react'
import Divider from '@/app/components/base/divider'
import Button from '@/app/components/base/button'
import { RiBookOpenLine, RiEqualizer2Line } from '@remixicon/react'
import type { CredentialSelectorProps } from './credential-selector'
import CredentialSelector from './credential-selector'
import Tooltip from '@/app/components/base/tooltip'
import { useTranslation } from 'react-i18next'
type HeaderProps = {
docTitle: string
docLink: string
onClickConfiguration?: () => void
} & CredentialSelectorProps
const Header = ({
docTitle,
docLink,
onClickConfiguration,
...rest
}: HeaderProps) => {
const { t } = useTranslation()
return (
<div className='flex items-center justify-between gap-x-2'>
<div className='flex items-center gap-x-1 overflow-hidden'>
<CredentialSelector
{...rest}
/>
<Divider type='vertical' className='mx-1 h-3.5 shrink-0' />
<Tooltip
popupContent={t('datasetPipeline.configurationTip', { pluginName: rest.pluginName })}
position='top'
>
<Button
variant='ghost'
size='small'
className='size-6 shrink-0 px-1'
>
<RiEqualizer2Line
className='h-4 w-4'
onClick={onClickConfiguration}
/>
</Button>
</Tooltip>
</div>
<a
className='system-xs-medium flex shrink-0 items-center gap-x-1 text-text-accent'
href={docLink}
target='_blank'
rel='noopener noreferrer'
>
<RiBookOpenLine className='size-3.5 shrink-0' />
<span title={docTitle}>{docTitle}</span>
</a>
</div>
)
}
export default React.memo(Header)