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;