mirror of
https://github.com/langgenius/dify.git
synced 2026-04-29 12:37:20 +08:00
refactor: Restructure breadcrumbs component; introduce Bucket and BreadcrumbItem components for improved navigation
This commit is contained in:
parent
9ce0c69687
commit
d44af3ec46
@ -1,57 +0,0 @@
|
|||||||
import { BucketsGray } from '@/app/components/base/icons/src/public/knowledge/online-drive'
|
|
||||||
import React, { useCallback } from 'react'
|
|
||||||
import { useTranslation } from 'react-i18next'
|
|
||||||
import { useDataSourceStore } from '../../../store'
|
|
||||||
import Tooltip from '@/app/components/base/tooltip'
|
|
||||||
|
|
||||||
type BreadcrumbsProps = {
|
|
||||||
prefix: string[]
|
|
||||||
keywords: string
|
|
||||||
bucket: string
|
|
||||||
searchResultsLength: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const Breadcrumbs = ({
|
|
||||||
prefix,
|
|
||||||
keywords,
|
|
||||||
bucket,
|
|
||||||
searchResultsLength,
|
|
||||||
}: BreadcrumbsProps) => {
|
|
||||||
const { t } = useTranslation()
|
|
||||||
const { setFileList, setSelectedFileList, setPrefix, setBucket } = useDataSourceStore().getState()
|
|
||||||
const isRoot = prefix.length === 0 && bucket === ''
|
|
||||||
const isSearching = !!keywords
|
|
||||||
|
|
||||||
const handleBackToBucketList = useCallback(() => {
|
|
||||||
setFileList([])
|
|
||||||
setSelectedFileList([])
|
|
||||||
setBucket('')
|
|
||||||
setPrefix([])
|
|
||||||
}, [setBucket, setFileList, setPrefix, setSelectedFileList])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className='flex grow items-center py-1'>
|
|
||||||
{isRoot && (
|
|
||||||
<div className='system-sm-medium text-test-secondary px-[5px] py-1'>
|
|
||||||
{t('datasetPipeline.onlineDrive.breadcrumbs.allBuckets')}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{!isRoot && (
|
|
||||||
<div className='flex items-center gap-x-0.5'>
|
|
||||||
<Tooltip
|
|
||||||
popupContent={t('datasetPipeline.onlineDrive.breadcrumbs.allBuckets')}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className='flex size-5 cursor-pointer items-center justify-center'
|
|
||||||
onClick={handleBackToBucketList}
|
|
||||||
>
|
|
||||||
<BucketsGray />
|
|
||||||
</div>
|
|
||||||
</Tooltip>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default React.memo(Breadcrumbs)
|
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { BucketsGray } from '@/app/components/base/icons/src/public/knowledge/online-drive'
|
||||||
|
import Tooltip from '@/app/components/base/tooltip'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
type BucketProps = {
|
||||||
|
handleBackToBucketList: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bucket = ({
|
||||||
|
handleBackToBucketList,
|
||||||
|
}: BucketProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tooltip
|
||||||
|
popupContent={t('datasetPipeline.onlineDrive.breadcrumbs.allBuckets')}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className='flex size-6 cursor-pointer items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||||
|
onClick={handleBackToBucketList}
|
||||||
|
>
|
||||||
|
<BucketsGray />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
<span className='system-xs-regular text-divider-deep'>/</span>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(Bucket)
|
||||||
@ -0,0 +1,98 @@
|
|||||||
|
import React, { useCallback, useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useDataSourceStore } from '../../../../store'
|
||||||
|
import Bucket from './bucket'
|
||||||
|
import BreadcrumbItem from './item'
|
||||||
|
|
||||||
|
type BreadcrumbsProps = {
|
||||||
|
prefix: string[]
|
||||||
|
keywords: string
|
||||||
|
bucket: string
|
||||||
|
searchResultsLength: number
|
||||||
|
isInPipeline: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Breadcrumbs = ({
|
||||||
|
prefix,
|
||||||
|
keywords,
|
||||||
|
bucket,
|
||||||
|
searchResultsLength,
|
||||||
|
isInPipeline,
|
||||||
|
}: BreadcrumbsProps) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { setFileList, setSelectedFileList, setPrefix, setBucket } = useDataSourceStore().getState()
|
||||||
|
const showSearchResult = !!keywords && searchResultsLength > 0
|
||||||
|
const isRoot = prefix.length === 0 && bucket === ''
|
||||||
|
|
||||||
|
const breadcrumbs = useMemo(() => {
|
||||||
|
const displayBreadcrumbNum = isInPipeline ? 2 : 3
|
||||||
|
const prefixToDisplay = prefix.slice(0, displayBreadcrumbNum - 1)
|
||||||
|
const collapsedBreadcrumbs = prefix.slice(displayBreadcrumbNum - 1, prefix.length - 1)
|
||||||
|
return {
|
||||||
|
original: prefix,
|
||||||
|
needCollapsed: prefix.length > displayBreadcrumbNum,
|
||||||
|
prefixBreadcrumbs: prefixToDisplay,
|
||||||
|
collapsedBreadcrumbs,
|
||||||
|
lastBreadcrumb: prefix[prefix.length - 1],
|
||||||
|
}
|
||||||
|
}, [isInPipeline, prefix])
|
||||||
|
|
||||||
|
const handleBackToBucketList = useCallback(() => {
|
||||||
|
setFileList([])
|
||||||
|
setSelectedFileList([])
|
||||||
|
setBucket('')
|
||||||
|
setPrefix([])
|
||||||
|
}, [setBucket, setFileList, setPrefix, setSelectedFileList])
|
||||||
|
|
||||||
|
const handleClickBreadcrumb = useCallback((index: number) => {
|
||||||
|
const newPrefix = breadcrumbs.prefixBreadcrumbs.slice(0, index - 1)
|
||||||
|
setFileList([])
|
||||||
|
setSelectedFileList([])
|
||||||
|
setPrefix(newPrefix)
|
||||||
|
}, [breadcrumbs.prefixBreadcrumbs, setFileList, setPrefix, setSelectedFileList])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex grow items-center py-1'>
|
||||||
|
{showSearchResult && (
|
||||||
|
<div className='system-sm-medium text-test-secondary px-[5px] py-1'>
|
||||||
|
{t('datasetPipeline.onlineDrive.breadcrumbs.searchResult', {
|
||||||
|
searchResultsLength,
|
||||||
|
folderName: prefix.length > 0 ? prefix[prefix.length - 1] : bucket,
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!showSearchResult && isRoot && (
|
||||||
|
<div className='system-sm-medium text-test-secondary px-[5px] py-1'>
|
||||||
|
{t('datasetPipeline.onlineDrive.breadcrumbs.allBuckets')}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{!showSearchResult && !isRoot && (
|
||||||
|
<div className='flex items-center gap-x-0.5'>
|
||||||
|
{bucket && (
|
||||||
|
<Bucket handleBackToBucketList={handleBackToBucketList} />
|
||||||
|
)}
|
||||||
|
{!breadcrumbs.needCollapsed && (
|
||||||
|
<>
|
||||||
|
{breadcrumbs.original.map((breadcrumb, index) => {
|
||||||
|
const isLast = index === breadcrumbs.original.length - 1
|
||||||
|
return (
|
||||||
|
<BreadcrumbItem
|
||||||
|
key={`${breadcrumb}-${index}`}
|
||||||
|
index={index}
|
||||||
|
handleClick={handleClickBreadcrumb}
|
||||||
|
name={breadcrumb}
|
||||||
|
isActive={isLast}
|
||||||
|
showSeparator={!isLast}
|
||||||
|
disabled={isLast}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default React.memo(Breadcrumbs)
|
||||||
@ -0,0 +1,47 @@
|
|||||||
|
import React, { useCallback } from 'react'
|
||||||
|
import cn from '@/utils/classnames'
|
||||||
|
|
||||||
|
type BreadcrumbItemProps = {
|
||||||
|
name: string
|
||||||
|
index: number
|
||||||
|
handleClick: (index: number) => void
|
||||||
|
disabled?: boolean
|
||||||
|
isActive?: boolean
|
||||||
|
showSeparator?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const BreadcrumbItem = ({
|
||||||
|
name,
|
||||||
|
index,
|
||||||
|
handleClick,
|
||||||
|
disabled = false,
|
||||||
|
isActive = false,
|
||||||
|
showSeparator = true,
|
||||||
|
}: BreadcrumbItemProps) => {
|
||||||
|
const handleClickItem = useCallback(() => {
|
||||||
|
if (!disabled)
|
||||||
|
handleClick(index)
|
||||||
|
}, [disabled, handleClick, index])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type='button'
|
||||||
|
className={cn(
|
||||||
|
'rounded-md px-[5px] py-1',
|
||||||
|
isActive ? 'system-sm-medium text-text-secondary' : 'system-sm-regular text-text-tertiary',
|
||||||
|
!disabled && 'hover:bg-state-base-hover',
|
||||||
|
)}
|
||||||
|
disabled={disabled}
|
||||||
|
onClick={handleClickItem}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</button>
|
||||||
|
{showSeparator && <span className='system-xs-regular text-divider-deep'>/</span>}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
BreadcrumbItem.displayName = 'BreadcrumbItem'
|
||||||
|
|
||||||
|
export default React.memo(BreadcrumbItem)
|
||||||
@ -11,6 +11,7 @@ type HeaderProps = {
|
|||||||
searchResultsLength: number
|
searchResultsLength: number
|
||||||
handleInputChange: React.ChangeEventHandler<HTMLInputElement>
|
handleInputChange: React.ChangeEventHandler<HTMLInputElement>
|
||||||
handleResetKeywords: () => void
|
handleResetKeywords: () => void
|
||||||
|
isInPipeline: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
const Header = ({
|
const Header = ({
|
||||||
@ -18,6 +19,7 @@ const Header = ({
|
|||||||
inputValue,
|
inputValue,
|
||||||
keywords,
|
keywords,
|
||||||
bucket,
|
bucket,
|
||||||
|
isInPipeline,
|
||||||
searchResultsLength,
|
searchResultsLength,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
handleResetKeywords,
|
handleResetKeywords,
|
||||||
@ -31,6 +33,7 @@ const Header = ({
|
|||||||
keywords={keywords}
|
keywords={keywords}
|
||||||
bucket={bucket}
|
bucket={bucket}
|
||||||
searchResultsLength={searchResultsLength}
|
searchResultsLength={searchResultsLength}
|
||||||
|
isInPipeline={isInPipeline}
|
||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
value={inputValue}
|
value={inputValue}
|
||||||
|
|||||||
@ -60,6 +60,7 @@ const FileList = ({
|
|||||||
inputValue={inputValue}
|
inputValue={inputValue}
|
||||||
keywords={keywords}
|
keywords={keywords}
|
||||||
bucket={bucket}
|
bucket={bucket}
|
||||||
|
isInPipeline={isInPipeline}
|
||||||
handleInputChange={handleInputChange}
|
handleInputChange={handleInputChange}
|
||||||
searchResultsLength={searchResultsLength}
|
searchResultsLength={searchResultsLength}
|
||||||
handleResetKeywords={handleResetKeywords}
|
handleResetKeywords={handleResetKeywords}
|
||||||
|
|||||||
@ -66,12 +66,12 @@ const Item = ({
|
|||||||
'flex grow items-center gap-x-1 overflow-hidden py-0.5',
|
'flex grow items-center gap-x-1 overflow-hidden py-0.5',
|
||||||
disabled && 'opacity-30',
|
disabled && 'opacity-30',
|
||||||
)}>
|
)}>
|
||||||
<FileIcon type={file.type} fileName={file.key} className='shrink-0' />
|
<FileIcon type={file.type} fileName={file.displayName} className='shrink-0' />
|
||||||
<span
|
<span
|
||||||
className='system-sm-medium grow truncate text-text-secondary'
|
className='system-sm-medium grow truncate text-text-secondary'
|
||||||
title={file.key}
|
title={file.displayName}
|
||||||
>
|
>
|
||||||
{file.key}
|
{file.displayName}
|
||||||
</span>
|
</span>
|
||||||
{!isFolder && file.size && (
|
{!isFolder && file.size && (
|
||||||
<span className='system-xs-regular shrink-0 text-text-tertiary'>{formatFileSize(file.size)}</span>
|
<span className='system-xs-regular shrink-0 text-text-tertiary'>{formatFileSize(file.size)}</span>
|
||||||
|
|||||||
@ -43,13 +43,14 @@ const OnlineDrive = ({
|
|||||||
: `/rag/pipelines/${pipelineId}/workflows/draft/datasource/nodes/${nodeId}/run`
|
: `/rag/pipelines/${pipelineId}/workflows/draft/datasource/nodes/${nodeId}/run`
|
||||||
|
|
||||||
const getOnlineDrive = useCallback(async () => {
|
const getOnlineDrive = useCallback(async () => {
|
||||||
|
const prefixString = prefix.length > 0 ? `${prefix.join('/')}/` : ''
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
ssePost(
|
ssePost(
|
||||||
datasourceNodeRunURL,
|
datasourceNodeRunURL,
|
||||||
{
|
{
|
||||||
body: {
|
body: {
|
||||||
inputs: {
|
inputs: {
|
||||||
prefix: prefix.join('/'),
|
prefix: prefixString,
|
||||||
bucket,
|
bucket,
|
||||||
start_after: startAfter,
|
start_after: startAfter,
|
||||||
max_keys: 30, // Adjust as needed
|
max_keys: 30, // Adjust as needed
|
||||||
@ -111,11 +112,12 @@ const OnlineDrive = ({
|
|||||||
if (file.type === OnlineDriveFileType.file) return
|
if (file.type === OnlineDriveFileType.file) return
|
||||||
setFileList([])
|
setFileList([])
|
||||||
if (file.type === OnlineDriveFileType.bucket) {
|
if (file.type === OnlineDriveFileType.bucket) {
|
||||||
setBucket(file.key)
|
setBucket(file.displayName)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
const key = file.displayName.endsWith('/') ? file.displayName.slice(0, -1) : file.displayName
|
||||||
const newPrefix = produce(prefix, (draft) => {
|
const newPrefix = produce(prefix, (draft) => {
|
||||||
const newList = file.key.split('/')
|
const newList = key.split('/')
|
||||||
draft.push(...newList)
|
draft.push(...newList)
|
||||||
})
|
})
|
||||||
setPrefix(newPrefix)
|
setPrefix(newPrefix)
|
||||||
|
|||||||
@ -18,16 +18,20 @@ export const convertOnlineDriveDataToFileList = (data: OnlineDriveData[]): Onlin
|
|||||||
data.forEach((item) => {
|
data.forEach((item) => {
|
||||||
fileList.push({
|
fileList.push({
|
||||||
key: item.bucket,
|
key: item.bucket,
|
||||||
|
displayName: item.bucket,
|
||||||
type: OnlineDriveFileType.bucket,
|
type: OnlineDriveFileType.bucket,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
data[0].files.forEach((file) => {
|
data[0].files.forEach((file) => {
|
||||||
|
const isFileType = isFile(file.key)
|
||||||
|
const filePathList = file.key.split('/')
|
||||||
fileList.push({
|
fileList.push({
|
||||||
key: file.key,
|
key: file.key,
|
||||||
size: isFile(file.key) ? file.size : undefined,
|
displayName: `${isFileType ? filePathList.pop() : filePathList[filePathList.length - 2]}${isFileType ? '' : '/'}`,
|
||||||
type: isFile(file.key) ? OnlineDriveFileType.file : OnlineDriveFileType.folder,
|
size: isFileType ? file.size : undefined,
|
||||||
|
type: isFileType ? OnlineDriveFileType.file : OnlineDriveFileType.folder,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -276,6 +276,7 @@ export enum OnlineDriveFileType {
|
|||||||
|
|
||||||
export type OnlineDriveFile = {
|
export type OnlineDriveFile = {
|
||||||
key: string
|
key: string
|
||||||
|
displayName: string
|
||||||
size?: number
|
size?: number
|
||||||
type: OnlineDriveFileType
|
type: OnlineDriveFileType
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user