mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? feat: Move files in file manager #1826 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1,6 +1,12 @@
|
||||
import NewDocumentLink from '@/components/new-document-link';
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import { api_host } from '@/utils/api';
|
||||
import {
|
||||
getExtension,
|
||||
isSupportedPreviewDocumentType,
|
||||
} from '@/utils/document-util';
|
||||
import { downloadFile } from '@/utils/file-util';
|
||||
import {
|
||||
DeleteOutlined,
|
||||
@ -11,18 +17,13 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import { Button, Space, Tooltip } from 'antd';
|
||||
import { useHandleDeleteFile } from '../hooks';
|
||||
|
||||
import NewDocumentLink from '@/components/new-document-link';
|
||||
import {
|
||||
getExtension,
|
||||
isSupportedPreviewDocumentType,
|
||||
} from '@/utils/document-util';
|
||||
import styles from './index.less';
|
||||
|
||||
interface IProps {
|
||||
record: IFile;
|
||||
setCurrentRecord: (record: any) => void;
|
||||
showRenameModal: (record: IFile) => void;
|
||||
showMoveFileModal: (ids: string[]) => void;
|
||||
showConnectToKnowledgeModal: (record: IFile) => void;
|
||||
setSelectedRowKeys(keys: string[]): void;
|
||||
}
|
||||
@ -33,6 +34,7 @@ const ActionCell = ({
|
||||
showRenameModal,
|
||||
showConnectToKnowledgeModal,
|
||||
setSelectedRowKeys,
|
||||
showMoveFileModal,
|
||||
}: IProps) => {
|
||||
const documentId = record.id;
|
||||
const beingUsed = false;
|
||||
@ -64,6 +66,10 @@ const ActionCell = ({
|
||||
showConnectToKnowledgeModal(record);
|
||||
};
|
||||
|
||||
const onShowMoveFileModal = () => {
|
||||
showMoveFileModal([documentId]);
|
||||
};
|
||||
|
||||
return (
|
||||
<Space size={0}>
|
||||
{isKnowledgeBase || (
|
||||
@ -90,6 +96,18 @@ const ActionCell = ({
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isKnowledgeBase || (
|
||||
<Tooltip title={t('move', { keyPrefix: 'common' })}>
|
||||
<Button
|
||||
type="text"
|
||||
disabled={beingUsed}
|
||||
onClick={onShowMoveFileModal}
|
||||
className={styles.iconButton}
|
||||
>
|
||||
<SvgIcon name={`move`} width={16}></SvgIcon>
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
{isKnowledgeBase || (
|
||||
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
||||
<Button
|
||||
|
||||
@ -17,13 +17,14 @@ import {
|
||||
MenuProps,
|
||||
Space,
|
||||
} from 'antd';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
useHandleBreadcrumbClick,
|
||||
useHandleDeleteFile,
|
||||
useSelectBreadcrumbItems,
|
||||
} from './hooks';
|
||||
|
||||
import SvgIcon from '@/components/svg-icon';
|
||||
import {
|
||||
IListResult,
|
||||
useFetchParentFolderList,
|
||||
@ -36,6 +37,7 @@ interface IProps
|
||||
showFolderCreateModal: () => void;
|
||||
showFileUploadModal: () => void;
|
||||
setSelectedRowKeys: (keys: string[]) => void;
|
||||
showMoveFileModal: (ids: string[]) => void;
|
||||
}
|
||||
|
||||
const FileToolbar = ({
|
||||
@ -45,6 +47,7 @@ const FileToolbar = ({
|
||||
setSelectedRowKeys,
|
||||
searchString,
|
||||
handleInputChange,
|
||||
showMoveFileModal,
|
||||
}: IProps) => {
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const breadcrumbItems = useSelectBreadcrumbItems();
|
||||
@ -111,6 +114,10 @@ const FileToolbar = ({
|
||||
setSelectedRowKeys,
|
||||
);
|
||||
|
||||
const handleShowMoveFileModal = useCallback(() => {
|
||||
showMoveFileModal(selectedRowKeys);
|
||||
}, [selectedRowKeys, showMoveFileModal]);
|
||||
|
||||
const disabled = selectedRowKeys.length === 0;
|
||||
|
||||
const items: MenuProps['items'] = useMemo(() => {
|
||||
@ -127,8 +134,20 @@ const FileToolbar = ({
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: '5',
|
||||
onClick: handleShowMoveFileModal,
|
||||
label: (
|
||||
<Flex gap={10}>
|
||||
<span className={styles.deleteIconWrapper}>
|
||||
<SvgIcon name={`move`} width={18}></SvgIcon>
|
||||
</span>
|
||||
<b>{t('move', { keyPrefix: 'common' })}</b>
|
||||
</Flex>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [handleRemoveFile, t]);
|
||||
}, [handleShowMoveFileModal, t, handleRemoveFile]);
|
||||
|
||||
return (
|
||||
<div className={styles.filter}>
|
||||
|
||||
@ -21,24 +21,12 @@ const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => {
|
||||
return onOk(ret.name);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
hideModal();
|
||||
};
|
||||
|
||||
const onFinish = (values: any) => {
|
||||
console.log('Success:', values);
|
||||
};
|
||||
|
||||
const onFinishFailed = (errorInfo: any) => {
|
||||
console.log('Failed:', errorInfo);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('newFolder', { keyPrefix: 'fileManager' })}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
onCancel={hideModal}
|
||||
okButtonProps={{ loading }}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
@ -47,8 +35,6 @@ const FolderCreateModal = ({ visible, hideModal, loading, onOk }: IProps) => {
|
||||
labelCol={{ span: 4 }}
|
||||
wrapperCol={{ span: 20 }}
|
||||
style={{ maxWidth: 600 }}
|
||||
onFinish={onFinish}
|
||||
onFinishFailed={onFinishFailed}
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
|
||||
@ -4,6 +4,7 @@ import {
|
||||
useCreateFolder,
|
||||
useDeleteFile,
|
||||
useFetchParentFolderList,
|
||||
useMoveFile,
|
||||
useRenameFile,
|
||||
useUploadFile,
|
||||
} from '@/hooks/file-manager-hooks';
|
||||
@ -246,3 +247,48 @@ export const useHandleBreadcrumbClick = () => {
|
||||
|
||||
return { handleBreadcrumbClick };
|
||||
};
|
||||
|
||||
export const useHandleMoveFile = (
|
||||
setSelectedRowKeys: (keys: string[]) => void,
|
||||
) => {
|
||||
const {
|
||||
visible: moveFileVisible,
|
||||
hideModal: hideMoveFileModal,
|
||||
showModal: showMoveFileModal,
|
||||
} = useSetModalState();
|
||||
const { moveFile, loading } = useMoveFile();
|
||||
const [sourceFileIds, setSourceFileIds] = useState<string[]>([]);
|
||||
|
||||
const onMoveFileOk = useCallback(
|
||||
async (targetFolderId: string) => {
|
||||
const ret = await moveFile({
|
||||
src_file_ids: sourceFileIds,
|
||||
dest_file_id: targetFolderId,
|
||||
});
|
||||
|
||||
if (ret === 0) {
|
||||
setSelectedRowKeys([]);
|
||||
hideMoveFileModal();
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
[moveFile, hideMoveFileModal, sourceFileIds, setSelectedRowKeys],
|
||||
);
|
||||
|
||||
const handleShowMoveFileModal = useCallback(
|
||||
(ids: string[]) => {
|
||||
setSourceFileIds(ids);
|
||||
showMoveFileModal();
|
||||
},
|
||||
[showMoveFileModal],
|
||||
);
|
||||
|
||||
return {
|
||||
initialValue: '',
|
||||
moveFileLoading: loading,
|
||||
onMoveFileOk,
|
||||
moveFileVisible,
|
||||
hideMoveFileModal,
|
||||
showMoveFileModal: handleShowMoveFileModal,
|
||||
};
|
||||
};
|
||||
|
||||
@ -9,6 +9,7 @@ import {
|
||||
useGetRowSelection,
|
||||
useHandleConnectToKnowledge,
|
||||
useHandleCreateFolder,
|
||||
useHandleMoveFile,
|
||||
useHandleUploadFile,
|
||||
useNavigateToOtherFolder,
|
||||
useRenameCurrentFile,
|
||||
@ -23,6 +24,7 @@ import { getExtension } from '@/utils/document-util';
|
||||
import ConnectToKnowledgeModal from './connect-to-knowledge-modal';
|
||||
import FolderCreateModal from './folder-create-modal';
|
||||
import styles from './index.less';
|
||||
import FileMovingModal from './move-file-modal';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@ -61,7 +63,13 @@ const FileManager = () => {
|
||||
initialValue,
|
||||
connectToKnowledgeLoading,
|
||||
} = useHandleConnectToKnowledge();
|
||||
// const { pagination } = useGetFilesPagination();
|
||||
const {
|
||||
showMoveFileModal,
|
||||
moveFileVisible,
|
||||
onMoveFileOk,
|
||||
hideMoveFileModal,
|
||||
moveFileLoading,
|
||||
} = useHandleMoveFile(setSelectedRowKeys);
|
||||
const { pagination, data, searchString, handleInputChange, loading } =
|
||||
useFetchFileList();
|
||||
const columns: ColumnsType<IFile> = [
|
||||
@ -139,6 +147,7 @@ const FileManager = () => {
|
||||
console.info(record);
|
||||
}}
|
||||
showRenameModal={showFileRenameModal}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
showConnectToKnowledgeModal={showConnectToKnowledgeModal}
|
||||
setSelectedRowKeys={setSelectedRowKeys}
|
||||
></ActionCell>
|
||||
@ -155,6 +164,7 @@ const FileManager = () => {
|
||||
showFolderCreateModal={showFolderCreateModal}
|
||||
showFileUploadModal={showFileUploadModal}
|
||||
setSelectedRowKeys={setSelectedRowKeys}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
></FileToolbar>
|
||||
<Table
|
||||
dataSource={data?.files}
|
||||
@ -191,6 +201,14 @@ const FileManager = () => {
|
||||
onOk={onConnectToKnowledgeOk}
|
||||
loading={connectToKnowledgeLoading}
|
||||
></ConnectToKnowledgeModal>
|
||||
{moveFileVisible && (
|
||||
<FileMovingModal
|
||||
visible={moveFileVisible}
|
||||
hideModal={hideMoveFileModal}
|
||||
onOk={onMoveFileOk}
|
||||
loading={moveFileLoading}
|
||||
></FileMovingModal>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,64 @@
|
||||
import { useFetchPureFileList } from '@/hooks/file-manager-hooks';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
import type { GetProp, TreeSelectProps } from 'antd';
|
||||
import { TreeSelect } from 'antd';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
type DefaultOptionType = GetProp<TreeSelectProps, 'treeData'>[number];
|
||||
|
||||
interface IProps {
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
const AsyncTreeSelect = ({ value, onChange }: IProps) => {
|
||||
const { fetchList } = useFetchPureFileList();
|
||||
const [treeData, setTreeData] = useState<Omit<DefaultOptionType, 'label'>[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
const onLoadData: TreeSelectProps['loadData'] = useCallback(
|
||||
async ({ id }) => {
|
||||
const ret = await fetchList(id);
|
||||
if (ret.retcode === 0) {
|
||||
setTreeData((tree) => {
|
||||
return tree.concat(
|
||||
ret.data.files
|
||||
.filter((x: IFile) => x.type === 'folder')
|
||||
.map((x: IFile) => ({
|
||||
id: x.id,
|
||||
pId: x.parent_id,
|
||||
value: x.id,
|
||||
title: x.name,
|
||||
isLeaf: false,
|
||||
})),
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
[fetchList],
|
||||
);
|
||||
|
||||
const handleChange = (newValue: string) => {
|
||||
onChange?.(newValue);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
onLoadData?.({ id: '', props: '' });
|
||||
}, [onLoadData]);
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
treeDataSimpleMode
|
||||
style={{ width: '100%' }}
|
||||
value={value}
|
||||
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
|
||||
placeholder="Please select"
|
||||
onChange={handleChange}
|
||||
loadData={onLoadData}
|
||||
treeData={treeData}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default AsyncTreeSelect;
|
||||
54
web/src/pages/file-manager/move-file-modal/index.tsx
Normal file
54
web/src/pages/file-manager/move-file-modal/index.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { Form, Modal } from 'antd';
|
||||
import AsyncTreeSelect from './async-tree-select';
|
||||
|
||||
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||
loading: boolean;
|
||||
onOk: (id: string) => void;
|
||||
}
|
||||
|
||||
const FileMovingModal = ({ visible, hideModal, loading, onOk }: IProps) => {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslate('fileManager');
|
||||
|
||||
type FieldType = {
|
||||
name?: string;
|
||||
};
|
||||
|
||||
const handleOk = async () => {
|
||||
const ret = await form.validateFields();
|
||||
|
||||
return onOk(ret.name);
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={t('move', { keyPrefix: 'common' })}
|
||||
open={visible}
|
||||
onOk={handleOk}
|
||||
onCancel={hideModal}
|
||||
okButtonProps={{ loading }}
|
||||
confirmLoading={loading}
|
||||
width={600}
|
||||
>
|
||||
<Form
|
||||
name="basic"
|
||||
labelCol={{ span: 6 }}
|
||||
wrapperCol={{ span: 18 }}
|
||||
autoComplete="off"
|
||||
form={form}
|
||||
>
|
||||
<Form.Item<FieldType>
|
||||
label={t('destinationFolder')}
|
||||
name="name"
|
||||
rules={[{ required: true, message: t('pleaseSelect') }]}
|
||||
>
|
||||
<AsyncTreeSelect></AsyncTreeSelect>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileMovingModal;
|
||||
Reference in New Issue
Block a user