diff --git a/frontend/src/components/sf-table/index.js b/frontend/src/components/sf-table/index.js index 2f7e0ecadf..a96377e411 100644 --- a/frontend/src/components/sf-table/index.js +++ b/frontend/src/components/sf-table/index.js @@ -160,6 +160,7 @@ SFTable.propTypes = { loadAll: PropTypes.func, moveRecords: PropTypes.func, renderCustomDraggedRows: PropTypes.func, + updateSelectedRecordIds: PropTypes.func, }; export default SFTable; diff --git a/frontend/src/components/sf-table/table-main/records/index.js b/frontend/src/components/sf-table/table-main/records/index.js index 97065688df..1e4da9d117 100644 --- a/frontend/src/components/sf-table/table-main/records/index.js +++ b/frontend/src/components/sf-table/table-main/records/index.js @@ -411,6 +411,7 @@ class Records extends Component { // clear selected records this.onDeselectAllRecords(); + this.props.updateSelectedRecordIds([]); }; selectCell = (cellPosition) => { @@ -603,6 +604,7 @@ class Records extends Component { const { treeMetrics } = this.state; let updatedTreeMetrics = { ...treeMetrics }; TreeMetrics.selectTreeNodesByKeys(nodesKeys, updatedTreeMetrics); + this.props.updateSelectedRecordIds(TreeMetrics.getSelectedIds(updatedTreeMetrics, this.props.treeNodeKeyRecordIdMap)); this.setState({ treeMetrics: updatedTreeMetrics }); }; @@ -614,6 +616,7 @@ class Records extends Component { let updatedTreeMetrics = { ...treeMetrics }; TreeMetrics.selectTreeNode(nodeKey, updatedTreeMetrics); + this.props.updateSelectedRecordIds(TreeMetrics.getSelectedIds(updatedTreeMetrics, this.props.treeNodeKeyRecordIdMap)); this.setState({ treeMetrics: updatedTreeMetrics }); }; @@ -624,6 +627,7 @@ class Records extends Component { } let updatedTreeMetrics = { ...treeMetrics }; TreeMetrics.deselectTreeNode(nodeKey, updatedTreeMetrics); + this.props.updateSelectedRecordIds(TreeMetrics.getSelectedIds(updatedTreeMetrics, this.props.treeNodeKeyRecordIdMap)); this.setState({ treeMetrics: updatedTreeMetrics }); }; @@ -633,6 +637,7 @@ class Records extends Component { let updatedTreeMetrics = { ...treeMetrics }; const allNodesKeys = recordsTree.map((node) => getTreeNodeKey(node)).filter(Boolean); TreeMetrics.selectTreeNodesByKeys(allNodesKeys, updatedTreeMetrics); + this.props.updateSelectedRecordIds(TreeMetrics.getSelectedIds(updatedTreeMetrics, this.props.treeNodeKeyRecordIdMap)); this.setState({ recordMetrics: updatedTreeMetrics }); }; @@ -1063,6 +1068,7 @@ Records.propTypes = { moveRecord: PropTypes.func, addFolder: PropTypes.func, moveRecords: PropTypes.func, + updateSelectedRecordIds: PropTypes.func, }; export default Records; diff --git a/frontend/src/components/toolbar/all-tags-toolbar.js b/frontend/src/components/toolbar/all-tags-toolbar.js new file mode 100644 index 0000000000..d8cc85581e --- /dev/null +++ b/frontend/src/components/toolbar/all-tags-toolbar.js @@ -0,0 +1,81 @@ +import React, { useCallback, useEffect, useState } from 'react'; +import { gettext } from '../../utils/constants'; +import ItemDropdownMenu from '../dropdown-menu/metadata-item-dropdown-menu'; +import { EVENT_BUS_TYPE } from '../../metadata/constants'; +import TextTranslation from '../../utils/text-translation'; +import EventBus from '../common/event-bus'; + +const AllTagsToolbar = () => { + const [selectedTagIds, setSelectedTagIds] = useState([]); + + const canDelete = window.sfTagsDataContext && window.sfTagsDataContext.checkCanDeleteTag(); + const eventBus = EventBus.getInstance(); + + const unSelect = useCallback(() => { + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.SELECT_NONE); + }, [eventBus]); + + const deleteTags = useCallback(() => { + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.DELETE_TAGS, selectedTagIds); + }, [selectedTagIds, eventBus]); + + const getMenuList = useCallback(() => { + const { MERGE_TAGS, NEW_CHILD_TAG } = TextTranslation; + const list = []; + if (selectedTagIds.length > 1) { + list.push(MERGE_TAGS); + return list; + } + list.push(NEW_CHILD_TAG); + return list; + }, [selectedTagIds]); + + const onMenuItemClick = useCallback((operation, e) => { + switch (operation) { + case TextTranslation.MERGE_TAGS.key: { + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.MERGE_TAGS, selectedTagIds, { left: e.clientX, top: e.clientY }); + break; + } + case TextTranslation.NEW_CHILD_TAG.key: { + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.NEW_SUB_TAG, selectedTagIds[0]); + break; + } + } + }, [eventBus, selectedTagIds]); + + useEffect(() => { + const unsubscribeSelectTags = eventBus && eventBus.subscribe(EVENT_BUS_TYPE.SELECT_TAGS, (ids) => { + setSelectedTagIds(ids); + }); + + return () => { + unsubscribeSelectTags && unsubscribeSelectTags(); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const length = selectedTagIds.length; + return ( +
+ + + {length}{' '}{gettext('selected')} + + {canDelete && + + + + } + {length > 0 && ( + + )} +
+ ); +}; + +export default AllTagsToolbar; diff --git a/frontend/src/components/toolbar/metadata-path-toolbar.js b/frontend/src/components/toolbar/metadata-path-toolbar.js new file mode 100644 index 0000000000..8f33b72698 --- /dev/null +++ b/frontend/src/components/toolbar/metadata-path-toolbar.js @@ -0,0 +1,28 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { TAGS_MODE } from '../dir-view-mode/constants'; +import { ALL_TAGS_ID } from '../../tag/constants'; +import AllTagsToolbar from './all-tags-toolbar'; +import TagFilesToolbar from './tag-files-toolbar'; +import TableFilesToolbar from './table-files-toolbar'; + +const MetadataPathToolbar = ({ repoID, repoInfo, mode, path }) => { + if (mode === TAGS_MODE) { + const isAllTagsView = path.split('/').pop() === ALL_TAGS_ID; + if (isAllTagsView) return ; + + return ; + } + return ( + + ); +}; + +MetadataPathToolbar.propTypes = { + repoID: PropTypes.string.isRequired, + repoInfo: PropTypes.object.isRequired, + mode: PropTypes.string.isRequired, + path: PropTypes.string.isRequired, +}; + +export default MetadataPathToolbar; diff --git a/frontend/src/metadata/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js index f2df3be297..c275e25586 100644 --- a/frontend/src/metadata/constants/event-bus-type.js +++ b/frontend/src/metadata/constants/event-bus-type.js @@ -99,6 +99,12 @@ export const EVENT_BUS_TYPE = { SELECT_TAG_FILES: 'select_tag_files', UNSELECT_TAG_FILES: 'unselect_tag_files', + // tags + SELECT_TAGS: 'select_tags', + DELETE_TAGS: 'delete_tags', + MERGE_TAGS: 'merge_tags', + NEW_SUB_TAG: 'new_sub_tag', + // file FILE_HISTORY: 'file_history', FILE_ACCESS_LOG: 'file_access_log', 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 46451896a0..48ba482f86 100644 --- a/frontend/src/pages/lib-content-view/lib-content-view.js +++ b/frontend/src/pages/lib-content-view/lib-content-view.js @@ -30,8 +30,7 @@ import DirTool from '../../components/cur-dir-path/dir-tool'; 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 MetadataPathToolbar from '../../components/toolbar/metadata-path-toolbar'; import '../../css/lib-content-view.css'; @@ -2193,7 +2192,7 @@ class LibContentView extends React.Component { render() { const { repoID } = this.props; let { currentRepoInfo, userPerm, isCopyMoveProgressDialogShow, isDeleteFolderDialogOpen, errorMsg, - path, usedRepoTags, isDirentSelected } = this.state; + path, usedRepoTags, isDirentSelected, currentMode } = this.state; if (this.state.libNeedDecrypt) { return ( @@ -2278,10 +2277,8 @@ class LibContentView extends React.Component { 'animation-children': isDirentSelected })}> {isDirentSelected ? ( - this.state.currentMode === TAGS_MODE ? ( - - ) : this.state.currentMode === METADATA_MODE ? ( - + currentMode === TAGS_MODE || currentMode === METADATA_MODE ? ( + ) : ( { +const AllTags = ({ updateCurrentPath, toggleShowDirentToolbar, ...params }) => { const [displayTag, setDisplayTag] = useState(''); const [isLoadingMore, setLoadingMore] = useState(false); @@ -99,6 +99,7 @@ const AllTags = ({ updateCurrentPath, ...params }) => { setDisplayTag={onChangeDisplayTag} isLoadingMoreRecords={isLoadingMore} loadMore={loadMore} + toggleShowDirentToolbar={toggleShowDirentToolbar} /> ); diff --git a/frontend/src/tag/views/all-tags/tags-table/index.js b/frontend/src/tag/views/all-tags/tags-table/index.js index 598ee5f292..955eaa7db7 100644 --- a/frontend/src/tag/views/all-tags/tags-table/index.js +++ b/frontend/src/tag/views/all-tags/tags-table/index.js @@ -36,6 +36,7 @@ const TagsTable = ({ modifyColumnWidth: modifyColumnWidthAPI, setDisplayTag, loadMore, + toggleShowDirentToolbar, }) => { const { tagsData, updateTag, deleteTags, addTagLinks, deleteTagLinks, deleteTagsLinks, addChildTag, mergeTags } = useTags(); @@ -46,6 +47,8 @@ const TagsTable = ({ const parentTagIdRef = useRef(null); const mergeTagsSelectorProps = useRef({}); + const eventBus = EventBus.getInstance(); + const table = useMemo(() => { if (!tagsData) { return { @@ -108,10 +111,9 @@ const TagsTable = ({ const onDeleteTags = useCallback((tagsIds) => { deleteTags(tagsIds); - - const eventBus = EventBus.getInstance(); + toggleShowDirentToolbar(false); eventBus.dispatch(TABLE_EVENT_BUS_TYPE.SELECT_NONE); - }, [deleteTags]); + }, [eventBus, deleteTags, toggleShowDirentToolbar]); const onNewSubTag = useCallback((parentTagId) => { parentTagIdRef.current = parentTagId; @@ -260,13 +262,25 @@ const TagsTable = ({ } }, [table, addTagLinks, deleteTagsLinks]); + const updateSelectedTagIds = useCallback((ids) => { + toggleShowDirentToolbar(ids.length > 0); + setTimeout(() => { + eventBus && eventBus.dispatch(EVENT_BUS_TYPE.SELECT_TAGS, ids); + }, 0); + }, [eventBus, toggleShowDirentToolbar]); + useEffect(() => { - const eventBus = EventBus.getInstance(); const unsubscribeUpdateSearchResult = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SEARCH_RESULT, updateSearchResult); + const unsubscribeDeleteTags = eventBus.subscribe(EVENT_BUS_TYPE.DELETE_TAGS, onDeleteTags); + const unsubscribeMergeTags = eventBus.subscribe(EVENT_BUS_TYPE.MERGE_TAGS, onMergeTags); + const unsubscribeNewSubTag = eventBus.subscribe(EVENT_BUS_TYPE.NEW_SUB_TAG, onNewSubTag); return () => { unsubscribeUpdateSearchResult(); + unsubscribeDeleteTags(); + unsubscribeMergeTags(); + unsubscribeNewSubTag(); }; - }, [updateSearchResult]); + }, [eventBus, updateSearchResult, onDeleteTags, onMergeTags, onNewSubTag, updateSelectedTagIds]); return ( <> @@ -296,6 +310,7 @@ const TagsTable = ({ loadMore={loadMore} renderCustomDraggedRows={renderCustomDraggedRows} moveRecords={moveTags} + updateSelectedRecordIds={updateSelectedTagIds} /> {isShowNewSubTagDialog && ( @@ -314,6 +329,7 @@ TagsTable.propTypes = { modifyColumnWidth: PropTypes.func, setDisplayTag: PropTypes.func, loadMore: PropTypes.func, + toggleShowDirentToolbar: PropTypes.func, }; export default TagsTable; diff --git a/frontend/src/utils/text-translation.js b/frontend/src/utils/text-translation.js index 14190172c9..1c33771115 100644 --- a/frontend/src/utils/text-translation.js +++ b/frontend/src/utils/text-translation.js @@ -256,6 +256,16 @@ const TextTranslation = { key: 'OCR', value: gettext('OCR'), }, + + // tag view + MERGE_TAGS: { + key: 'Merge tags', + value: gettext('Merge tags'), + }, + NEW_CHILD_TAG: { + key: 'New child tag', + value: gettext('New child tag'), + }, }; export default TextTranslation;