mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-21 21:36:42 +08:00
### What problem does this PR solve? Feat: Deleting files in batches. #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
47
web/src/components/bulk-operate-bar.tsx
Normal file
47
web/src/components/bulk-operate-bar.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import { Button } from '@/components/ui/button';
|
||||||
|
import { Card, CardContent } from '@/components/ui/card';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { ReactNode, useCallback } from 'react';
|
||||||
|
import { ConfirmDeleteDialog } from './confirm-delete-dialog';
|
||||||
|
|
||||||
|
export type BulkOperateItemType = {
|
||||||
|
id: string;
|
||||||
|
label: ReactNode;
|
||||||
|
icon: ReactNode;
|
||||||
|
onClick(): void;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BulkOperateBarProps = { list: BulkOperateItemType[] };
|
||||||
|
|
||||||
|
export function BulkOperateBar({ list }: BulkOperateBarProps) {
|
||||||
|
const isDeleteItem = useCallback((id: string) => {
|
||||||
|
return id === 'delete';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="mb-4">
|
||||||
|
<CardContent className="p-1">
|
||||||
|
<ul className="flex gap-2">
|
||||||
|
{list.map((x) => (
|
||||||
|
<li
|
||||||
|
key={x.id}
|
||||||
|
className={cn({ ['text-text-delete-red']: isDeleteItem(x.id) })}
|
||||||
|
>
|
||||||
|
<ConfirmDeleteDialog
|
||||||
|
hidden={!isDeleteItem(x.id)}
|
||||||
|
onOk={x.onClick}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
variant={'ghost'}
|
||||||
|
onClick={isDeleteItem(x.id) ? () => {} : x.onClick}
|
||||||
|
>
|
||||||
|
{x.icon} {x.label}
|
||||||
|
</Button>
|
||||||
|
</ConfirmDeleteDialog>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -16,15 +16,21 @@ interface IProps {
|
|||||||
title?: string;
|
title?: string;
|
||||||
onOk?: (...args: any[]) => any;
|
onOk?: (...args: any[]) => any;
|
||||||
onCancel?: (...args: any[]) => any;
|
onCancel?: (...args: any[]) => any;
|
||||||
|
hidden?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ConfirmDeleteDialog({
|
export function ConfirmDeleteDialog({
|
||||||
children,
|
children,
|
||||||
title,
|
title,
|
||||||
onOk,
|
onOk,
|
||||||
|
hidden = false,
|
||||||
}: IProps & PropsWithChildren) {
|
}: IProps & PropsWithChildren) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
if (hidden) {
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AlertDialog>
|
<AlertDialog>
|
||||||
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
<AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
|
||||||
|
|||||||
@ -1,9 +1,18 @@
|
|||||||
import { IFolder } from '@/interfaces/database/file-manager';
|
import {
|
||||||
|
IFetchFileListResult,
|
||||||
|
IFolder,
|
||||||
|
} from '@/interfaces/database/file-manager';
|
||||||
import fileManagerService from '@/services/file-manager-service';
|
import fileManagerService from '@/services/file-manager-service';
|
||||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { message } from 'antd';
|
import { useDebounce } from 'ahooks';
|
||||||
|
import { PaginationProps, message } from 'antd';
|
||||||
|
import { useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useSearchParams } from 'umi';
|
import { useSearchParams } from 'umi';
|
||||||
|
import {
|
||||||
|
useGetPaginationWithRouter,
|
||||||
|
useHandleSearchChange,
|
||||||
|
} from './logic-hooks';
|
||||||
import { useSetPaginationParams } from './route-hook';
|
import { useSetPaginationParams } from './route-hook';
|
||||||
|
|
||||||
export const enum FileApiAction {
|
export const enum FileApiAction {
|
||||||
@ -12,6 +21,7 @@ export const enum FileApiAction {
|
|||||||
MoveFile = 'moveFile',
|
MoveFile = 'moveFile',
|
||||||
CreateFolder = 'createFolder',
|
CreateFolder = 'createFolder',
|
||||||
FetchParentFolderList = 'fetchParentFolderList',
|
FetchParentFolderList = 'fetchParentFolderList',
|
||||||
|
DeleteFile = 'deleteFile',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useGetFolderId = () => {
|
export const useGetFolderId = () => {
|
||||||
@ -136,3 +146,85 @@ export const useFetchParentFolderList = () => {
|
|||||||
|
|
||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface IListResult {
|
||||||
|
searchString: string;
|
||||||
|
handleInputChange: React.ChangeEventHandler<HTMLInputElement>;
|
||||||
|
pagination: PaginationProps;
|
||||||
|
setPagination: (pagination: { page: number; pageSize: number }) => void;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useFetchFileList = () => {
|
||||||
|
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||||
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
|
const id = useGetFolderId();
|
||||||
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
|
|
||||||
|
const { data, isFetching: loading } = useQuery<IFetchFileListResult>({
|
||||||
|
queryKey: [
|
||||||
|
FileApiAction.FetchFileList,
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
debouncedSearchString,
|
||||||
|
...pagination,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initialData: { files: [], parent_folder: {} as IFolder, total: 0 },
|
||||||
|
gcTime: 0,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await fileManagerService.listFile({
|
||||||
|
parent_id: id,
|
||||||
|
keywords: debouncedSearchString,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
page: pagination.current,
|
||||||
|
});
|
||||||
|
|
||||||
|
return data?.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
|
(e) => {
|
||||||
|
setPagination({ page: 1 });
|
||||||
|
handleInputChange(e);
|
||||||
|
},
|
||||||
|
[handleInputChange, setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
searchString,
|
||||||
|
handleInputChange: onInputChange,
|
||||||
|
pagination: { ...pagination, total: data?.total },
|
||||||
|
setPagination,
|
||||||
|
loading,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useDeleteFile = () => {
|
||||||
|
const { setPaginationParams } = useSetPaginationParams();
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const {
|
||||||
|
data,
|
||||||
|
isPending: loading,
|
||||||
|
mutateAsync,
|
||||||
|
} = useMutation({
|
||||||
|
mutationKey: [FileApiAction.DeleteFile],
|
||||||
|
mutationFn: async (params: { fileIds: string[]; parentId: string }) => {
|
||||||
|
const { data } = await fileManagerService.removeFile(params);
|
||||||
|
if (data.code === 0) {
|
||||||
|
message.success(t('message.deleted'));
|
||||||
|
setPaginationParams(1); // TODO: There should be a better way to paginate the request list
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [FileApiAction.FetchFileList],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return data.code;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return { data, loading, deleteFile: mutateAsync };
|
||||||
|
};
|
||||||
|
|||||||
@ -31,3 +31,9 @@ export interface IFolder {
|
|||||||
update_time: number;
|
update_time: number;
|
||||||
source_type: string;
|
source_type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IFetchFileListResult = {
|
||||||
|
files: IFile[];
|
||||||
|
parent_folder: IFolder;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import { BulkOperateBar } from '@/components/bulk-operate-bar';
|
||||||
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||||
import ListFilterBar from '@/components/list-filter-bar';
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Upload } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
import { DatasetTable } from './dataset-table';
|
import { DatasetTable } from './dataset-table';
|
||||||
|
import { useBulkOperateDataset } from './use-bulk-operate-dataset';
|
||||||
import { useHandleUploadDocument } from './use-upload-document';
|
import { useHandleUploadDocument } from './use-upload-document';
|
||||||
|
|
||||||
export default function Dataset() {
|
export default function Dataset() {
|
||||||
@ -13,6 +15,8 @@ export default function Dataset() {
|
|||||||
onDocumentUploadOk,
|
onDocumentUploadOk,
|
||||||
documentUploadLoading,
|
documentUploadLoading,
|
||||||
} = useHandleUploadDocument();
|
} = useHandleUploadDocument();
|
||||||
|
const { list } = useBulkOperateDataset();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="p-8">
|
<section className="p-8">
|
||||||
<ListFilterBar title="Files">
|
<ListFilterBar title="Files">
|
||||||
@ -25,8 +29,8 @@ export default function Dataset() {
|
|||||||
Upload file
|
Upload file
|
||||||
</Button>
|
</Button>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
|
<BulkOperateBar list={list}></BulkOperateBar>
|
||||||
<DatasetTable></DatasetTable>
|
<DatasetTable></DatasetTable>
|
||||||
|
|
||||||
{documentUploadVisible && (
|
{documentUploadVisible && (
|
||||||
<FileUploadDialog
|
<FileUploadDialog
|
||||||
hideModal={hideDocumentUploadModal}
|
hideModal={hideDocumentUploadModal}
|
||||||
|
|||||||
41
web/src/pages/dataset/dataset/use-bulk-operate-dataset.tsx
Normal file
41
web/src/pages/dataset/dataset/use-bulk-operate-dataset.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { Ban, CircleCheck, CircleX, Play, Trash2 } from 'lucide-react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
export function useBulkOperateDataset() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
id: 'enabled',
|
||||||
|
label: t('knowledgeDetails.enabled'),
|
||||||
|
icon: <CircleCheck />,
|
||||||
|
onClick: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'disabled',
|
||||||
|
label: t('knowledgeDetails.disabled'),
|
||||||
|
icon: <Ban />,
|
||||||
|
onClick: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'run',
|
||||||
|
label: t('knowledgeDetails.run'),
|
||||||
|
icon: <Play />,
|
||||||
|
onClick: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cancel',
|
||||||
|
label: t('knowledgeDetails.cancel'),
|
||||||
|
icon: <CircleX />,
|
||||||
|
onClick: () => {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delete',
|
||||||
|
label: t('common.delete'),
|
||||||
|
icon: <Trash2 />,
|
||||||
|
onClick: () => {},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return { list };
|
||||||
|
}
|
||||||
@ -5,6 +5,7 @@ import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request';
|
|||||||
import { pick } from 'lodash';
|
import { pick } from 'lodash';
|
||||||
import { Plus } from 'lucide-react';
|
import { Plus } from 'lucide-react';
|
||||||
import { PropsWithChildren, useCallback } from 'react';
|
import { PropsWithChildren, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { DatasetCard } from './dataset-card';
|
import { DatasetCard } from './dataset-card';
|
||||||
import { DatasetCreatingDialog } from './dataset-creating-dialog';
|
import { DatasetCreatingDialog } from './dataset-creating-dialog';
|
||||||
import { DatasetsFilterPopover } from './datasets-filter-popover';
|
import { DatasetsFilterPopover } from './datasets-filter-popover';
|
||||||
@ -13,6 +14,7 @@ import { useSaveKnowledge } from './hooks';
|
|||||||
import { useRenameDataset } from './use-rename-dataset';
|
import { useRenameDataset } from './use-rename-dataset';
|
||||||
|
|
||||||
export default function Datasets() {
|
export default function Datasets() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
visible,
|
visible,
|
||||||
hideModal,
|
hideModal,
|
||||||
@ -63,8 +65,8 @@ export default function Datasets() {
|
|||||||
>
|
>
|
||||||
<Button variant={'tertiary'} size={'sm'} onClick={showModal}>
|
<Button variant={'tertiary'} size={'sm'} onClick={showModal}>
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
{t('knowledgeList.createKnowledgeBase')}
|
||||||
</Button>
|
</Button>
|
||||||
Create dataset
|
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8">
|
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8">
|
||||||
{kbs.map((dataset) => {
|
{kbs.map((dataset) => {
|
||||||
|
|||||||
@ -17,12 +17,12 @@ import {
|
|||||||
UseHandleConnectToKnowledgeReturnType,
|
UseHandleConnectToKnowledgeReturnType,
|
||||||
UseRenameCurrentFileReturnType,
|
UseRenameCurrentFileReturnType,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
import { UseMoveDocumentReturnType } from './use-move-file';
|
import { UseMoveDocumentShowType } from './use-move-file';
|
||||||
|
|
||||||
type IProps = Pick<CellContext<IFile, unknown>, 'row'> &
|
type IProps = Pick<CellContext<IFile, unknown>, 'row'> &
|
||||||
Pick<UseHandleConnectToKnowledgeReturnType, 'showConnectToKnowledgeModal'> &
|
Pick<UseHandleConnectToKnowledgeReturnType, 'showConnectToKnowledgeModal'> &
|
||||||
Pick<UseRenameCurrentFileReturnType, 'showFileRenameModal'> &
|
Pick<UseRenameCurrentFileReturnType, 'showFileRenameModal'> &
|
||||||
Pick<UseMoveDocumentReturnType, 'showMoveFileModal'>;
|
UseMoveDocumentShowType;
|
||||||
|
|
||||||
export function ActionCell({
|
export function ActionCell({
|
||||||
row,
|
row,
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
ColumnFiltersState,
|
ColumnFiltersState,
|
||||||
|
OnChangeFn,
|
||||||
|
RowSelectionState,
|
||||||
SortingState,
|
SortingState,
|
||||||
VisibilityState,
|
VisibilityState,
|
||||||
flexRender,
|
flexRender,
|
||||||
@ -33,7 +35,7 @@ import {
|
|||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import { useFetchFileList } from '@/hooks/file-manager-hooks';
|
import { useFetchFileList } from '@/hooks/use-file-request';
|
||||||
import { IFile } from '@/interfaces/database/file-manager';
|
import { IFile } from '@/interfaces/database/file-manager';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import { formatFileSize } from '@/utils/common-util';
|
import { formatFileSize } from '@/utils/common-util';
|
||||||
@ -44,18 +46,33 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { ActionCell } from './action-cell';
|
import { ActionCell } from './action-cell';
|
||||||
import { useHandleConnectToKnowledge, useRenameCurrentFile } from './hooks';
|
import { useHandleConnectToKnowledge, useRenameCurrentFile } from './hooks';
|
||||||
import { LinkToDatasetDialog } from './link-to-dataset-dialog';
|
import { LinkToDatasetDialog } from './link-to-dataset-dialog';
|
||||||
import { MoveDialog } from './move-dialog';
|
import { UseMoveDocumentShowType } from './use-move-file';
|
||||||
import { useHandleMoveFile } from './use-move-file';
|
|
||||||
import { useNavigateToOtherFolder } from './use-navigate-to-folder';
|
import { useNavigateToOtherFolder } from './use-navigate-to-folder';
|
||||||
|
|
||||||
export function FilesTable() {
|
type FilesTableProps = Pick<
|
||||||
|
ReturnType<typeof useFetchFileList>,
|
||||||
|
'files' | 'loading' | 'pagination' | 'setPagination' | 'total'
|
||||||
|
> & {
|
||||||
|
rowSelection: RowSelectionState;
|
||||||
|
setRowSelection: OnChangeFn<RowSelectionState>;
|
||||||
|
} & UseMoveDocumentShowType;
|
||||||
|
|
||||||
|
export function FilesTable({
|
||||||
|
files,
|
||||||
|
total,
|
||||||
|
pagination,
|
||||||
|
setPagination,
|
||||||
|
loading,
|
||||||
|
rowSelection,
|
||||||
|
setRowSelection,
|
||||||
|
showMoveFileModal,
|
||||||
|
}: FilesTableProps) {
|
||||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
const [columnVisibility, setColumnVisibility] =
|
const [columnVisibility, setColumnVisibility] =
|
||||||
React.useState<VisibilityState>({});
|
React.useState<VisibilityState>({});
|
||||||
const [rowSelection, setRowSelection] = React.useState({});
|
|
||||||
const { t } = useTranslation('translation', {
|
const { t } = useTranslation('translation', {
|
||||||
keyPrefix: 'fileManager',
|
keyPrefix: 'fileManager',
|
||||||
});
|
});
|
||||||
@ -77,16 +94,6 @@ export function FilesTable() {
|
|||||||
fileRenameLoading,
|
fileRenameLoading,
|
||||||
} = useRenameCurrentFile();
|
} = useRenameCurrentFile();
|
||||||
|
|
||||||
const {
|
|
||||||
showMoveFileModal,
|
|
||||||
moveFileVisible,
|
|
||||||
onMoveFileOk,
|
|
||||||
hideMoveFileModal,
|
|
||||||
moveFileLoading,
|
|
||||||
} = useHandleMoveFile();
|
|
||||||
|
|
||||||
const { pagination, data, loading, setPagination } = useFetchFileList();
|
|
||||||
|
|
||||||
const columns: ColumnDef<IFile>[] = [
|
const columns: ColumnDef<IFile>[] = [
|
||||||
{
|
{
|
||||||
id: 'select',
|
id: 'select',
|
||||||
@ -244,7 +251,7 @@ export function FilesTable() {
|
|||||||
}, [pagination]);
|
}, [pagination]);
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useReactTable({
|
||||||
data: data?.files || [],
|
data: files || [],
|
||||||
columns,
|
columns,
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
onColumnFiltersChange: setColumnFilters,
|
onColumnFiltersChange: setColumnFilters,
|
||||||
@ -277,7 +284,7 @@ export function FilesTable() {
|
|||||||
rowSelection,
|
rowSelection,
|
||||||
pagination: currentPagination,
|
pagination: currentPagination,
|
||||||
},
|
},
|
||||||
rowCount: data?.total ?? 0,
|
rowCount: total ?? 0,
|
||||||
debugTable: true,
|
debugTable: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -333,8 +340,8 @@ export function FilesTable() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center justify-end space-x-2 py-4">
|
<div className="flex items-center justify-end space-x-2 py-4">
|
||||||
<div className="flex-1 text-sm text-muted-foreground">
|
<div className="flex-1 text-sm text-muted-foreground">
|
||||||
{table.getFilteredSelectedRowModel().rows.length} of {data?.total}{' '}
|
{table.getFilteredSelectedRowModel().rows.length} of {total} row(s)
|
||||||
row(s) selected.
|
selected.
|
||||||
</div>
|
</div>
|
||||||
<div className="space-x-2">
|
<div className="space-x-2">
|
||||||
<Button
|
<Button
|
||||||
@ -371,13 +378,6 @@ export function FilesTable() {
|
|||||||
loading={fileRenameLoading}
|
loading={fileRenameLoading}
|
||||||
></RenameDialog>
|
></RenameDialog>
|
||||||
)}
|
)}
|
||||||
{moveFileVisible && (
|
|
||||||
<MoveDialog
|
|
||||||
hideModal={hideMoveFileModal}
|
|
||||||
onOk={onMoveFileOk}
|
|
||||||
loading={moveFileLoading}
|
|
||||||
></MoveDialog>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
import { useSetModalState } from '@/hooks/common-hooks';
|
||||||
import {
|
import {
|
||||||
useConnectToKnowledge,
|
useConnectToKnowledge,
|
||||||
useDeleteFile,
|
|
||||||
useRenameFile,
|
useRenameFile,
|
||||||
} from '@/hooks/file-manager-hooks';
|
} from '@/hooks/file-manager-hooks';
|
||||||
import { IFile } from '@/interfaces/database/file-manager';
|
import { IFile } from '@/interfaces/database/file-manager';
|
||||||
@ -77,29 +76,6 @@ export type UseRenameCurrentFileReturnType = ReturnType<
|
|||||||
typeof useRenameCurrentFile
|
typeof useRenameCurrentFile
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export const useHandleDeleteFile = (
|
|
||||||
fileIds: string[],
|
|
||||||
setSelectedRowKeys: (keys: string[]) => void,
|
|
||||||
) => {
|
|
||||||
const { deleteFile: removeDocument } = useDeleteFile();
|
|
||||||
const showDeleteConfirm = useShowDeleteConfirm();
|
|
||||||
const parentId = useGetFolderId();
|
|
||||||
|
|
||||||
const handleRemoveFile = () => {
|
|
||||||
showDeleteConfirm({
|
|
||||||
onOk: async () => {
|
|
||||||
const code = await removeDocument({ fileIds, parentId });
|
|
||||||
if (code === 0) {
|
|
||||||
setSelectedRowKeys([]);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return { handleRemoveFile };
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useHandleConnectToKnowledge = () => {
|
export const useHandleConnectToKnowledge = () => {
|
||||||
const {
|
const {
|
||||||
visible: connectToKnowledgeVisible,
|
visible: connectToKnowledgeVisible,
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import { BulkOperateBar } from '@/components/bulk-operate-bar';
|
||||||
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||||
import ListFilterBar from '@/components/list-filter-bar';
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@ -8,12 +9,19 @@ import {
|
|||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from '@/components/ui/dropdown-menu';
|
} from '@/components/ui/dropdown-menu';
|
||||||
|
import { useFetchFileList } from '@/hooks/use-file-request';
|
||||||
|
import { RowSelectionState } from '@tanstack/react-table';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
import { Upload } from 'lucide-react';
|
import { Upload } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CreateFolderDialog } from './create-folder-dialog';
|
import { CreateFolderDialog } from './create-folder-dialog';
|
||||||
import { FileBreadcrumb } from './file-breadcrumb';
|
import { FileBreadcrumb } from './file-breadcrumb';
|
||||||
import { FilesTable } from './files-table';
|
import { FilesTable } from './files-table';
|
||||||
|
import { MoveDialog } from './move-dialog';
|
||||||
|
import { useBulkOperateFile } from './use-bulk-operate-file';
|
||||||
import { useHandleCreateFolder } from './use-create-folder';
|
import { useHandleCreateFolder } from './use-create-folder';
|
||||||
|
import { useHandleMoveFile } from './use-move-file';
|
||||||
import { useHandleUploadFile } from './use-upload-file';
|
import { useHandleUploadFile } from './use-upload-file';
|
||||||
|
|
||||||
export default function Files() {
|
export default function Files() {
|
||||||
@ -34,6 +42,33 @@ export default function Files() {
|
|||||||
onFolderCreateOk,
|
onFolderCreateOk,
|
||||||
} = useHandleCreateFolder();
|
} = useHandleCreateFolder();
|
||||||
|
|
||||||
|
const {
|
||||||
|
pagination,
|
||||||
|
files,
|
||||||
|
total,
|
||||||
|
loading,
|
||||||
|
setPagination,
|
||||||
|
searchString,
|
||||||
|
handleInputChange,
|
||||||
|
} = useFetchFileList();
|
||||||
|
|
||||||
|
const {
|
||||||
|
showMoveFileModal,
|
||||||
|
moveFileVisible,
|
||||||
|
onMoveFileOk,
|
||||||
|
hideMoveFileModal,
|
||||||
|
moveFileLoading,
|
||||||
|
} = useHandleMoveFile();
|
||||||
|
|
||||||
|
const [rowSelection, setRowSelection] = useState<RowSelectionState>({});
|
||||||
|
|
||||||
|
const { list } = useBulkOperateFile({
|
||||||
|
files,
|
||||||
|
rowSelection,
|
||||||
|
showMoveFileModal,
|
||||||
|
setRowSelection,
|
||||||
|
});
|
||||||
|
|
||||||
const leftPanel = (
|
const leftPanel = (
|
||||||
<div>
|
<div>
|
||||||
<FileBreadcrumb></FileBreadcrumb>
|
<FileBreadcrumb></FileBreadcrumb>
|
||||||
@ -42,7 +77,12 @@ export default function Files() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="p-8">
|
<section className="p-8">
|
||||||
<ListFilterBar leftPanel={leftPanel}>
|
<ListFilterBar
|
||||||
|
leftPanel={leftPanel}
|
||||||
|
searchString={searchString}
|
||||||
|
onSearchChange={handleInputChange}
|
||||||
|
showFilter={false}
|
||||||
|
>
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant={'tertiary'} size={'sm'}>
|
<Button variant={'tertiary'} size={'sm'}>
|
||||||
@ -61,7 +101,17 @@ export default function Files() {
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</ListFilterBar>
|
</ListFilterBar>
|
||||||
<FilesTable></FilesTable>
|
{!isEmpty(rowSelection) && <BulkOperateBar list={list}></BulkOperateBar>}
|
||||||
|
<FilesTable
|
||||||
|
files={files}
|
||||||
|
total={total}
|
||||||
|
pagination={pagination}
|
||||||
|
setPagination={setPagination}
|
||||||
|
loading={loading}
|
||||||
|
rowSelection={rowSelection}
|
||||||
|
setRowSelection={setRowSelection}
|
||||||
|
showMoveFileModal={showMoveFileModal}
|
||||||
|
></FilesTable>
|
||||||
{fileUploadVisible && (
|
{fileUploadVisible && (
|
||||||
<FileUploadDialog
|
<FileUploadDialog
|
||||||
hideModal={hideFileUploadModal}
|
hideModal={hideFileUploadModal}
|
||||||
@ -77,6 +127,14 @@ export default function Files() {
|
|||||||
onOk={onFolderCreateOk}
|
onOk={onFolderCreateOk}
|
||||||
></CreateFolderDialog>
|
></CreateFolderDialog>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{moveFileVisible && (
|
||||||
|
<MoveDialog
|
||||||
|
hideModal={hideMoveFileModal}
|
||||||
|
onOk={onMoveFileOk}
|
||||||
|
loading={moveFileLoading}
|
||||||
|
></MoveDialog>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
53
web/src/pages/files/use-bulk-operate-file.tsx
Normal file
53
web/src/pages/files/use-bulk-operate-file.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { IFile } from '@/interfaces/database/file-manager';
|
||||||
|
import { OnChangeFn, RowSelectionState } from '@tanstack/react-table';
|
||||||
|
import { FolderInput, Trash2 } from 'lucide-react';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useHandleDeleteFile } from './use-delete-file';
|
||||||
|
import { UseMoveDocumentShowType } from './use-move-file';
|
||||||
|
|
||||||
|
export function useBulkOperateFile({
|
||||||
|
files,
|
||||||
|
rowSelection,
|
||||||
|
showMoveFileModal,
|
||||||
|
setRowSelection,
|
||||||
|
}: {
|
||||||
|
files: IFile[];
|
||||||
|
rowSelection: RowSelectionState;
|
||||||
|
setRowSelection: OnChangeFn<RowSelectionState>;
|
||||||
|
} & UseMoveDocumentShowType) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const selectedIds = useMemo(() => {
|
||||||
|
const indexes = Object.keys(rowSelection);
|
||||||
|
return files
|
||||||
|
.filter((x, idx) => indexes.some((y) => Number(y) === idx))
|
||||||
|
.map((x) => x.id);
|
||||||
|
}, [files, rowSelection]);
|
||||||
|
|
||||||
|
const { handleRemoveFile } = useHandleDeleteFile();
|
||||||
|
|
||||||
|
const list = [
|
||||||
|
{
|
||||||
|
id: 'move',
|
||||||
|
label: t('common.move'),
|
||||||
|
icon: <FolderInput />,
|
||||||
|
onClick: () => {
|
||||||
|
showMoveFileModal(selectedIds);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'delete',
|
||||||
|
label: t('common.delete'),
|
||||||
|
icon: <Trash2 />,
|
||||||
|
onClick: async () => {
|
||||||
|
const code = await handleRemoveFile(selectedIds);
|
||||||
|
if (code === 0) {
|
||||||
|
setRowSelection({});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
return { list };
|
||||||
|
}
|
||||||
19
web/src/pages/files/use-delete-file.ts
Normal file
19
web/src/pages/files/use-delete-file.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { useDeleteFile } from '@/hooks/use-file-request';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
import { useGetFolderId } from './hooks';
|
||||||
|
|
||||||
|
export const useHandleDeleteFile = () => {
|
||||||
|
const { deleteFile: removeDocument } = useDeleteFile();
|
||||||
|
const parentId = useGetFolderId();
|
||||||
|
|
||||||
|
const handleRemoveFile = useCallback(
|
||||||
|
async (fileIds: string[]) => {
|
||||||
|
const code = await removeDocument({ fileIds, parentId });
|
||||||
|
|
||||||
|
return code;
|
||||||
|
},
|
||||||
|
[parentId, removeDocument],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { handleRemoveFile };
|
||||||
|
};
|
||||||
@ -2,9 +2,7 @@ import { useSetModalState } from '@/hooks/common-hooks';
|
|||||||
import { useMoveFile } from '@/hooks/use-file-request';
|
import { useMoveFile } from '@/hooks/use-file-request';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
|
||||||
export const useHandleMoveFile = () =>
|
export const useHandleMoveFile = () => {
|
||||||
// setSelectedRowKeys: (keys: string[]) => void,
|
|
||||||
{
|
|
||||||
const {
|
const {
|
||||||
visible: moveFileVisible,
|
visible: moveFileVisible,
|
||||||
hideModal: hideMoveFileModal,
|
hideModal: hideMoveFileModal,
|
||||||
@ -45,6 +43,11 @@ export const useHandleMoveFile = () =>
|
|||||||
hideMoveFileModal,
|
hideMoveFileModal,
|
||||||
showMoveFileModal: handleShowMoveFileModal,
|
showMoveFileModal: handleShowMoveFileModal,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UseMoveDocumentReturnType = ReturnType<typeof useHandleMoveFile>;
|
export type UseMoveDocumentReturnType = ReturnType<typeof useHandleMoveFile>;
|
||||||
|
|
||||||
|
export type UseMoveDocumentShowType = Pick<
|
||||||
|
ReturnType<typeof useHandleMoveFile>,
|
||||||
|
'showMoveFileModal'
|
||||||
|
>;
|
||||||
|
|||||||
Reference in New Issue
Block a user