diff --git a/frontend/src/components/dir-view-mode/dir-column-view.js b/frontend/src/components/dir-view-mode/dir-column-view.js index 5294a7c6d0..aef5a78696 100644 --- a/frontend/src/components/dir-view-mode/dir-column-view.js +++ b/frontend/src/components/dir-view-mode/dir-column-view.js @@ -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 && ( diff --git a/frontend/src/components/toolbar/table-files-toolbar.js b/frontend/src/components/toolbar/table-files-toolbar.js new file mode 100644 index 0000000000..e9dc4b563d --- /dev/null +++ b/frontend/src/components/toolbar/table-files-toolbar.js @@ -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 ( +
+ + + {length}{' '}{gettext('selected')} + + {(length === 1 && canModify) && + <> + + + + + } + {canModify && + + + + } + {length > 0 && ( + + )} +
+ ); +}; + +TableFilesToolbar.propTypes = { + repoID: PropTypes.string.isRequired, +}; + +export default TableFilesToolbar; diff --git a/frontend/src/metadata/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js index c0ceb1814c..f2df3be297 100644 --- a/frontend/src/metadata/constants/event-bus-type.js +++ b/frontend/src/metadata/constants/event-bus-type.js @@ -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', diff --git a/frontend/src/metadata/hooks/metadata-view.js b/frontend/src/metadata/hooks/metadata-view.js index f663a2281e..52e9ffdd21 100644 --- a/frontend/src/metadata/hooks/metadata-view.js +++ b/frontend/src/metadata/hooks/metadata-view.js @@ -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} diff --git a/frontend/src/metadata/views/table/context-menu/index.js b/frontend/src/metadata/views/table/context-menu/index.js index 87b03a69c7..98df10fa79 100644 --- a/frontend/src/metadata/views/table/context-menu/index.js +++ b/frontend/src/metadata/views/table/context-menu/index.js @@ -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} /> diff --git a/frontend/src/metadata/views/table/index.js b/frontend/src/metadata/views/table/index.js index 026c60d788..ce25223e26 100644 --- a/frontend/src/metadata/views/table/index.js +++ b/frontend/src/metadata/views/table/index.js @@ -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} /> ); diff --git a/frontend/src/metadata/views/table/table-main/index.js b/frontend/src/metadata/views/table/table-main/index.js index d1437cd2f5..b937ddf172 100644 --- a/frontend/src/metadata/views/table/table-main/index.js +++ b/frontend/src/metadata/views/table/table-main/index.js @@ -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} diff --git a/frontend/src/metadata/views/table/table-main/records/body.js b/frontend/src/metadata/views/table/table-main/records/body.js index 8589d1d987..a8bd9353c0 100644 --- a/frontend/src/metadata/views/table/table-main/records/body.js +++ b/frontend/src/metadata/views/table/table-main/records/body.js @@ -564,6 +564,7 @@ class RecordsBody extends Component { getTableCanvasContainerRect={this.props.getTableCanvasContainerRect} updateFileTags={this.props.updateFileTags} deleteRecords={this.props.deleteRecords} + moveRecord={this.props.moveRecord} />
{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; diff --git a/frontend/src/metadata/views/table/table-main/records/index.js b/frontend/src/metadata/views/table/table-main/records/index.js index a2465b45b6..02879c105b 100644 --- a/frontend/src/metadata/views/table/table-main/records/index.js +++ b/frontend/src/metadata/views/table/table-main/records/index.js @@ -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 { ), 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; diff --git a/frontend/src/pages/lib-content-view/lib-content-view.js b/frontend/src/pages/lib-content-view/lib-content-view.js index 4a9a5a2664..46451896a0 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -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 ? ( - : + ) : this.state.currentMode === METADATA_MODE ? ( + + ) : ( + ) ) : (