mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-27 15:54:39 +00:00
Optimize/table view toolbar (#7567)
* optimize table view toolbar * optimize --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
@@ -216,6 +216,7 @@ class DirColumnView extends React.Component {
|
||||
updateCurrentDirent={this.props.updateCurrentDirent}
|
||||
showDirentDetail={this.props.showDirentDetail}
|
||||
updateCurrentPath={this.props.updateCurrentPath}
|
||||
toggleShowDirentToolbar={this.props.toggleShowDirentToolbar}
|
||||
/>
|
||||
)}
|
||||
{currentMode === TAGS_MODE && (
|
||||
|
197
frontend/src/components/toolbar/table-files-toolbar.js
Normal file
197
frontend/src/components/toolbar/table-files-toolbar.js
Normal file
@@ -0,0 +1,197 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ItemDropdownMenu from '../dropdown-menu/item-dropdown-menu';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../metadata/constants';
|
||||
import TextTranslation from '../../utils/text-translation';
|
||||
import { getFileName } from '../../tag/utils/file';
|
||||
import RowUtils from '../../metadata/views/table/utils/row-utils';
|
||||
import { checkIsDir } from '../../metadata/utils/row';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { getFileNameFromRecord } from '../../metadata/utils/cell';
|
||||
import { getColumnByKey } from '../../metadata/utils/column';
|
||||
import { useMetadataStatus } from '../../hooks';
|
||||
import { openInNewTab, openParentFolder } from '../../metadata/utils/file';
|
||||
|
||||
const TableFilesToolbar = ({ repoID }) => {
|
||||
const [selectedRecordIds, setSelectedRecordIds] = useState([]);
|
||||
const metadataRef = useRef([]);
|
||||
const { enableOCR } = useMetadataStatus();
|
||||
|
||||
const canModify = window.sfMetadataContext && window.sfMetadataContext.canModify();
|
||||
const eventBus = window.sfMetadataContext && window.sfMetadataContext.eventBus;
|
||||
|
||||
const records = useMemo(() => selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean) || [], [selectedRecordIds]);
|
||||
|
||||
const unSelect = useCallback(() => {
|
||||
setSelectedRecordIds([]);
|
||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, []);
|
||||
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||
}, [eventBus]);
|
||||
|
||||
const deleteRecords = useCallback(() => {
|
||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DELETE_RECORDS, selectedRecordIds, {
|
||||
success_callback: () => {
|
||||
eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE);
|
||||
}
|
||||
});
|
||||
}, [eventBus, selectedRecordIds]);
|
||||
|
||||
const toggleMoveDialog = useCallback(() => {
|
||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, records[0]);
|
||||
}, [eventBus, records]);
|
||||
|
||||
const checkCanModifyRow = (row) => window.sfMetadataContext.canModifyRow(row);
|
||||
|
||||
const getMenuList = useCallback(() => {
|
||||
const { EXTRACT_FILE_DETAIL, EXTRACT_FILE_DETAILS, OPEN_FILE_IN_NEW_TAB, OPEN_FOLDER_IN_NEW_TAB, OPEN_PARENT_FOLDER, GENERATE_DESCRIPTION, OCR } = TextTranslation;
|
||||
const length = selectedRecordIds.length;
|
||||
const list = [];
|
||||
if (length > 1) {
|
||||
const imageOrVideoRecords = records.filter(record => {
|
||||
const isFolder = checkIsDir(record);
|
||||
if (isFolder) return false;
|
||||
const canModifyRow = checkCanModifyRow(record);
|
||||
if (!canModifyRow) return false;
|
||||
const fileName = getFileName(record);
|
||||
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
|
||||
});
|
||||
if (imageOrVideoRecords.length > 0) {
|
||||
list.push(EXTRACT_FILE_DETAILS);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
const record = records[0];
|
||||
const isFolder = checkIsDir(record);
|
||||
const canModifyRow = checkCanModifyRow(record);
|
||||
|
||||
list.push(isFolder ? OPEN_FOLDER_IN_NEW_TAB : OPEN_FILE_IN_NEW_TAB);
|
||||
list.push(OPEN_PARENT_FOLDER);
|
||||
|
||||
const modifyOptions = [];
|
||||
|
||||
if (modifyOptions.length > 0) {
|
||||
list.push('Divider');
|
||||
list.push(...modifyOptions);
|
||||
}
|
||||
|
||||
if (!isFolder && canModifyRow) {
|
||||
const { columns } = metadataRef.current;
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
const isDescribableFile = canModifyRow && Utils.isDescriptionSupportedFile(fileName);
|
||||
const isImage = Utils.imageCheck(fileName);
|
||||
const isVideo = Utils.videoCheck(fileName);
|
||||
const descriptionColumn = getColumnByKey(columns, PRIVATE_COLUMN_KEY.FILE_DESCRIPTION);
|
||||
const aiOptions = [];
|
||||
|
||||
if (isImage || isVideo) {
|
||||
aiOptions.push(EXTRACT_FILE_DETAIL);
|
||||
}
|
||||
|
||||
if (descriptionColumn && isDescribableFile) {
|
||||
aiOptions.push(GENERATE_DESCRIPTION);
|
||||
}
|
||||
|
||||
if (enableOCR && isImage) {
|
||||
aiOptions.push(OCR);
|
||||
}
|
||||
|
||||
if (aiOptions.length > 0) {
|
||||
list.push('Divider');
|
||||
list.push(...aiOptions);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}, [selectedRecordIds, records, enableOCR]);
|
||||
|
||||
const onMenuItemClick = useCallback((operation) => {
|
||||
const records = selectedRecordIds.map(id => RowUtils.getRecordById(id, metadataRef.current)).filter(Boolean);
|
||||
switch (operation) {
|
||||
case TextTranslation.EXTRACT_FILE_DETAIL.key:
|
||||
case TextTranslation.EXTRACT_FILE_DETAILS.key: {
|
||||
const imageOrVideoRecords = records.filter(record => {
|
||||
const isFolder = checkIsDir(record);
|
||||
if (isFolder) return false;
|
||||
const canModifyRow = checkCanModifyRow(record);
|
||||
if (!canModifyRow) return false;
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
return Utils.imageCheck(fileName) || Utils.videoCheck(fileName);
|
||||
});
|
||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_RECORD_DETAILS, imageOrVideoRecords);
|
||||
break;
|
||||
}
|
||||
case TextTranslation.OPEN_FILE_IN_NEW_TAB.key: {
|
||||
openInNewTab(repoID, records[0]);
|
||||
break;
|
||||
}
|
||||
case TextTranslation.OPEN_FOLDER_IN_NEW_TAB.key: {
|
||||
openParentFolder(records[0]);
|
||||
break;
|
||||
}
|
||||
case TextTranslation.OPEN_PARENT_FOLDER.key: {
|
||||
openParentFolder(records[0]);
|
||||
break;
|
||||
}
|
||||
case TextTranslation.GENERATE_DESCRIPTION.key: {
|
||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.GENERATE_DESCRIPTION, records[0]);
|
||||
break;
|
||||
}
|
||||
case TextTranslation.OCR.key: {
|
||||
eventBus && eventBus.dispatch(EVENT_BUS_TYPE.OCR, records[0]);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [repoID, eventBus, selectedRecordIds]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeSelectedFileIds = eventBus && eventBus.subscribe(EVENT_BUS_TYPE.SELECT_RECORDS, (ids, metadata) => {
|
||||
metadataRef.current = metadata || [];
|
||||
setSelectedRecordIds(ids);
|
||||
});
|
||||
|
||||
return () => {
|
||||
unsubscribeSelectedFileIds && unsubscribeSelectedFileIds();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const length = selectedRecordIds.length;
|
||||
return (
|
||||
<div className="selected-dirents-toolbar">
|
||||
<span className="cur-view-path-btn px-2" onClick={unSelect}>
|
||||
<span className="sf3-font-x-01 sf3-font mr-2" aria-label={gettext('Unselect')} title={gettext('Unselect')}></span>
|
||||
<span>{length}{' '}{gettext('selected')}</span>
|
||||
</span>
|
||||
{(length === 1 && canModify) &&
|
||||
<>
|
||||
<span className="cur-view-path-btn" onClick={toggleMoveDialog}>
|
||||
<span className="sf3-font-move1 sf3-font" aria-label={gettext('Move')} title={gettext('Move')}></span>
|
||||
</span>
|
||||
</>
|
||||
}
|
||||
{canModify &&
|
||||
<span className="cur-view-path-btn" onClick={deleteRecords}>
|
||||
<span className="sf3-font-delete1 sf3-font" aria-label={gettext('Delete')} title={gettext('Delete')}></span>
|
||||
</span>
|
||||
}
|
||||
{length > 0 && (
|
||||
<ItemDropdownMenu
|
||||
item={{}}
|
||||
toggleClass={'cur-view-path-btn sf3-font-more-vertical sf3-font'}
|
||||
onMenuItemClick={onMenuItemClick}
|
||||
getMenuList={getMenuList}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
TableFilesToolbar.propTypes = {
|
||||
repoID: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default TableFilesToolbar;
|
@@ -38,6 +38,14 @@ export const EVENT_BUS_TYPE = {
|
||||
LOCAL_RECORD_DETAIL_CHANGED: 'local_record_detail_changed',
|
||||
LOCAL_COLUMN_DATA_CHANGED: 'local_column_data_changed',
|
||||
FOCUS_CANVAS: 'focus_canvas',
|
||||
UPDATE_SELECTED_RECORD_IDS: 'update_selected_record_ids',
|
||||
SELECT_RECORDS: 'select_records',
|
||||
TOGGLE_MOVE_DIALOG: 'toggle_move_dialog',
|
||||
MOVE_RECORD: 'move_record',
|
||||
DELETE_RECORDS: 'delete_records',
|
||||
UPDATE_RECORD_DETAILS: 'update_record_details',
|
||||
GENERATE_DESCRIPTION: 'generate_description',
|
||||
OCR: 'ocr',
|
||||
|
||||
// metadata
|
||||
RELOAD_DATA: 'reload_data',
|
||||
|
@@ -8,10 +8,12 @@ import { Utils, validateName } from '../../utils/utils';
|
||||
import { useMetadata } from './metadata';
|
||||
import { useCollaborators } from './collaborators';
|
||||
import { getRowById } from '../../components/sf-table/utils/table';
|
||||
import { getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord, getUniqueFileName } from '../utils/cell';
|
||||
import { getFileNameFromRecord, getFileObjIdFromRecord, getParentDirFromRecord, getRecordIdFromRecord, getUniqueFileName } from '../utils/cell';
|
||||
import { gettext } from '../../utils/constants';
|
||||
import { checkIsDir } from '../utils/row';
|
||||
import { useTags } from '../../tag/hooks';
|
||||
import { useMetadataAIOperations } from '../../hooks/metadata-ai-operation';
|
||||
import { getColumnByKey } from '../utils/column';
|
||||
|
||||
const MetadataViewContext = React.createContext(null);
|
||||
|
||||
@@ -23,6 +25,7 @@ export const MetadataViewProvider = ({
|
||||
deleteFilesCallback,
|
||||
moveFileCallback,
|
||||
copyFileCallback,
|
||||
toggleShowDirentToolbar,
|
||||
...params
|
||||
}) => {
|
||||
const { modifyLocalFileTags } = useTags();
|
||||
@@ -35,6 +38,7 @@ export const MetadataViewProvider = ({
|
||||
|
||||
const { collaborators } = useCollaborators();
|
||||
const { isBeingBuilt, setIsBeingBuilt } = useMetadata();
|
||||
const { onOCR, generateDescription, extractFilesDetails } = useMetadataAIOperations();
|
||||
|
||||
const tableChanged = useCallback(() => {
|
||||
setMetadata(storeRef.current.data);
|
||||
@@ -96,7 +100,7 @@ export const MetadataViewProvider = ({
|
||||
storeRef.current.modifyLocalColumnData(columnKey, newData, oldData);
|
||||
}, []);
|
||||
|
||||
const modifyRecords = (rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste = false, { success_callback, fail_callback } = {}) => {
|
||||
const modifyRecords = useCallback((rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste = false, { success_callback, fail_callback } = {}) => {
|
||||
const isRename = storeRef.current.checkIsRenameFileOperator(rowIds, idOriginalRowUpdates);
|
||||
let newName = null;
|
||||
if (isRename) {
|
||||
@@ -140,7 +144,7 @@ export const MetadataViewProvider = ({
|
||||
success_callback && success_callback();
|
||||
},
|
||||
});
|
||||
};
|
||||
}, [metadata, storeRef, renameFileCallback]);
|
||||
|
||||
const deleteRecords = (recordsIds, { success_callback, fail_callback } = {}) => {
|
||||
if (!Array.isArray(recordsIds) || recordsIds.length === 0) return;
|
||||
@@ -284,6 +288,89 @@ export const MetadataViewProvider = ({
|
||||
storeRef.current.updateFileTags(data);
|
||||
}, [storeRef, modifyLocalFileTags]);
|
||||
|
||||
const updateSelectedRecordIds = useCallback((ids) => {
|
||||
toggleShowDirentToolbar(ids.length > 0);
|
||||
setTimeout(() => {
|
||||
window.sfMetadataContext && window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_RECORDS, ids, metadata);
|
||||
}, 0);
|
||||
}, [metadata, toggleShowDirentToolbar]);
|
||||
|
||||
const updateRecordDetails = useCallback((records) => {
|
||||
const recordObjIds = records.map(record => getFileObjIdFromRecord(record));
|
||||
if (recordObjIds.length > 50) {
|
||||
toaster.danger(gettext('Select up to 50 files'));
|
||||
return;
|
||||
}
|
||||
|
||||
const recordIds = records.map(record => getRecordIdFromRecord(record));
|
||||
extractFilesDetails(recordObjIds, {
|
||||
success_callback: ({ details }) => {
|
||||
const captureColumn = getColumnByKey(metadata.columns, PRIVATE_COLUMN_KEY.CAPTURE_TIME);
|
||||
if (!captureColumn) return;
|
||||
let idOldRecordData = {};
|
||||
let idOriginalOldRecordData = {};
|
||||
const captureColumnKey = PRIVATE_COLUMN_KEY.CAPTURE_TIME;
|
||||
records.forEach(record => {
|
||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [captureColumnKey]: record[captureColumnKey] };
|
||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [captureColumnKey]: record[captureColumnKey] };
|
||||
});
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
details.forEach(detail => {
|
||||
const updateRecordId = detail[PRIVATE_COLUMN_KEY.ID];
|
||||
idRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
|
||||
});
|
||||
modifyRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
});
|
||||
}, [metadata, extractFilesDetails, modifyRecords]);
|
||||
|
||||
const updateRecordDescription = useCallback((record) => {
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (!fileName || !parentDir) return;
|
||||
const checkIsDescribableFile = Utils.isDescriptionSupportedFile(fileName);
|
||||
if (!checkIsDescribableFile) return;
|
||||
|
||||
const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
||||
let idOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [descriptionColumnKey]: record[descriptionColumnKey] } };
|
||||
let idOriginalOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [descriptionColumnKey]: record[descriptionColumnKey] } };
|
||||
generateDescription({ parentDir, fileName }, {
|
||||
success_callback: ({ description }) => {
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
modifyRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
});
|
||||
}, [modifyRecords, generateDescription]);
|
||||
|
||||
const ocr = useCallback((record) => {
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (!Utils.imageCheck(fileName)) return;
|
||||
|
||||
const ocrResultColumnKey = PRIVATE_COLUMN_KEY.OCR;
|
||||
let idOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [ocrResultColumnKey]: record[ocrResultColumnKey] } };
|
||||
let idOriginalOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [ocrResultColumnKey]: record[ocrResultColumnKey] } };
|
||||
onOCR({ parentDir, fileName }, {
|
||||
success_callback: ({ ocrResult }) => {
|
||||
if (!ocrResult) return;
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
|
||||
modifyRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
},
|
||||
});
|
||||
}, [modifyRecords, onOCR]);
|
||||
|
||||
// init
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
@@ -316,6 +403,12 @@ export const MetadataViewProvider = ({
|
||||
const unsubscribeModifySettings = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SETTINGS, modifySettings);
|
||||
const unsubscribeLocalRecordChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, updateLocalRecord);
|
||||
const unsubscribeLocalColumnChanged = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_COLUMN_DATA_CHANGED, updateLocalColumnData);
|
||||
const unsubscribeUpdateSelectedRecordIds = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SELECTED_RECORD_IDS, updateSelectedRecordIds);
|
||||
const unsubscribeMoveRecord = eventBus.subscribe(EVENT_BUS_TYPE.MOVE_RECORD, moveRecord);
|
||||
const unsubscribeDeleteRecords = eventBus.subscribe(EVENT_BUS_TYPE.DELETE_RECORDS, deleteRecords);
|
||||
const unsubscribeUpdateDetails = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_RECORD_DETAILS, updateRecordDetails);
|
||||
const unsubscribeUpdateDescription = eventBus.subscribe(EVENT_BUS_TYPE.GENERATE_DESCRIPTION, updateRecordDescription);
|
||||
const unsubscribeOCR = eventBus.subscribe(EVENT_BUS_TYPE.OCR, ocr);
|
||||
|
||||
return () => {
|
||||
if (window.sfMetadataContext) {
|
||||
@@ -335,6 +428,12 @@ export const MetadataViewProvider = ({
|
||||
unsubscribeModifySettings();
|
||||
unsubscribeLocalRecordChanged();
|
||||
unsubscribeLocalColumnChanged();
|
||||
unsubscribeUpdateSelectedRecordIds();
|
||||
unsubscribeMoveRecord();
|
||||
unsubscribeDeleteRecords();
|
||||
unsubscribeUpdateDetails();
|
||||
unsubscribeUpdateDescription();
|
||||
unsubscribeOCR();
|
||||
delayReloadDataTimer.current && clearTimeout(delayReloadDataTimer.current);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -370,6 +469,10 @@ export const MetadataViewProvider = ({
|
||||
updateFileTags,
|
||||
addFolder: params.addFolder,
|
||||
updateCurrentPath: params.updateCurrentPath,
|
||||
updateSelectedRecordIds,
|
||||
updateRecordDetails,
|
||||
updateRecordDescription,
|
||||
ocr,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useRef, useCallback, useMemo } from 'react';
|
||||
import React, { useState, useRef, useCallback, useMemo, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import toaster from '../../../../components/toast';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { useMetadataView } from '../../../hooks/metadata-view';
|
||||
@@ -8,15 +7,12 @@ import { useMetadataStatus } from '../../../../hooks';
|
||||
import { getColumnByKey, isNameColumn } from '../../../utils/column';
|
||||
import { checkIsDir } from '../../../utils/row';
|
||||
import { EVENT_BUS_TYPE, EVENT_BUS_TYPE as METADATA_EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../constants';
|
||||
import { getFileNameFromRecord, getParentDirFromRecord, getFileObjIdFromRecord,
|
||||
getRecordIdFromRecord,
|
||||
} from '../../../utils/cell';
|
||||
import { getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../../utils/cell';
|
||||
import FileTagsDialog from '../../../components/dialog/file-tags-dialog';
|
||||
import { openInNewTab, openParentFolder } from '../../../utils/file';
|
||||
import DeleteFolderDialog from '../../../../components/dialog/delete-folder-dialog';
|
||||
import MoveDirent from '../../../../components/dialog/move-dirent-dialog';
|
||||
import { Dirent } from '../../../../models';
|
||||
import { useMetadataAIOperations } from '../../../../hooks/metadata-ai-operation';
|
||||
import ContextMenuComponent from '../../../components/context-menu';
|
||||
import RowUtils from '../utils/row-utils';
|
||||
|
||||
@@ -38,8 +34,9 @@ const OPERATION = {
|
||||
};
|
||||
|
||||
const ContextMenu = ({
|
||||
isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected, updateRecords,
|
||||
getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, updateFileTags, moveRecord, addFolder
|
||||
isGroupView, selectedRange, selectedPosition, recordMetrics, recordGetterByIndex, onClearSelected, onCopySelected,
|
||||
getTableContentRect, getTableCanvasContainerRect, deleteRecords, selectNone, updateFileTags, moveRecord, addFolder, updateRecordDetails,
|
||||
updateRecordDescription, ocr,
|
||||
}) => {
|
||||
const currentRecord = useRef(null);
|
||||
|
||||
@@ -49,7 +46,6 @@ const ContextMenu = ({
|
||||
|
||||
const { metadata } = useMetadataView();
|
||||
const { enableOCR } = useMetadataStatus();
|
||||
const { onOCR, generateDescription, extractFilesDetails } = useMetadataAIOperations();
|
||||
|
||||
const repoID = window.sfMetadataStore.repoId;
|
||||
|
||||
@@ -223,87 +219,14 @@ const ContextMenu = ({
|
||||
return list;
|
||||
}, [isGroupView, selectedPosition, recordMetrics, selectedRange, metadata, recordGetterByIndex, checkIsDescribableFile, enableOCR, getAbleDeleteRecords]);
|
||||
|
||||
const handelGenerateDescription = useCallback((record) => {
|
||||
if (!checkCanModifyRow(record)) return;
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (!fileName || !parentDir) return;
|
||||
const checkIsDescribableFile = Utils.isDescriptionSupportedFile(fileName);
|
||||
if (!checkIsDescribableFile) return;
|
||||
|
||||
const descriptionColumnKey = PRIVATE_COLUMN_KEY.FILE_DESCRIPTION;
|
||||
let idOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [descriptionColumnKey]: record[descriptionColumnKey] } };
|
||||
let idOriginalOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [descriptionColumnKey]: record[descriptionColumnKey] } };
|
||||
generateDescription({ parentDir, fileName }, {
|
||||
success_callback: ({ description }) => {
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [descriptionColumnKey]: description };
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
});
|
||||
}, [updateRecords, generateDescription]);
|
||||
|
||||
const toggleFileTagsRecord = useCallback((record = null) => {
|
||||
setFileTagsRecord(record);
|
||||
}, []);
|
||||
|
||||
const ocr = useCallback((record) => {
|
||||
if (!checkCanModifyRow(record)) return;
|
||||
const parentDir = getParentDirFromRecord(record);
|
||||
const fileName = getFileNameFromRecord(record);
|
||||
if (!Utils.imageCheck(fileName)) return;
|
||||
|
||||
const ocrResultColumnKey = PRIVATE_COLUMN_KEY.OCR;
|
||||
let idOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [ocrResultColumnKey]: record[ocrResultColumnKey] } };
|
||||
let idOriginalOldRecordData = { [record[PRIVATE_COLUMN_KEY.ID]]: { [ocrResultColumnKey]: record[ocrResultColumnKey] } };
|
||||
onOCR({ parentDir, fileName }, {
|
||||
success_callback: ({ ocrResult }) => {
|
||||
if (!ocrResult) return;
|
||||
const updateRecordId = record[PRIVATE_COLUMN_KEY.ID];
|
||||
const recordIds = [updateRecordId];
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
idRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [ocrResultColumnKey]: ocrResult ? JSON.stringify(ocrResult) : null };
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
},
|
||||
});
|
||||
}, [updateRecords, onOCR]);
|
||||
|
||||
const updateFileDetails = useCallback((records) => {
|
||||
const recordObjIds = records.map(record => getFileObjIdFromRecord(record));
|
||||
if (recordObjIds.length > 50) {
|
||||
toaster.danger(gettext('Select up to 50 files'));
|
||||
return;
|
||||
}
|
||||
|
||||
const recordIds = records.map(record => getRecordIdFromRecord(record));
|
||||
extractFilesDetails(recordObjIds, {
|
||||
success_callback: ({ details }) => {
|
||||
const captureColumn = getColumnByKey(metadata.columns, PRIVATE_COLUMN_KEY.CAPTURE_TIME);
|
||||
if (!captureColumn) return;
|
||||
let idOldRecordData = {};
|
||||
let idOriginalOldRecordData = {};
|
||||
const captureColumnKey = PRIVATE_COLUMN_KEY.CAPTURE_TIME;
|
||||
records.forEach(record => {
|
||||
idOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [captureColumnKey]: record[captureColumnKey] };
|
||||
idOriginalOldRecordData[record[PRIVATE_COLUMN_KEY.ID]] = { [captureColumnKey]: record[captureColumnKey] };
|
||||
});
|
||||
let idRecordUpdates = {};
|
||||
let idOriginalRecordUpdates = {};
|
||||
details.forEach(detail => {
|
||||
const updateRecordId = detail[PRIVATE_COLUMN_KEY.ID];
|
||||
idRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
|
||||
idOriginalRecordUpdates[updateRecordId] = { [captureColumnKey]: detail[captureColumnKey] };
|
||||
});
|
||||
updateRecords({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData });
|
||||
}
|
||||
});
|
||||
}, [metadata, extractFilesDetails, updateRecords]);
|
||||
const handleMoveRecord = useCallback((...params) => {
|
||||
selectNone();
|
||||
moveRecord && moveRecord(...params);
|
||||
}, [moveRecord, selectNone]);
|
||||
|
||||
const handleOptionClick = useCallback((option, event) => {
|
||||
switch (option.value) {
|
||||
@@ -329,7 +252,7 @@ const ContextMenu = ({
|
||||
case OPERATION.GENERATE_DESCRIPTION: {
|
||||
const { record } = option;
|
||||
if (!record) break;
|
||||
handelGenerateDescription(record);
|
||||
updateRecordDescription(record);
|
||||
break;
|
||||
}
|
||||
case OPERATION.FILE_TAGS: {
|
||||
@@ -374,12 +297,12 @@ const ContextMenu = ({
|
||||
}
|
||||
case OPERATION.FILE_DETAILS: {
|
||||
const { records } = option;
|
||||
updateFileDetails(records);
|
||||
updateRecordDetails(records);
|
||||
break;
|
||||
}
|
||||
case OPERATION.FILE_DETAIL: {
|
||||
const { record } = option;
|
||||
updateFileDetails([record]);
|
||||
updateRecordDetails([record]);
|
||||
break;
|
||||
}
|
||||
case OPERATION.MOVE: {
|
||||
@@ -392,7 +315,15 @@ const ContextMenu = ({
|
||||
break;
|
||||
}
|
||||
}
|
||||
}, [repoID, onCopySelected, onClearSelected, handelGenerateDescription, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateFileDetails, toggleFileTagsRecord, toggleMoveDialog]);
|
||||
}, [repoID, onCopySelected, onClearSelected, updateRecordDescription, ocr, deleteRecords, toggleDeleteFolderDialog, selectNone, updateRecordDetails, toggleFileTagsRecord, toggleMoveDialog]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeToggleMoveDialog = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MOVE_DIALOG, toggleMoveDialog);
|
||||
|
||||
return () => {
|
||||
unsubscribeToggleMoveDialog();
|
||||
};
|
||||
}, [toggleMoveDialog]);
|
||||
|
||||
const currentRecordId = getRecordIdFromRecord(currentRecord.current);
|
||||
const fileName = getFileNameFromRecord(currentRecord.current);
|
||||
@@ -424,7 +355,7 @@ const ContextMenu = ({
|
||||
repoID={repoID}
|
||||
dirent={new Dirent({ name: fileName })}
|
||||
isMultipleOperation={false}
|
||||
onItemMove={(...params) => moveRecord(currentRecordId, ...params)}
|
||||
onItemMove={(...params) => handleMoveRecord(currentRecordId, ...params)}
|
||||
onCancelMove={toggleMoveDialog}
|
||||
onAddFolder={addFolder}
|
||||
/>
|
||||
|
@@ -27,7 +27,11 @@ const Table = () => {
|
||||
insertColumn,
|
||||
updateFileTags,
|
||||
moveRecord,
|
||||
addFolder
|
||||
addFolder,
|
||||
updateSelectedRecordIds,
|
||||
updateRecordDetails,
|
||||
updateRecordDescription,
|
||||
ocr,
|
||||
} = useMetadataView();
|
||||
const containerRef = useRef(null);
|
||||
|
||||
@@ -176,6 +180,10 @@ const Table = () => {
|
||||
onGridKeyUp={onHotKeyUp}
|
||||
moveRecord={moveRecord}
|
||||
addFolder={addFolder}
|
||||
updateSelectedRecordIds={updateSelectedRecordIds}
|
||||
updateRecordDetails={updateRecordDetails}
|
||||
updateRecordDescription={updateRecordDescription}
|
||||
ocr={ocr}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
@@ -46,10 +46,6 @@ const TableMain = ({
|
||||
modifyRecord && modifyRecord(rowId, updates, oldRowData, originalUpdates, originalOldRowData);
|
||||
}, [modifyRecord]);
|
||||
|
||||
const updateRecords = useCallback(({ recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste = false }) => {
|
||||
modifyRecords && modifyRecords(recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
|
||||
}, [modifyRecords]);
|
||||
|
||||
const handelInsertColumn = useCallback((name, type, { key, data }) => {
|
||||
insertColumn && insertColumn(name, type, { key, data });
|
||||
}, [insertColumn]);
|
||||
@@ -74,7 +70,6 @@ const TableMain = ({
|
||||
paste={paste}
|
||||
groupOffsetLeft={groupOffset}
|
||||
modifyRecord={updateRecord}
|
||||
updateRecords={updateRecords}
|
||||
deleteRecords={props.deleteRecords}
|
||||
getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange}
|
||||
recordGetterById={recordGetterById}
|
||||
|
@@ -564,6 +564,7 @@ class RecordsBody extends Component {
|
||||
getTableCanvasContainerRect={this.props.getTableCanvasContainerRect}
|
||||
updateFileTags={this.props.updateFileTags}
|
||||
deleteRecords={this.props.deleteRecords}
|
||||
moveRecord={this.props.moveRecord}
|
||||
/>
|
||||
<div className="sf-metadata-result-table" style={{ width: this.props.totalWidth + SEQUENCE_COLUMN_WIDTH }} ref={this.setResultRef}>
|
||||
{this.renderRecords()}
|
||||
@@ -631,6 +632,8 @@ RecordsBody.propTypes = {
|
||||
cacheDownloadFilesProps: PropTypes.func,
|
||||
onCellContextMenu: PropTypes.func,
|
||||
getTableCanvasContainerRect: PropTypes.func,
|
||||
moveRecord: PropTypes.func,
|
||||
addFolder: PropTypes.func,
|
||||
};
|
||||
|
||||
export default RecordsBody;
|
||||
|
@@ -487,6 +487,8 @@ class Records extends Component {
|
||||
this.setState({
|
||||
recordMetrics: updatedRecordMetrics,
|
||||
});
|
||||
const ids = Object.keys(updatedRecordMetrics.idSelectedRecordMap);
|
||||
this.props.updateSelectedRecordIds(ids);
|
||||
};
|
||||
|
||||
selectRecordsById = (recordIds) => {
|
||||
@@ -500,6 +502,7 @@ class Records extends Component {
|
||||
this.setState({
|
||||
recordMetrics: updatedRecordMetrics,
|
||||
});
|
||||
this.props.updateSelectedRecordIds(recordIds);
|
||||
};
|
||||
|
||||
deselectRecord = (recordId) => {
|
||||
@@ -512,6 +515,8 @@ class Records extends Component {
|
||||
this.setState({
|
||||
recordMetrics: updatedRecordMetrics,
|
||||
});
|
||||
const ids = Object.keys(updatedRecordMetrics.idSelectedRecordMap);
|
||||
this.props.updateSelectedRecordIds(ids);
|
||||
};
|
||||
|
||||
selectAllRecords = () => {
|
||||
@@ -539,6 +544,7 @@ class Records extends Component {
|
||||
this.setState({
|
||||
recordMetrics: updatedRecordMetrics,
|
||||
});
|
||||
this.props.updateSelectedRecordIds(selectedRowIds);
|
||||
};
|
||||
|
||||
onDeselectAllRecords = () => {
|
||||
@@ -552,6 +558,7 @@ class Records extends Component {
|
||||
recordMetrics: updatedRecordMetrics,
|
||||
lastRowIdxUiSelected: { groupRecordIndex: -1, recordIndex: -1 },
|
||||
});
|
||||
this.props.updateSelectedRecordIds([]);
|
||||
};
|
||||
|
||||
hasSelectedCell = ({ groupRecordIndex, recordIndex }, selectedPosition) => {
|
||||
@@ -609,6 +616,7 @@ class Records extends Component {
|
||||
const recordId = record._id;
|
||||
if (!RecordMetrics.isRecordSelected(recordId, recordMetrics)) {
|
||||
this.setState({ recordMetrics: this.createRowMetrics() });
|
||||
this.props.updateSelectedRecordIds([]);
|
||||
}
|
||||
|
||||
// select cell when click out of selectRange
|
||||
@@ -633,10 +641,13 @@ class Records extends Component {
|
||||
<ContextMenu
|
||||
isGroupView={isGroupView}
|
||||
recordGetterByIndex={this.props.recordGetterByIndex}
|
||||
updateRecords={this.props.updateRecords}
|
||||
deleteRecords={this.props.deleteRecords}
|
||||
moveRecord={this.props.moveRecord}
|
||||
addFolder={this.props.addFolder}
|
||||
selectNone={this.selectNone}
|
||||
updateRecordDetails={this.props.updateRecordDetails}
|
||||
updateRecordDescription={this.props.updateRecordDescription}
|
||||
ocr={this.props.ocr}
|
||||
/>
|
||||
),
|
||||
hasSelectedRecord: this.hasSelectedRecord(),
|
||||
@@ -651,6 +662,8 @@ class Records extends Component {
|
||||
cacheScrollTop: this.storeScrollTop,
|
||||
onCellContextMenu: this.onCellContextMenu,
|
||||
getTableCanvasContainerRect: this.getTableCanvasContainerRect,
|
||||
moveRecord: this.props.moveRecord,
|
||||
addFolder: this.props.addFolder
|
||||
};
|
||||
if (this.props.isGroupView) {
|
||||
return (
|
||||
@@ -766,7 +779,6 @@ Records.propTypes = {
|
||||
getTableContentRect: PropTypes.func,
|
||||
scrollToLoadMore: PropTypes.func,
|
||||
updateRecord: PropTypes.func,
|
||||
updateRecords: PropTypes.func,
|
||||
recordGetterById: PropTypes.func,
|
||||
recordGetterByIndex: PropTypes.func,
|
||||
loadAll: PropTypes.func,
|
||||
@@ -779,6 +791,7 @@ Records.propTypes = {
|
||||
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
|
||||
moveRecord: PropTypes.func,
|
||||
addFolder: PropTypes.func,
|
||||
updateSelectedRecordIds: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Records;
|
||||
|
@@ -31,6 +31,7 @@ import Detail from '../../components/dirent-detail';
|
||||
import DirColumnView from '../../components/dir-view-mode/dir-column-view';
|
||||
import SelectedDirentsToolbar from '../../components/toolbar/selected-dirents-toolbar';
|
||||
import TagFilesToolbar from '../../components/toolbar/tag-files-toolbar';
|
||||
import TableFilesToolbar from '../../components/toolbar/table-files-toolbar';
|
||||
|
||||
import '../../css/lib-content-view.css';
|
||||
|
||||
@@ -2277,9 +2278,11 @@ class LibContentView extends React.Component {
|
||||
'animation-children': isDirentSelected
|
||||
})}>
|
||||
{isDirentSelected ? (
|
||||
this.state.currentMode === TAGS_MODE ?
|
||||
this.state.currentMode === TAGS_MODE ? (
|
||||
<TagFilesToolbar currentRepoInfo={this.state.currentRepoInfo} />
|
||||
:
|
||||
) : this.state.currentMode === METADATA_MODE ? (
|
||||
<TableFilesToolbar repoID={this.props.repoID} />
|
||||
) : (
|
||||
<SelectedDirentsToolbar
|
||||
repoID={this.props.repoID}
|
||||
path={this.state.path}
|
||||
@@ -2305,6 +2308,7 @@ class LibContentView extends React.Component {
|
||||
onItemConvert={this.onConvertItem}
|
||||
onAddFolder={this.onAddFolder}
|
||||
/>
|
||||
)
|
||||
) : (
|
||||
<CurDirPath
|
||||
currentRepoInfo={this.state.currentRepoInfo}
|
||||
|
@@ -197,7 +197,65 @@ const TextTranslation = {
|
||||
ADD_VIEW: {
|
||||
key: 'ADD_VIEW',
|
||||
value: gettext('Add view')
|
||||
}
|
||||
},
|
||||
|
||||
// table view
|
||||
OPEN_FILE_IN_NEW_TAB: {
|
||||
key: 'Open file in new tab',
|
||||
value: gettext('Open file in new tab'),
|
||||
},
|
||||
OPEN_FOLDER_IN_NEW_TAB: {
|
||||
key: 'Open folder in new tab',
|
||||
value: gettext('Open folder in new tab'),
|
||||
},
|
||||
OPEN_PARENT_FOLDER: {
|
||||
key: 'Open parent folder',
|
||||
value: gettext('Open parent folder'),
|
||||
},
|
||||
EXTRACT_FILE_DETAIL: {
|
||||
key: 'Extract file detail',
|
||||
value: gettext('Extract file detail'),
|
||||
},
|
||||
EXTRACT_FILE_DETAILS: {
|
||||
key: 'Extract file details',
|
||||
value: gettext('Extract file details'),
|
||||
},
|
||||
DELETE_FILE: {
|
||||
key: 'Delete file',
|
||||
value: gettext('Delete file'),
|
||||
},
|
||||
DELETE_FOLDER: {
|
||||
key: 'Delete folder',
|
||||
value: gettext('Delete folder'),
|
||||
},
|
||||
MOVE_FILE: {
|
||||
key: 'Move file',
|
||||
value: gettext('Move file'),
|
||||
},
|
||||
MOVE_FOLDER: {
|
||||
key: 'Move folder',
|
||||
value: gettext('Move folder'),
|
||||
},
|
||||
RENAME_FILE: {
|
||||
key: 'Rename file',
|
||||
value: gettext('Rename file'),
|
||||
},
|
||||
RENAME_FOLDER: {
|
||||
key: 'Rename folder',
|
||||
value: gettext('Rename folder'),
|
||||
},
|
||||
GENERATE_DESCRIPTION: {
|
||||
key: 'Generate description',
|
||||
value: gettext('Generate description'),
|
||||
},
|
||||
GENERATE_TAGS: {
|
||||
key: 'Generate tags',
|
||||
value: gettext('Generate file tags'),
|
||||
},
|
||||
OCR: {
|
||||
key: 'OCR',
|
||||
value: gettext('OCR'),
|
||||
},
|
||||
};
|
||||
|
||||
export default TextTranslation;
|
||||
|
Reference in New Issue
Block a user