mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-26 20:12:38 +08:00
Revert "refactor(FileItem, FileList, FilesPage, i18n): enhance file management UI and localization"
This reverts commit edeb9f84f9.
This commit is contained in:
parent
f1804bc3a0
commit
20b3db0c01
@ -470,7 +470,7 @@
|
||||
"count": "files",
|
||||
"created_at": "Created At",
|
||||
"delete": "Delete",
|
||||
"delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete {{count}} files?",
|
||||
"delete.content": "Deleting a file will delete its reference from all messages. Are you sure you want to delete this file?",
|
||||
"delete.paintings.warning": "Image contains this file, deletion is not possible",
|
||||
"delete.title": "Delete File",
|
||||
"document": "Document",
|
||||
|
||||
@ -470,7 +470,7 @@
|
||||
"count": "ファイル",
|
||||
"created_at": "作成日",
|
||||
"delete": "削除",
|
||||
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。この{{count}}ファイルを削除してもよろしいですか?",
|
||||
"delete.content": "ファイルを削除すると、ファイルがすべてのメッセージで参照されることを削除します。このファイルを削除してもよろしいですか?",
|
||||
"delete.paintings.warning": "画像に含まれているため、削除できません",
|
||||
"delete.title": "ファイルを削除",
|
||||
"document": "ドキュメント",
|
||||
|
||||
@ -470,7 +470,7 @@
|
||||
"count": "файлов",
|
||||
"created_at": "Дата создания",
|
||||
"delete": "Удалить",
|
||||
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот {{count}} файл",
|
||||
"delete.content": "Удаление файла удалит его из всех сообщений, вы уверены, что хотите удалить этот файл?",
|
||||
"delete.paintings.warning": "В изображениях содержится этот файл, удаление невозможно",
|
||||
"delete.title": "Удалить файл",
|
||||
"document": "Документ",
|
||||
|
||||
@ -470,7 +470,7 @@
|
||||
"count": "个文件",
|
||||
"created_at": "创建时间",
|
||||
"delete": "删除",
|
||||
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除这{{count}}个文件吗?",
|
||||
"delete.content": "删除文件会删除文件在所有消息中的引用,确定要删除此文件吗?",
|
||||
"delete.paintings.warning": "绘图中包含该图片,暂时无法删除",
|
||||
"delete.title": "删除文件",
|
||||
"document": "文档",
|
||||
|
||||
@ -470,7 +470,7 @@
|
||||
"count": "個檔案",
|
||||
"created_at": "建立時間",
|
||||
"delete": "刪除",
|
||||
"delete.content": "刪除檔案會刪除檔案在所有訊息中的引用,確定要刪除這{{count}}個檔案嗎?",
|
||||
"delete.content": "刪除檔案會刪除檔案在所有訊息中的引用,確定要刪除此檔案嗎?",
|
||||
"delete.paintings.warning": "繪圖中包含該圖片,暫時無法刪除",
|
||||
"delete.title": "刪除檔案",
|
||||
"document": "文件",
|
||||
|
||||
@ -12,7 +12,7 @@ import {
|
||||
GlobalOutlined,
|
||||
LinkOutlined
|
||||
} from '@ant-design/icons'
|
||||
import { t } from 'i18next'
|
||||
import { Flex } from 'antd'
|
||||
import React, { memo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
@ -21,14 +21,10 @@ interface FileItemProps {
|
||||
icon?: React.ReactNode
|
||||
name: React.ReactNode | string
|
||||
ext: string
|
||||
size: string
|
||||
created_at: string
|
||||
count?: number
|
||||
checkbox?: React.ReactNode
|
||||
extra?: React.ReactNode | string
|
||||
actions: React.ReactNode
|
||||
}
|
||||
style?: React.CSSProperties
|
||||
gridTemplate?: string
|
||||
}
|
||||
|
||||
const getFileIcon = (type?: string) => {
|
||||
@ -79,30 +75,19 @@ const getFileIcon = (type?: string) => {
|
||||
return <FileUnknownFilled />
|
||||
}
|
||||
|
||||
const FileItem: React.FC<FileItemProps> = ({ fileInfo, style, gridTemplate = '' }) => {
|
||||
const { name, ext, size, created_at, count, actions, icon, checkbox } = fileInfo
|
||||
const FileItem: React.FC<FileItemProps> = ({ fileInfo, style }) => {
|
||||
const { name, ext, extra, actions, icon } = fileInfo
|
||||
|
||||
return (
|
||||
<FileItemCard style={style}>
|
||||
<FileGrid style={{ gridTemplateColumns: gridTemplate }}>
|
||||
{checkbox && <FileCell>{checkbox}</FileCell>}
|
||||
<FileCell>
|
||||
<FileIcon>{icon || getFileIcon(ext)}</FileIcon>
|
||||
</FileCell>
|
||||
<FileCell>
|
||||
<FileNameColumn>
|
||||
<FileName>{name}</FileName>
|
||||
{count && (
|
||||
<FileCount>
|
||||
{count} {t('files.count')}
|
||||
</FileCount>
|
||||
)}
|
||||
</FileNameColumn>
|
||||
</FileCell>
|
||||
<FileCell style={{ textAlign: 'right' }}>{size}</FileCell>
|
||||
<FileCell style={{ textAlign: 'right' }}>{created_at}</FileCell>
|
||||
<FileCell style={{ justifyContent: 'center' }}>{actions}</FileCell>
|
||||
</FileGrid>
|
||||
<CardContent>
|
||||
<FileIcon>{icon || getFileIcon(ext)}</FileIcon>
|
||||
<Flex vertical justify="center" gap={0} flex={1} style={{ width: '0px' }}>
|
||||
<FileName>{name}</FileName>
|
||||
{extra && <FileInfo>{extra}</FileInfo>}
|
||||
</Flex>
|
||||
<FileActions>{actions}</FileActions>
|
||||
</CardContent>
|
||||
</FileItemCard>
|
||||
)
|
||||
}
|
||||
@ -126,36 +111,20 @@ const FileItemCard = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const FileGrid = styled.div`
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
const CardContent = styled.div`
|
||||
padding: 8px 8px 8px 16px;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const FileCell = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const FileIcon = styled.div`
|
||||
max-height: 44px;
|
||||
width: 100%;
|
||||
color: var(--color-text-3);
|
||||
font-size: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
const FileNameColumn = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
`
|
||||
|
||||
const FileName = styled.div`
|
||||
@ -173,9 +142,16 @@ const FileName = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const FileCount = styled.div`
|
||||
const FileInfo = styled.div`
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
`
|
||||
|
||||
const FileActions = styled.div`
|
||||
max-height: 44px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
export default memo(FileItem)
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
import FileManager from '@renderer/services/FileManager'
|
||||
import { FileType, FileTypes } from '@renderer/types'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { Col, Image, Row, Spin } from 'antd'
|
||||
import { t } from 'i18next'
|
||||
import VirtualList from 'rc-virtual-list'
|
||||
import React, { memo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import FileItem from './FileItem'
|
||||
|
||||
@ -14,47 +19,43 @@ interface FileItemProps {
|
||||
size: string
|
||||
ext: string
|
||||
created_at: string
|
||||
checkbox?: React.ReactNode
|
||||
actions: React.ReactNode
|
||||
}[]
|
||||
files?: FileType[]
|
||||
selectedFileIds?: string[]
|
||||
onFileSelect?: (fileId: string, checked: boolean) => void
|
||||
columnWidths?: string
|
||||
}
|
||||
|
||||
const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
|
||||
// if (id === FileTypes.IMAGE && files?.length && files?.length > 0) {
|
||||
// return (
|
||||
// <div style={{ padding: 16, overflowY: 'auto' }}>
|
||||
// <Image.PreviewGroup>
|
||||
// <Row gutter={[16, 16]}>
|
||||
// {files?.map((file) => (
|
||||
// <Col key={file.id} xs={24} sm={12} md={8} lg={4} xl={3}>
|
||||
// <ImageWrapper>
|
||||
// <LoadingWrapper>
|
||||
// <Spin />
|
||||
// </LoadingWrapper>
|
||||
// <Image
|
||||
// src={FileManager.getFileUrl(file)}
|
||||
// style={{ height: '100%', objectFit: 'cover', cursor: 'pointer' }}
|
||||
// preview={{ mask: false }}
|
||||
// onLoad={(e) => {
|
||||
// const img = e.target as HTMLImageElement
|
||||
// img.parentElement?.classList.add('loaded')
|
||||
// }}
|
||||
// />
|
||||
// <ImageInfo>
|
||||
// <div>{formatFileSize(file.size)}</div>
|
||||
// </ImageInfo>
|
||||
// </ImageWrapper>
|
||||
// </Col>
|
||||
// ))}
|
||||
// </Row>
|
||||
// </Image.PreviewGroup>
|
||||
// </div>
|
||||
// )
|
||||
// }
|
||||
const FileList: React.FC<FileItemProps> = ({ id, list, files }) => {
|
||||
if (id === FileTypes.IMAGE && files?.length && files?.length > 0) {
|
||||
return (
|
||||
<div style={{ padding: 16, overflowY: 'auto' }}>
|
||||
<Image.PreviewGroup>
|
||||
<Row gutter={[16, 16]}>
|
||||
{files?.map((file) => (
|
||||
<Col key={file.id} xs={24} sm={12} md={8} lg={4} xl={3}>
|
||||
<ImageWrapper>
|
||||
<LoadingWrapper>
|
||||
<Spin />
|
||||
</LoadingWrapper>
|
||||
<Image
|
||||
src={FileManager.getFileUrl(file)}
|
||||
style={{ height: '100%', objectFit: 'cover', cursor: 'pointer' }}
|
||||
preview={{ mask: false }}
|
||||
onLoad={(e) => {
|
||||
const img = e.target as HTMLImageElement
|
||||
img.parentElement?.classList.add('loaded')
|
||||
}}
|
||||
/>
|
||||
<ImageInfo>
|
||||
<div>{formatFileSize(file.size)}</div>
|
||||
</ImageInfo>
|
||||
</ImageWrapper>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
</Image.PreviewGroup>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<VirtualList
|
||||
@ -62,7 +63,7 @@ const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
|
||||
height={window.innerHeight - 100}
|
||||
itemHeight={75}
|
||||
itemKey="key"
|
||||
style={{ padding: '0 0 16px 0' }}
|
||||
style={{ padding: '0 16px 16px 16px' }}
|
||||
styles={{
|
||||
verticalScrollBar: {
|
||||
width: 6
|
||||
@ -75,21 +76,16 @@ const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
|
||||
<div
|
||||
style={{
|
||||
height: '75px',
|
||||
paddingTop: '8px',
|
||||
margin: '0 16px'
|
||||
paddingTop: '12px'
|
||||
}}>
|
||||
<FileItem
|
||||
key={item.key}
|
||||
fileInfo={{
|
||||
name: item.file,
|
||||
ext: item.ext,
|
||||
size: item.size,
|
||||
created_at: item.created_at,
|
||||
count: item.count,
|
||||
checkbox: item.checkbox,
|
||||
extra: `${item.created_at} · ${item.count}${t('files.count')} · ${item.size}`,
|
||||
actions: item.actions
|
||||
}}
|
||||
gridTemplate={columnWidths}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@ -97,70 +93,70 @@ const FileList: React.FC<FileItemProps> = ({ list, columnWidths }) => {
|
||||
)
|
||||
}
|
||||
|
||||
// const ImageWrapper = styled.div`
|
||||
// position: relative;
|
||||
// aspect-ratio: 1;
|
||||
// overflow: hidden;
|
||||
// border-radius: 8px;
|
||||
// background-color: var(--color-background-soft);
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// border: 0.5px solid var(--color-border);
|
||||
const ImageWrapper = styled.div`
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-background-soft);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0.5px solid var(--color-border);
|
||||
|
||||
// .ant-image {
|
||||
// height: 100%;
|
||||
// width: 100%;
|
||||
// opacity: 0;
|
||||
// transition:
|
||||
// opacity 0.3s ease,
|
||||
// transform 0.3s ease;
|
||||
.ant-image {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
opacity: 0;
|
||||
transition:
|
||||
opacity 0.3s ease,
|
||||
transform 0.3s ease;
|
||||
|
||||
// &.loaded {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
&.loaded {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
// &:hover {
|
||||
// .ant-image.loaded {
|
||||
// transform: scale(1.05);
|
||||
// }
|
||||
&:hover {
|
||||
.ant-image.loaded {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
// div:last-child {
|
||||
// opacity: 1;
|
||||
// }
|
||||
// }
|
||||
// `
|
||||
div:last-child {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
// const LoadingWrapper = styled.div`
|
||||
// position: absolute;
|
||||
// top: 0;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// bottom: 0;
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
// justify-content: center;
|
||||
// background-color: var(--color-background-soft);
|
||||
// `
|
||||
const LoadingWrapper = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--color-background-soft);
|
||||
`
|
||||
|
||||
// const ImageInfo = styled.div`
|
||||
// position: absolute;
|
||||
// bottom: 0;
|
||||
// left: 0;
|
||||
// right: 0;
|
||||
// background: rgba(0, 0, 0, 0.6);
|
||||
// color: white;
|
||||
// padding: 5px 8px;
|
||||
// opacity: 0;
|
||||
// transition: opacity 0.3s ease;
|
||||
// font-size: 12px;
|
||||
const ImageInfo = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
color: white;
|
||||
padding: 5px 8px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
font-size: 12px;
|
||||
|
||||
// > div:first-child {
|
||||
// white-space: nowrap;
|
||||
// overflow: hidden;
|
||||
// text-overflow: ellipsis;
|
||||
// }
|
||||
// `
|
||||
> div:first-child {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
`
|
||||
|
||||
export default memo(FileList)
|
||||
|
||||
@ -15,7 +15,7 @@ import store from '@renderer/store'
|
||||
import { FileType, FileTypes } from '@renderer/types'
|
||||
import { Message } from '@renderer/types/newMessage'
|
||||
import { formatFileSize } from '@renderer/utils'
|
||||
import { Button, Checkbox, Empty, Flex, Popconfirm } from 'antd'
|
||||
import { Button, Empty, Flex, Popconfirm } from 'antd'
|
||||
import dayjs from 'dayjs'
|
||||
import { useLiveQuery } from 'dexie-react-hooks'
|
||||
import { File as FileIcon, FileImage, FileText, FileType as FileTypeIcon } from 'lucide-react'
|
||||
@ -25,8 +25,6 @@ import styled from 'styled-components'
|
||||
|
||||
import FileList from './FileList'
|
||||
|
||||
const GRID_TEMPLATE = 'auto 60px 1fr 120px 140px 100px'
|
||||
|
||||
type SortField = 'created_at' | 'size' | 'name'
|
||||
type SortOrder = 'asc' | 'desc'
|
||||
|
||||
@ -35,7 +33,6 @@ const FilesPage: FC = () => {
|
||||
const [fileType, setFileType] = useState<string>('document')
|
||||
const [sortField, setSortField] = useState<SortField>('created_at')
|
||||
const [sortOrder, setSortOrder] = useState<SortOrder>('desc')
|
||||
const [selectedFileIds, setSelectedFileIds] = useState<string[]>([])
|
||||
|
||||
const tempFilesSort = (files: FileType[]) => {
|
||||
return files.sort((a, b) => {
|
||||
@ -173,38 +170,6 @@ const FilesPage: FC = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 全选/取消全选
|
||||
const handleSelectAll = (checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedFileIds(sortedFiles.map((file) => file.id))
|
||||
} else {
|
||||
setSelectedFileIds([])
|
||||
}
|
||||
}
|
||||
|
||||
// 单个文件选择
|
||||
const handleFileSelect = (fileId: string, checked: boolean) => {
|
||||
if (checked) {
|
||||
setSelectedFileIds((prev) => [...prev, fileId])
|
||||
} else {
|
||||
setSelectedFileIds((prev) => prev.filter((id) => id !== fileId))
|
||||
}
|
||||
}
|
||||
|
||||
// 批量删除
|
||||
const handleBatchDelete = async () => {
|
||||
if (selectedFileIds.length === 0) return
|
||||
|
||||
try {
|
||||
for (const fileId of selectedFileIds) {
|
||||
await handleDelete(fileId)
|
||||
}
|
||||
setSelectedFileIds([])
|
||||
} catch (error) {
|
||||
Logger.error('Batch delete error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const dataSource = sortedFiles?.map((file) => {
|
||||
return {
|
||||
key: file.id,
|
||||
@ -216,18 +181,12 @@ const FilesPage: FC = () => {
|
||||
ext: file.ext,
|
||||
created_at: dayjs(file.created_at).format('MM-DD HH:mm'),
|
||||
created_at_unix: dayjs(file.created_at).unix(),
|
||||
checkbox: (
|
||||
<Checkbox
|
||||
checked={selectedFileIds.includes(file.id)}
|
||||
onChange={(e) => handleFileSelect(file.id, e.target.checked)}
|
||||
/>
|
||||
),
|
||||
actions: (
|
||||
<Flex align="center" gap={0} style={{ opacity: 0.7 }}>
|
||||
<Button type="text" icon={<EditOutlined />} onClick={() => handleRename(file.id)} />
|
||||
<Popconfirm
|
||||
title={t('files.delete.title')}
|
||||
description={t('files.delete.content', { count: 1 })}
|
||||
description={t('files.delete.content')}
|
||||
okText={t('common.confirm')}
|
||||
cancelText={t('common.cancel')}
|
||||
onConfirm={() => handleDelete(file.id)}
|
||||
@ -264,90 +223,26 @@ const FilesPage: FC = () => {
|
||||
))}
|
||||
</SideNav>
|
||||
<MainContent>
|
||||
<TableHeader>
|
||||
<TableGrid style={{ gridTemplateColumns: GRID_TEMPLATE }}>
|
||||
<TableCell>
|
||||
<Checkbox
|
||||
indeterminate={selectedFileIds.length > 0 && selectedFileIds.length < sortedFiles.length}
|
||||
checked={selectedFileIds.length === sortedFiles.length && sortedFiles.length > 0}
|
||||
onChange={(e) => handleSelectAll(e.target.checked)}
|
||||
disabled={sortedFiles.length === 0}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{/* 图标列 */}</TableCell>
|
||||
<TableCell>
|
||||
<SortButton
|
||||
active={sortField === 'name'}
|
||||
onClick={() => {
|
||||
if (sortField === 'name') {
|
||||
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
setSortField('name')
|
||||
setSortOrder('desc')
|
||||
}
|
||||
}}>
|
||||
{t('files.name')}
|
||||
{sortField === 'name' &&
|
||||
(sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
|
||||
</SortButton>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SortButton
|
||||
active={sortField === 'size'}
|
||||
onClick={() => {
|
||||
if (sortField === 'size') {
|
||||
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
setSortField('size')
|
||||
setSortOrder('desc')
|
||||
}
|
||||
}}>
|
||||
{t('files.size')}
|
||||
{sortField === 'size' &&
|
||||
(sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
|
||||
</SortButton>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SortButton
|
||||
active={sortField === 'created_at'}
|
||||
onClick={() => {
|
||||
if (sortField === 'created_at') {
|
||||
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
setSortField('created_at')
|
||||
setSortOrder('desc')
|
||||
}
|
||||
}}>
|
||||
{t('files.created_at')}
|
||||
{sortField === 'created_at' &&
|
||||
(sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
|
||||
</SortButton>
|
||||
</TableCell>
|
||||
<TableCell style={{ justifyContent: 'center' }}>
|
||||
{selectedFileIds.length > 0 && (
|
||||
<Popconfirm
|
||||
title={t('files.delete.title')}
|
||||
description={t('files.delete.content', { count: selectedFileIds.length })}
|
||||
okText={t('common.confirm')}
|
||||
cancelText={t('common.cancel')}
|
||||
onConfirm={handleBatchDelete}
|
||||
icon={<ExclamationCircleOutlined style={{ color: 'red' }} />}>
|
||||
<Button type="text" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableGrid>
|
||||
</TableHeader>
|
||||
|
||||
<SortContainer>
|
||||
{['created_at', 'size', 'name'].map((field) => (
|
||||
<SortButton
|
||||
key={field}
|
||||
active={sortField === field}
|
||||
onClick={() => {
|
||||
if (sortField === field) {
|
||||
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc')
|
||||
} else {
|
||||
setSortField(field as 'created_at' | 'size' | 'name')
|
||||
setSortOrder('desc')
|
||||
}
|
||||
}}>
|
||||
{t(`files.${field}`)}
|
||||
{sortField === field && (sortOrder === 'desc' ? <SortDescendingOutlined /> : <SortAscendingOutlined />)}
|
||||
</SortButton>
|
||||
))}
|
||||
</SortContainer>
|
||||
{dataSource && dataSource?.length > 0 ? (
|
||||
<FileList
|
||||
id={fileType}
|
||||
list={dataSource}
|
||||
files={sortedFiles}
|
||||
selectedFileIds={selectedFileIds}
|
||||
onFileSelect={handleFileSelect}
|
||||
columnWidths={GRID_TEMPLATE}
|
||||
/>
|
||||
<FileList id={fileType} list={dataSource} files={sortedFiles} />
|
||||
) : (
|
||||
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
||||
)}
|
||||
@ -370,6 +265,14 @@ const MainContent = styled.div`
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const SortContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 0.5px solid var(--color-border);
|
||||
`
|
||||
|
||||
const ContentContainer = styled.div`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
@ -412,39 +315,20 @@ const SideNav = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const SortButton = styled.div<{ active?: boolean }>`
|
||||
position: relative;
|
||||
const SortButton = styled(Button)<{ active?: boolean }>`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 12px;
|
||||
height: 30px;
|
||||
border-radius: var(--list-item-border-radius);
|
||||
border: 0.5px solid ${(props) => (props.active ? 'var(--color-border)' : 'transparent')};
|
||||
background-color: ${(props) => (props.active ? 'var(--color-background-soft)' : 'transparent')};
|
||||
color: ${(props) => (props.active ? 'var(--color-text)' : 'var(--color-text-secondary)')};
|
||||
font-weight: ${(props) => (props.active ? '500' : '400')};
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
left: -8px;
|
||||
right: -8px;
|
||||
bottom: -4px;
|
||||
border-radius: var(--list-item-border-radius);
|
||||
border: 0.5px solid ${(props) => (props.active ? 'var(--color-border)' : 'transparent')};
|
||||
background-color: ${(props) => (props.active ? 'var(--color-background-soft)' : 'transparent')};
|
||||
transition: all 0.2s ease;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&:hover::before {
|
||||
&:hover {
|
||||
background-color: var(--color-background-soft);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
> * {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.anticon {
|
||||
@ -452,25 +336,4 @@ const SortButton = styled.div<{ active?: boolean }>`
|
||||
}
|
||||
`
|
||||
|
||||
const TableGrid = styled.div`
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 8px 8px 0px 16px;
|
||||
align-items: center;
|
||||
`
|
||||
|
||||
const TableCell = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
|
||||
&:last-child {
|
||||
justify-content: center;
|
||||
}
|
||||
`
|
||||
|
||||
const TableHeader = styled.div`
|
||||
margin: 0 16px;
|
||||
`
|
||||
|
||||
export default FilesPage
|
||||
|
||||
@ -21,8 +21,8 @@ import { useTranslation } from 'react-i18next'
|
||||
import styled from 'styled-components'
|
||||
|
||||
import CustomCollapse from '../../components/CustomCollapse'
|
||||
import FileItem from '../files/FileItem'
|
||||
import { NavbarIcon } from '../home/ChatNavbar'
|
||||
import KnowledgeFileItem from './components/KnowledgeFileItem'
|
||||
import KnowledgeSearchPopup from './components/KnowledgeSearchPopup'
|
||||
import KnowledgeSettingsPopup from './components/KnowledgeSettingsPopup'
|
||||
import StatusIcon from './components/StatusIcon'
|
||||
@ -323,8 +323,8 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
{(item) => {
|
||||
const file = item.content as FileType
|
||||
return (
|
||||
<div style={{ height: '75px', marginTop: '12px' }}>
|
||||
<KnowledgeFileItem
|
||||
<div style={{ height: '75px', paddingTop: '12px' }}>
|
||||
<FileItem
|
||||
key={item.id}
|
||||
fileInfo={{
|
||||
name: (
|
||||
@ -381,7 +381,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<FlexColumn>
|
||||
{directoryItems.length === 0 && <EmptyView />}
|
||||
{directoryItems.reverse().map((item) => (
|
||||
<KnowledgeFileItem
|
||||
<FileItem
|
||||
key={item.id}
|
||||
fileInfo={{
|
||||
name: (
|
||||
@ -433,7 +433,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<FlexColumn>
|
||||
{urlItems.length === 0 && <EmptyView />}
|
||||
{urlItems.reverse().map((item) => (
|
||||
<KnowledgeFileItem
|
||||
<FileItem
|
||||
key={item.id}
|
||||
fileInfo={{
|
||||
name: (
|
||||
@ -510,7 +510,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<FlexColumn>
|
||||
{sitemapItems.length === 0 && <EmptyView />}
|
||||
{sitemapItems.reverse().map((item) => (
|
||||
<KnowledgeFileItem
|
||||
<FileItem
|
||||
key={item.id}
|
||||
fileInfo={{
|
||||
name: (
|
||||
@ -565,7 +565,7 @@ const KnowledgeContent: FC<KnowledgeContentProps> = ({ selectedBase }) => {
|
||||
<FlexColumn>
|
||||
{noteItems.length === 0 && <EmptyView />}
|
||||
{noteItems.reverse().map((note) => (
|
||||
<KnowledgeFileItem
|
||||
<FileItem
|
||||
key={note.id}
|
||||
fileInfo={{
|
||||
name: <span onClick={() => handleEditNote(note)}>{(note.content as string).slice(0, 50)}...</span>,
|
||||
|
||||
@ -1,124 +0,0 @@
|
||||
import { getFileIcon } from '@renderer/pages/home/Inputbar/AttachmentPreview'
|
||||
import React, { memo } from 'react'
|
||||
import styled from 'styled-components'
|
||||
|
||||
interface KnowledgeFileItemProps {
|
||||
fileInfo: {
|
||||
icon?: React.ReactNode
|
||||
name: React.ReactNode | string
|
||||
ext: string
|
||||
extra?: string
|
||||
actions: React.ReactNode
|
||||
}
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
|
||||
const KnowledgeFileItem: React.FC<KnowledgeFileItemProps> = ({ fileInfo, style }) => {
|
||||
const { name, ext, extra, actions, icon } = fileInfo
|
||||
|
||||
return (
|
||||
<FileItemCard style={style}>
|
||||
<FileContainer>
|
||||
<FileIconContainer>
|
||||
<FileIcon>{icon || getFileIcon(ext)}</FileIcon>
|
||||
</FileIconContainer>
|
||||
<FileContent>
|
||||
<FileNameContainer>
|
||||
<FileName>{name}</FileName>
|
||||
{extra && <FileExtra>{extra}</FileExtra>}
|
||||
</FileNameContainer>
|
||||
</FileContent>
|
||||
<FileActions>{actions}</FileActions>
|
||||
</FileContainer>
|
||||
</FileItemCard>
|
||||
)
|
||||
}
|
||||
|
||||
const FileItemCard = styled.div`
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 0.5px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
transition:
|
||||
box-shadow 0.2s ease,
|
||||
background-color 0.2s ease;
|
||||
--shadow-color: rgba(0, 0, 0, 0.05);
|
||||
|
||||
&:hover {
|
||||
box-shadow:
|
||||
0 10px 15px -3px var(--shadow-color),
|
||||
0 4px 6px -4px var(--shadow-color);
|
||||
}
|
||||
|
||||
body[theme-mode='dark'] & {
|
||||
--shadow-color: rgba(255, 255, 255, 0.02);
|
||||
}
|
||||
`
|
||||
|
||||
const FileContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
min-height: 63px;
|
||||
`
|
||||
|
||||
const FileIconContainer = styled.div`
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
const FileIcon = styled.div`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: var(--color-text-3);
|
||||
font-size: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`
|
||||
|
||||
const FileContent = styled.div`
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
`
|
||||
|
||||
const FileNameContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
min-width: 0;
|
||||
`
|
||||
|
||||
const FileName = styled.div`
|
||||
font-size: 15px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s ease;
|
||||
|
||||
span {
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
`
|
||||
|
||||
const FileExtra = styled.div`
|
||||
font-size: 13px;
|
||||
color: var(--color-text-2);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`
|
||||
|
||||
const FileActions = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
flex-shrink: 0;
|
||||
`
|
||||
|
||||
export default memo(KnowledgeFileItem)
|
||||
Loading…
Reference in New Issue
Block a user