feat: Move files in file manager #1826 (#1837)

### 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:
balibabu 2024-08-07 10:12:11 +08:00 committed by GitHub
parent 4c2906d6fd
commit c55e9d16da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 292 additions and 24 deletions

View File

@ -0,0 +1,6 @@
<svg t="1722928702193" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6094"
width="200" height="200">
<path
d="M572.330667 597.333333H298.666667v-85.333333h273.664l-77.994667-77.994667L554.666667 373.632 735.701333 554.666667l-60.373333 60.330666L554.666667 735.701333l-60.330667-60.373333L572.330667 597.333333zM533.333333 263.509333H853.333333a85.333333 85.333333 0 0 1 85.333334 85.333334V810.666667a85.333333 85.333333 0 0 1-85.333334 85.333333H170.666667a85.333333 85.333333 0 0 1-85.333334-85.333333V213.333333a85.333333 85.333333 0 0 1 85.333334-85.333333h241.493333a85.333333 85.333333 0 0 1 76.117333 46.72L533.333333 263.509333z m0 85.333334a85.333333 85.333333 0 0 1-76.117333-46.72L412.202667 213.333333H170.666667v597.333334h682.666666V348.842667h-320z"
fill="#666666" p-id="6095"></path>
</svg>

After

Width:  |  Height:  |  Size: 877 B

View File

@ -28,6 +28,23 @@ export interface IListResult {
loading: boolean;
}
export const useFetchPureFileList = () => {
const { mutateAsync, isPending: loading } = useMutation({
mutationKey: ['fetchPureFileList'],
gcTime: 0,
mutationFn: async (parentId: string) => {
const { data } = await fileManagerService.listFile({
parent_id: parentId,
});
return data;
},
});
return { loading, fetchList: mutateAsync };
};
export const useFetchFileList = (): ResponseType<any> & IListResult => {
const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter();
@ -225,3 +242,31 @@ export const useConnectToKnowledge = () => {
return { data, loading, connectFileToKnowledge: mutateAsync };
};
export interface IMoveFileBody {
src_file_ids: string[];
dest_file_id: string; // target folder id
}
export const useMoveFile = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['moveFile'],
mutationFn: async (params: IMoveFileBody) => {
const { data } = await fileManagerService.moveFile(params);
if (data.retcode === 0) {
message.success(t('message.operated'));
queryClient.invalidateQueries({ queryKey: ['fetchFileList'] });
}
return data.retcode;
},
});
return { data, loading, moveFile: mutateAsync };
};

View File

@ -26,6 +26,7 @@ export default {
download: 'Download',
close: 'Close',
preview: 'Preview',
move: 'Move',
},
login: {
login: 'Sign in',
@ -564,6 +565,7 @@ The above is the content you need to summarize.`,
fileError: 'File error',
uploadLimit:
'The file size cannot exceed 10M, and the total number of files cannot exceed 128',
destinationFolder: 'Destination folder',
},
flow: {
cite: 'Cite',

View File

@ -26,6 +26,7 @@ export default {
download: '下載',
close: '關閉',
preview: '預覽',
move: '移動',
},
login: {
login: '登入',
@ -524,6 +525,7 @@ export default {
preview: '預覽',
fileError: '文件錯誤',
uploadLimit: '文件大小不能超過10M文件總數不超過128個',
destinationFolder: '目標資料夾',
},
flow: {
cite: '引用',

View File

@ -26,6 +26,7 @@ export default {
download: '下载',
close: '关闭',
preview: '预览',
move: '移动',
},
login: {
login: '登录',
@ -542,6 +543,7 @@ export default {
preview: '预览',
fileError: '文件错误',
uploadLimit: '文件大小不能超过10M文件总数不超过128个',
destinationFolder: '目标文件夹',
},
flow: {
flow: '工作流',

View File

@ -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

View File

@ -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}>

View File

@ -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}
>

View File

@ -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,
};
};

View File

@ -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>
);
};

View File

@ -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;

View 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;

View File

@ -13,6 +13,7 @@ const {
connectFileToKnowledge,
get_document_file,
getFile,
moveFile,
} = api;
const methods = {
@ -49,6 +50,10 @@ const methods = {
method: 'get',
responseType: 'blob',
},
moveFile: {
url: moveFile,
method: 'post',
},
} as const;
const fileManagerService = registerServer<keyof typeof methods>(

View File

@ -79,6 +79,7 @@ export default {
createFolder: `${api_host}/file/create`,
connectFileToKnowledge: `${api_host}/file2document/convert`,
getFile: `${api_host}/file/get`,
moveFile: `${api_host}/file/mv`,
// system
getSystemVersion: `${api_host}/system/version`,