1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-26 07:22:34 +00:00
Co-authored-by: 杨国璇 <ygx@MacBookPro.lan>
This commit is contained in:
杨国璇
2025-06-23 13:52:03 +08:00
committed by GitHub
parent 90491c187b
commit 07d0d72f64
73 changed files with 1222 additions and 1555 deletions

View File

@@ -0,0 +1,63 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import FileAccessLog from '../../components/dialog/file-access-log';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about access log
const AccessLogContext = React.createContext(null);
export const AccessLogProvider = forwardRef(({ repoID, eventBus, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const pathRef = useRef('');
const nameRef = useRef('');
const handleAccessLog = useCallback((path, name) => {
pathRef.current = path;
nameRef.current = name;
setDialogShow(true);
}, []);
const cancelAccessLog = useCallback(() => {
setDialogShow(false);
pathRef.current = '';
nameRef.current = '';
}, []);
useEffect(() => {
const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.ACCESS_LOG, handleAccessLog);
return () => {
unsubscribeCreateFile();
};
}, [eventBus, handleAccessLog]);
useImperativeHandle(ref, () => ({
handleAccessLog,
cancelAccessLog,
}), [handleAccessLog, cancelAccessLog]);
return (
<AccessLogContext.Provider value={{ eventBus, handleAccessLog }}>
{children}
{isDialogShow && (
<ModalPortal>
<FileAccessLog
repoID={repoID}
filePath={pathRef.current}
fileName={nameRef.current}
toggleDialog={cancelAccessLog}
/>
</ModalPortal>
)}
</AccessLogContext.Provider>
);
});
export const useAccessLogContext = () => {
const context = useContext(AccessLogContext);
if (!context) {
throw new Error('\'AccessLogContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,66 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import CopyDirentDialog from '../../components/dialog/copy-dirent-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about copy file
const CopyFileContext = React.createContext(null);
export const CopyFileProvider = forwardRef(({ eventBus, repoID, repoInfo, onCopy, onCopyItem, onCreateFolder, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const propsRef = useRef({});
const handleCopy = useCallback((path, direntData, isMultipleOperation = false, customCallback = null) => {
propsRef.current = {
path,
isMultipleOperation,
[isMultipleOperation ? 'selectedDirentList' : 'dirent']: direntData,
[isMultipleOperation ? 'onItemsCopy' : 'onItemCopy']: customCallback || (isMultipleOperation ? onCopy : onCopyItem),
};
setDialogShow(true);
}, [onCopy, onCopyItem]);
const cancelCopy = useCallback(() => {
setDialogShow(false);
propsRef.current = {};
}, []);
useEffect(() => {
const unsubscribeCopyFile = eventBus.subscribe(EVENT_BUS_TYPE.COPY_FILE, handleCopy);
return () => {
unsubscribeCopyFile();
};
}, [eventBus, handleCopy]);
useImperativeHandle(ref, () => ({
handleCopy,
cancelCopy,
}), [handleCopy, cancelCopy]);
const renderDialog = useCallback(() => {
if (!isDialogShow) return null;
const { encrypted: repoEncrypted } = repoInfo || {};
return (
<ModalPortal>
<CopyDirentDialog repoID={repoID} repoEncrypted={repoEncrypted} onAddFolder={onCreateFolder} onCancelCopy={cancelCopy} { ...propsRef.current }/>
</ModalPortal>
);
}, [repoID, repoInfo, isDialogShow, onCreateFolder, cancelCopy]);
return (
<CopyFileContext.Provider value={{ eventBus, handleCopy }}>
{children}
{renderDialog()}
</CopyFileContext.Provider>
);
});
export const useCopyFile = () => {
const context = useContext(CopyFileContext);
if (!context) {
throw new Error('\'CopyFileContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,71 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import CreateFileDialog from '../../components/dialog/create-file-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about create file
const CreateFileContext = React.createContext(null);
export const CreateFileProvider = forwardRef(({ eventBus, onCreateFile, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const pathRef = useRef('');
const fileTypeRef = useRef('');
const direntListRef = useRef([]);
const checkDuplicatedName = useCallback((newName) => {
return direntListRef.current.some(object => object.name === newName);
}, []);
const handleCreateFile = useCallback((path, direntList = [], fileType = '') => {
pathRef.current = path;
fileTypeRef.current = fileType;
direntListRef.current = direntList;
setDialogShow(true);
}, []);
const cancelCreateFile = useCallback(() => {
setDialogShow(false);
pathRef.current = '';
fileTypeRef.current = '';
direntListRef.current = [];
}, []);
useEffect(() => {
const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.CREATE_FILE, handleCreateFile);
return () => {
unsubscribeCreateFile();
};
}, [eventBus, handleCreateFile]);
useImperativeHandle(ref, () => ({
handleCreateFile,
cancelCreateFile,
}), [handleCreateFile, cancelCreateFile]);
return (
<CreateFileContext.Provider value={{ eventBus, handleCreateFile }}>
{children}
{isDialogShow && (
<ModalPortal>
<CreateFileDialog
parentPath={pathRef.current}
fileType={fileTypeRef.current}
onAddFile={onCreateFile}
checkDuplicatedName={checkDuplicatedName}
toggleDialog={cancelCreateFile}
/>
</ModalPortal>
)}
</CreateFileContext.Provider>
);
});
export const useCreateFile = () => {
const context = useContext(CreateFileContext);
if (!context) {
throw new Error('\'CreateFileContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,67 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import CreateFolderDialog from '../../components/dialog/create-folder-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about create folder
const CreateFolderContext = React.createContext(null);
export const CreateFolderProvider = forwardRef(({ eventBus, onCreateFolder, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const pathRef = useRef('');
const direntListRef = useRef([]);
const checkDuplicatedName = useCallback((newName) => {
return direntListRef.current.some(object => object.name === newName);
}, []);
const handleCreateFolder = useCallback((path, direntList = []) => {
pathRef.current = path;
direntListRef.current = direntList;
setDialogShow(true);
}, []);
const cancelCreateFolder = useCallback(() => {
setDialogShow(false);
pathRef.current = '';
direntListRef.current = [];
}, []);
useEffect(() => {
const unsubscribeCreateFolder = eventBus.subscribe(EVENT_BUS_TYPE.CREATE_FOLDER, handleCreateFolder);
return () => {
unsubscribeCreateFolder();
};
}, [eventBus, handleCreateFolder]);
useImperativeHandle(ref, () => ({
handleCreateFolder,
cancelCreateFolder,
}), [handleCreateFolder, cancelCreateFolder]);
return (
<CreateFolderContext.Provider value={{ eventBus, handleCreateFolder }}>
{children}
{isDialogShow && (
<ModalPortal>
<CreateFolderDialog
parentPath={pathRef.current}
onAddFolder={onCreateFolder}
checkDuplicatedName={checkDuplicatedName}
addFolderCancel={cancelCreateFolder}
/>
</ModalPortal>
)}
</CreateFolderContext.Provider>
);
});
export const useCreateFolder = () => {
const context = useContext(CreateFolderContext);
if (!context) {
throw new Error('\'CreateFolderContext\' is null');
}
return context;
};

View File

@@ -1,17 +1,17 @@
import React, { useContext, useEffect, useCallback, useState, useRef } from 'react';
import { useGoFileserver, fileServerRoot } from '../utils/constants';
import { Utils } from '../utils/utils';
import { seafileAPI } from '../utils/seafile-api';
import URLDecorator from '../utils/url-decorator';
import ModalPortal from '../components/modal-portal';
import ZipDownloadDialog from '../components/dialog/zip-download-dialog';
import toaster from '../components/toast';
import { EVENT_BUS_TYPE } from '../components/common/event-bus-type';
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import { useGoFileserver, fileServerRoot } from '../../utils/constants';
import { Utils } from '../../utils/utils';
import { seafileAPI } from '../../utils/seafile-api';
import URLDecorator from '../../utils/url-decorator';
import ModalPortal from '../../components/modal-portal';
import ZipDownloadDialog from '../../components/dialog/zip-download-dialog';
import toaster from '../../components/toast';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about download
const DownloadFileContext = React.createContext(null);
export const DownloadFileProvider = ({ repoID, eventBus, children }) => {
export const DownloadFileProvider = forwardRef(({ repoID, eventBus, children }, ref) => {
const [isZipDialogOpen, setZipDialogOpen] = useState();
const pathRef = useRef('');
@@ -56,6 +56,11 @@ export const DownloadFileProvider = ({ repoID, eventBus, children }) => {
};
}, [eventBus, handleDownload]);
useImperativeHandle(ref, () => ({
handleDownload,
cancelDownload,
}), [handleDownload, cancelDownload]);
return (
<DownloadFileContext.Provider value={{ eventBus, handleDownload }}>
{children}
@@ -71,7 +76,7 @@ export const DownloadFileProvider = ({ repoID, eventBus, children }) => {
)}
</DownloadFileContext.Provider>
);
};
});
export const useDownloadFile = () => {
const context = useContext(DownloadFileContext);

View File

@@ -0,0 +1,100 @@
import React, { useCallback, useContext, useRef } from 'react';
import { DownloadFileProvider } from './download';
import { CreateFileProvider } from './create-file';
import { CreateFolderProvider } from './create-folder';
import { RenameFileProvider } from './rename';
import { MoveFileProvider } from './move';
import { CopyFileProvider } from './copy';
import { ShareFileProvider } from './share';
import { LibSubFolderPermissionProvider } from './permission';
import { AccessLogProvider } from './access-log';
// This hook provides content about file operations, like a middleware
const FileOperationsContext = React.createContext(null);
export const FileOperationsProvider = ({
repoID, repoInfo, eventBus, enableDirPrivateShare, isGroupOwnedRepo,
onCreateFolder, onCreateFile,
onRename,
onMove, onMoveItem,
onCopy, onCopyItem,
children
}) => {
const createFileRef = useRef(null);
const createFolderRef = useRef(null);
const renameRef = useRef(null);
const downloadRef = useRef(null);
const moveRef = useRef(null);
const copyRef = useRef(null);
const shareRef = useRef(null);
const permissionRef = useRef(null);
const logRef = useRef(null);
const handleDownload = useCallback((...params) => {
downloadRef.current.handleDownload(...params);
}, []);
const handleCreateFolder = useCallback((...params) => {
createFolderRef.current.handleCreateFolder(...params);
}, []);
const handleCreateFile = useCallback((...params) => {
createFileRef.current.handleCreateFile(...params);
}, []);
const handleMove = useCallback((...params) => {
moveRef.current.handleMove(...params);
}, []);
const handleCopy = useCallback((...params) => {
copyRef.current.handleCopy(...params);
}, []);
const handleShare = useCallback((...params) => {
shareRef.current.handleShare(...params);
}, []);
const handleRename = useCallback((...params) => {
renameRef.current.handleRename(...params);
}, []);
const handleLibSubFolderPermission = useCallback((...params) => {
permissionRef.current.handleLibSubFolderPermission(...params);
}, []);
const handleAccessLog = useCallback((...params) => {
logRef.current.handleAccessLog(...params);
}, []);
return (
<FileOperationsContext.Provider value={{ eventBus, handleDownload, handleCreateFolder, handleCreateFile, handleMove, handleCopy, handleShare, handleRename, handleLibSubFolderPermission, handleAccessLog }}>
<CreateFolderProvider eventBus={eventBus} onCreateFolder={onCreateFolder} ref={createFolderRef}>
<CreateFileProvider eventBus={eventBus} onCreateFile={onCreateFile} ref={createFileRef}>
<RenameFileProvider eventBus={eventBus} onRename={onRename} ref={renameRef}>
<MoveFileProvider eventBus={eventBus} repoID={repoID} repoInfo={repoInfo} onCreateFolder={onCreateFolder} onMove={onMove} onMoveItem={onMoveItem} ref={moveRef}>
<CopyFileProvider eventBus={eventBus} repoID={repoID} repoInfo={repoInfo} onCreateFolder={onCreateFolder} onCopy={onCopy} onCopyItem={onCopyItem} ref={copyRef}>
<DownloadFileProvider repoID={repoID} eventBus={eventBus} ref={downloadRef}>
<ShareFileProvider repoID={repoID} eventBus={eventBus} repoInfo={repoInfo} isGroupOwnedRepo={isGroupOwnedRepo} enableDirPrivateShare={enableDirPrivateShare} ref={shareRef}>
<LibSubFolderPermissionProvider repoID={repoID} eventBus={eventBus} isGroupOwnedRepo={isGroupOwnedRepo} ref={permissionRef}>
<AccessLogProvider repoID={repoID} eventBus={eventBus} ref={logRef}>
{children}
</AccessLogProvider>
</LibSubFolderPermissionProvider>
</ShareFileProvider>
</DownloadFileProvider>
</CopyFileProvider>
</MoveFileProvider>
</RenameFileProvider>
</CreateFileProvider>
</CreateFolderProvider>
</FileOperationsContext.Provider>
);
};
export const useFileOperations = () => {
const context = useContext(FileOperationsContext);
if (!context) {
throw new Error('\'FileOperationsContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,65 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import MoveDirentDialog from '../../components/dialog/move-dirent-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about move file
const MoveFileContext = React.createContext(null);
export const MoveFileProvider = forwardRef(({ eventBus, repoID, repoInfo, onMove, onMoveItem, onCreateFolder, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const propsRef = useRef({});
const handleMove = useCallback((path, direntData, isMultipleOperation = false, customCallback) => {
propsRef.current = {
path,
isMultipleOperation,
[isMultipleOperation ? 'selectedDirentList' : 'dirent']: direntData,
[isMultipleOperation ? 'onItemsMove' : 'onItemMove']: customCallback || (isMultipleOperation ? onMove : onMoveItem),
};
setDialogShow(true);
}, [onMove, onMoveItem]);
const cancelMove = useCallback(() => {
setDialogShow(false);
propsRef.current = {};
}, []);
const renderDialog = useCallback(() => {
if (!isDialogShow) return null;
const { encrypted: repoEncrypted } = repoInfo || {};
return (
<ModalPortal>
<MoveDirentDialog repoID={repoID} repoEncrypted={repoEncrypted} onAddFolder={onCreateFolder} onCancelMove={cancelMove} { ...propsRef.current }/>
</ModalPortal>
);
}, [repoID, repoInfo, isDialogShow, onCreateFolder, cancelMove]);
useEffect(() => {
const unsubscribeMoveFile = eventBus.subscribe(EVENT_BUS_TYPE.MOVE_FILE, handleMove);
return () => {
unsubscribeMoveFile();
};
}, [eventBus, handleMove]);
useImperativeHandle(ref, () => ({
handleMove,
cancelMove,
}), [handleMove, cancelMove]);
return (
<MoveFileContext.Provider value={{ eventBus, handleMove }}>
{children}
{renderDialog()}
</MoveFileContext.Provider>
);
});
export const useMoveFile = () => {
const context = useContext(MoveFileContext);
if (!context) {
throw new Error('\'MoveFileContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,64 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import LibSubFolderPermissionDialog from '../../components/dialog/lib-sub-folder-permission-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about lib sub-folder permission
const LibSubFolderPermissionContext = React.createContext(null);
export const LibSubFolderPermissionProvider = forwardRef(({ repoID, eventBus, isGroupOwnedRepo, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const pathRef = useRef('');
const nameRef = useRef('');
const handleLibSubFolderPermission = useCallback((path, name) => {
pathRef.current = path;
nameRef.current = name;
setDialogShow(true);
}, []);
const cancelLibSubFolderPermission = useCallback(() => {
setDialogShow(false);
pathRef.current = '';
nameRef.current = '';
}, []);
useEffect(() => {
const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.PERMISSION, handleLibSubFolderPermission);
return () => {
unsubscribeCreateFile();
};
}, [eventBus, handleLibSubFolderPermission]);
useImperativeHandle(ref, () => ({
handleLibSubFolderPermission,
cancelLibSubFolderPermission,
}), [handleLibSubFolderPermission, cancelLibSubFolderPermission]);
return (
<LibSubFolderPermissionContext.Provider value={{ eventBus, handleLibSubFolderPermission }}>
{children}
{isDialogShow && (
<ModalPortal>
<LibSubFolderPermissionDialog
isDepartmentRepo={isGroupOwnedRepo}
repoID={repoID}
folderPath={pathRef.current}
folderName={nameRef.current}
toggleDialog={cancelLibSubFolderPermission}
/>
</ModalPortal>
)}
</LibSubFolderPermissionContext.Provider>
);
});
export const useLibSubFolderPermissionContext = () => {
const context = useContext(LibSubFolderPermissionContext);
if (!context) {
throw new Error('\'LibSubFolderPermissionContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,69 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import RenameDialog from '../../components/dialog/rename-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about rename file
const RenameFileContext = React.createContext(null);
export const RenameFileProvider = forwardRef(({ eventBus, onRename, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const direntRef = useRef('');
const direntListRef = useRef([]);
const callbackRef = useRef(() => {});
const checkDuplicatedName = useCallback((newName) => {
return direntListRef.current.some(object => object.name === newName);
}, []);
const handleRename = useCallback((dirent, direntList = [], callback) => {
direntRef.current = dirent;
direntListRef.current = direntList;
callbackRef.current = callback || ((newName) => onRename(dirent, newName));
setDialogShow(true);
}, [onRename]);
const cancelRename = useCallback(() => {
setDialogShow(false);
direntRef.current = null;
direntListRef.current = [];
}, []);
useEffect(() => {
const unsubscribeCreateFile = eventBus.subscribe(EVENT_BUS_TYPE.RENAME_FILE, handleRename);
return () => {
unsubscribeCreateFile();
};
}, [eventBus, handleRename]);
useImperativeHandle(ref, () => ({
handleRename,
cancelRename,
}), [handleRename, cancelRename]);
return (
<RenameFileContext.Provider value={{ eventBus, handleRename }}>
{children}
{isDialogShow && (
<ModalPortal>
<RenameDialog
dirent={direntRef.current}
onRename={callbackRef.current}
checkDuplicatedName={checkDuplicatedName}
toggleCancel={cancelRename}
/>
</ModalPortal>
)}
</RenameFileContext.Provider>
);
});
export const useRenameFile = () => {
const context = useContext(RenameFileContext);
if (!context) {
throw new Error('\'RenameFileContext\' is null');
}
return context;
};

View File

@@ -0,0 +1,68 @@
import React, { useContext, useEffect, useCallback, useState, useRef, forwardRef, useImperativeHandle } from 'react';
import ModalPortal from '../../components/modal-portal';
import ShareDialog from '../../components/dialog/share-dialog';
import { EVENT_BUS_TYPE } from '../../components/common/event-bus-type';
// This hook provides content about share
const ShareFileContext = React.createContext(null);
export const ShareFileProvider = forwardRef(({ repoID, eventBus, repoInfo, enableDirPrivateShare, isGroupOwnedRepo, children }, ref) => {
const [isDialogShow, setDialogShow] = useState();
const pathRef = useRef('');
const direntRef = useRef([]);
const handleShare = useCallback((path, dirent) => {
pathRef.current = path;
direntRef.current = dirent;
setDialogShow(true);
}, []);
const cancelShare = useCallback(() => {
setDialogShow(false);
pathRef.current = '';
direntRef.current = [];
}, []);
useEffect(() => {
const unsubscribeShareFile = eventBus.subscribe(EVENT_BUS_TYPE.SHARE_FILE, handleShare);
return () => {
unsubscribeShareFile();
};
}, [eventBus, handleShare]);
useImperativeHandle(ref, () => ({
handleShare,
cancelShare,
}), [handleShare, cancelShare]);
return (
<ShareFileContext.Provider value={{ eventBus, handleShare }}>
{children}
{isDialogShow && (
<ModalPortal>
<ShareDialog
itemType={direntRef.current.type}
itemName={direntRef.current.name}
itemPath={pathRef.current}
userPerm={direntRef.current.permission || repoInfo.permission }
repoID={repoID}
repoEncrypted={false}
enableDirPrivateShare={enableDirPrivateShare}
isGroupOwnedRepo={isGroupOwnedRepo}
toggleDialog={cancelShare}
/>
</ModalPortal>
)}
</ShareFileContext.Provider>
);
});
export const useShareFile = () => {
const context = useContext(ShareFileContext);
if (!context) {
throw new Error('\'ShareFileContext\' is null');
}
return context;
};

View File

@@ -1,3 +1,6 @@
export { DownloadFileProvider } from './download-file';
export { MetadataMiddlewareProvider } from './metadata-middleware';
export { FileOperationsProvider, useFileOperations } from './file-operations';
export { MetadataStatusProvider, useMetadataStatus } from './metadata-status';
export {
MetadataMiddlewareProvider,
MetadataAIOperationsProvider, useMetadataAIOperations,
} from '../metadata';

View File

@@ -1,174 +0,0 @@
import React, { useContext, useCallback, useMemo, useState, useRef } from 'react';
import metadataAPI from '../metadata/api';
import { Utils } from '../utils/utils';
import toaster from '../components/toast';
import { PRIVATE_COLUMN_KEY, EVENT_BUS_TYPE } from '../metadata/constants';
import { gettext, lang } from '../utils/constants';
import { OCRResultPopover } from '../metadata/components/popover';
import FileTagsDialog from '../metadata/components/dialog/file-tags-dialog';
// This hook provides content related to metadata ai operation
const MetadataAIOperationsContext = React.createContext(null);
export const MetadataAIOperationsProvider = ({
repoID,
enableMetadata = false,
enableOCR = false,
enableTags = false,
tagsLang,
repoInfo,
children
}) => {
const [isOcrResultDialogShow, setOcrResultDialogShow] = useState(false);
const [isFileTagsDialogShow, setFileTagsDialogShow] = useState(false);
const recordRef = useRef(null);
const targetRef = useRef(null);
const opCallBack = useRef(null);
const permission = useMemo(() => repoInfo.permission !== 'admin' && repoInfo.permission !== 'rw' ? 'r' : 'rw', [repoInfo]);
const canModify = useMemo(() => permission === 'rw', [permission]);
const closeFileTagsDialog = useCallback(() => {
recordRef.current = null;
opCallBack.current = null;
setFileTagsDialogShow(false);
}, []);
const closeOcrResultDialog = useCallback(() => {
recordRef.current = null;
targetRef.current = null;
opCallBack.current = null;
setOcrResultDialogShow(false);
}, []);
const onOCR = useCallback((record, { success_callback }, target) => {
targetRef.current = target;
recordRef.current = record;
opCallBack.current = success_callback;
setOcrResultDialogShow(true);
}, []);
const onOCRByImageDialog = useCallback(({ parentDir, fileName } = {}, target) => {
recordRef.current = {
[PRIVATE_COLUMN_KEY.PARENT_DIR]: parentDir,
[PRIVATE_COLUMN_KEY.FILE_NAME]: fileName,
};
targetRef.current = target;
opCallBack.current = (description) => {
const update = { [PRIVATE_COLUMN_KEY.FILE_DESCRIPTION]: description };
metadataAPI.modifyRecord(repoID, { parentDir, fileName }, update).then(res => {
const eventBus = window?.sfMetadataContext?.eventBus;
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, { parentDir, fileName }, update);
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_RECORD_DETAIL_CHANGED, { parentDir, fileName }, update);
});
};
setOcrResultDialogShow(true);
}, [repoID]);
const generateDescription = useCallback(({ parentDir, fileName }, { success_callback, fail_callback } = {}) => {
const filePath = Utils.joinPath(parentDir, fileName);
const isImage = Utils.imageCheck(fileName);
let APIName = '';
if (Utils.isDescriptionSupportedFile(fileName)) {
APIName = isImage ? 'imageCaption' : 'generateDescription';
}
if (!APIName) return;
const inProgressToaster = toaster.notifyInProgress(gettext('Generating description by AI...'), { duration: null });
metadataAPI[APIName](repoID, filePath, lang).then(res => {
const description = res?.data?.summary || res.data.desc || '';
inProgressToaster.close();
toaster.success(gettext('Description generated'));
success_callback && success_callback({ parentDir, fileName, description });
}).catch(error => {
inProgressToaster.close();
const errorMessage = gettext('Failed to generate description');
toaster.danger(errorMessage);
fail_callback && fail_callback();
});
}, [repoID]);
const extractFilesDetails = useCallback((objIds, { success_callback, fail_callback } = {}) => {
const inProgressToaster = toaster.notifyInProgress(gettext('Extracting file details by AI...'), { duration: null });
metadataAPI.extractFileDetails(repoID, objIds).then(res => {
const details = res?.data?.details || [];
inProgressToaster.close();
toaster.success(gettext('File details extracted'));
success_callback && success_callback({ details });
}).catch(error => {
inProgressToaster.close();
const errorMessage = gettext('Failed to extract file details');
toaster.danger(errorMessage);
fail_callback && fail_callback();
});
}, [repoID]);
const extractFileDetails = useCallback((objId, { success_callback, fail_callback } = {}) => {
extractFilesDetails([objId], {
success_callback: ({ details }) => {
success_callback && success_callback({ detail: details[0] });
},
fail_callback
});
}, [extractFilesDetails]);
const faceRecognition = useCallback((objIds, { success_callback, fail_callback } = {}) => {
const inProgressToaster = toaster.notifyInProgress(gettext('Detecting faces by AI...'), { duration: null });
metadataAPI.recognizeFaces(repoID, objIds).then(res => {
inProgressToaster.close();
toaster.success(gettext('Faces detected'));
success_callback && success_callback();
}).catch(error => {
inProgressToaster.close();
const errorMessage = gettext('Failed to detect faces');
toaster.danger(errorMessage);
fail_callback && fail_callback();
});
}, [repoID]);
const generateFileTags = useCallback((record, { success_callback }) => {
recordRef.current = record;
opCallBack.current = success_callback;
setFileTagsDialogShow(true);
}, []);
return (
<MetadataAIOperationsContext.Provider value={{
enableMetadata,
enableOCR,
enableTags,
tagsLang,
canModify,
onOCR,
onOCRByImageDialog,
generateDescription,
extractFilesDetails,
extractFileDetails,
faceRecognition,
generateFileTags,
}}>
{children}
{isFileTagsDialogShow && (
<FileTagsDialog record={recordRef.current} onToggle={closeFileTagsDialog} onSubmit={opCallBack.current} />
)}
{isOcrResultDialogShow && (
<OCRResultPopover
repoID={repoID}
target={targetRef.current}
record={recordRef.current}
onToggle={closeOcrResultDialog}
saveToDescription={opCallBack.current}
/>
)}
</MetadataAIOperationsContext.Provider>
);
};
export const useMetadataAIOperations = () => {
const context = useContext(MetadataAIOperationsContext);
if (!context) {
throw new Error('\'MetadataAIOperationsContext\' is null');
}
return context;
};

View File

@@ -1,38 +0,0 @@
import React, { useContext } from 'react';
import { MetadataAIOperationsProvider } from './metadata-ai-operation';
import { TagsProvider } from '../tag/hooks';
import { CollaboratorsProvider } from '../metadata';
import { useMetadataStatus } from './metadata-status';
const MetadataMiddlewareContext = React.createContext(null);
export const MetadataMiddlewareProvider = ({ repoID, currentPath, repoInfo, selectTagsView, tagsChangedCallback, children }) => {
const { enableMetadata, enableOCR, enableTags, tagsLang } = useMetadataStatus();
return (
<MetadataMiddlewareContext.Provider value={{}}>
<CollaboratorsProvider repoID={repoID}>
<TagsProvider repoID={repoID} currentPath={currentPath} repoInfo={repoInfo} selectTagsView={selectTagsView} tagsChangedCallback={tagsChangedCallback}>
<MetadataAIOperationsProvider
repoID={repoID}
enableMetadata={enableMetadata}
enableOCR={enableOCR}
enableTags={enableTags}
tagsLang={tagsLang}
repoInfo={repoInfo}
>
{children}
</MetadataAIOperationsProvider>
</TagsProvider>
</CollaboratorsProvider>
</MetadataMiddlewareContext.Provider>
);
};
export const useMetadataMiddleware = () => {
const context = useContext(MetadataMiddlewareContext);
if (!context) {
throw new Error('\'MetadataMiddlewareContext\' is null');
}
return context;
};