@@ -100,10 +108,12 @@ ActionsCell.propTypes = {
isLocked: PropTypes.bool,
isSelected: PropTypes.bool,
isLastFrozenCell: PropTypes.bool,
+ recordDraggable: PropTypes.bool,
recordId: PropTypes.string,
index: PropTypes.number,
height: PropTypes.number,
onSelectRecord: PropTypes.func,
+ handleDragStart: PropTypes.func,
};
export default ActionsCell;
diff --git a/frontend/src/components/sf-table/table-main/records/record/index.css b/frontend/src/components/sf-table/table-main/records/record/index.css
index 99b4f9c10e..e12850bdd0 100644
--- a/frontend/src/components/sf-table/table-main/records/record/index.css
+++ b/frontend/src/components/sf-table/table-main/records/record/index.css
@@ -24,3 +24,62 @@
.frozen-columns {
background-color: #fff;
}
+
+.sf-table-result-content .sf-table-row.can-drop-tip .sf-table-cell {
+ background-color: rgb(200, 220, 240) !important;
+}
+
+.sf-table-result-container.record-draggable .sf-table-row .drag-handler {
+ position: absolute;
+ left: 0;
+ top: 1px;
+ height: 30px;
+ width: 12px;
+}
+
+.sf-table-result-container.record-draggable .sf-table-row:hover .drag-handler {
+ background-image: url(../../../../../../../media/img/grippy_large.png);
+ background-repeat: no-repeat;
+ cursor: grab;
+}
+
+.sf-table-record-drag-layer {
+ position: fixed;
+ z-index: -1;
+ left: -9999;
+ top: 0;
+ pointer-events: none;
+ cursor: grabbing;
+}
+
+.sf-table-record-drag-layer .record-drag-layer-table {
+ width: auto;
+}
+
+.sf-table-record-drag-layer .rdg-dragged-record {
+ display: flex;
+ min-height: 32px;
+ height: auto;
+ padding: 0 1rem;
+ border-left: 1px solid #ddd;
+ border-bottom: 1px solid #ddd;
+ border-right: 1px solid #ddd;
+ background-color: #fff;
+}
+
+.sf-table-record-drag-layer .rdg-dragged-record:first-child {
+ border-top: 1px solid #ddd;
+}
+
+.sf-table-record-drag-layer .rdg-dragged-record .rdg-dragged-record-cell {
+ display: flex;
+ align-items: center;
+ min-width: 80px;
+ height: 32px;
+ padding: 8px;
+ border-bottom: none;
+}
+
+.sf-table-record-drag-layer .rdg-dragged-record .rdg-dragged-record-cell:last-child {
+ border-right: none;
+}
diff --git a/frontend/src/components/sf-table/table-main/records/record/index.js b/frontend/src/components/sf-table/table-main/records/record/index.js
index 626aa9c3bf..c1f48fc46b 100644
--- a/frontend/src/components/sf-table/table-main/records/record/index.js
+++ b/frontend/src/components/sf-table/table-main/records/record/index.js
@@ -10,11 +10,18 @@ import './index.css';
class Record extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ canDropTip: false,
+ };
+ }
+
componentDidMount() {
this.checkScroll();
}
- shouldComponentUpdate(nextProps) {
+ shouldComponentUpdate(nextProps, nextState) {
return (
nextProps.isGroupView !== this.props.isGroupView ||
nextProps.hasSelectedCell !== this.props.hasSelectedCell ||
@@ -40,7 +47,8 @@ class Record extends React.Component {
nextProps.treeNodeKey !== this.props.treeNodeKey ||
nextProps.treeNodeDepth !== this.props.treeNodeDepth ||
nextProps.hasChildNodes !== this.props.hasChildNodes ||
- nextProps.isFoldedTreeNode !== this.props.isFoldedTreeNode
+ nextProps.isFoldedTreeNode !== this.props.isFoldedTreeNode ||
+ nextState.canDropTip !== this.state.canDropTip
);
}
@@ -222,23 +230,67 @@ class Record extends React.Component {
return style;
};
- // handle drag copy
+ handleDragStart = (event) => {
+ event.stopPropagation();
+ if (!this.props.recordDraggable) return;
+ const { record, treeNodeKey } = this.props;
+ const draggingRecordSource = { draggingRecordId: record._id, draggingTreeNodeKey: treeNodeKey };
+ event.dataTransfer.effectAllowed = 'move';
+ this.props.recordDragDropEvents.onDragStart(event, draggingRecordSource);
+ };
+
+ checkHasDraggedRecord = () => {
+ return !!this.props.draggingRecordSource;
+ };
+
+ checkOverDraggingRecord = () => {
+ const { draggingRecordSource, record, treeNodeKey, showRecordAsTree } = this.props;
+ if (!this.checkHasDraggedRecord()) return false;
+
+ const { draggingRecordId, draggingTreeNodeKey } = draggingRecordSource;
+ if (showRecordAsTree) {
+ return draggingTreeNodeKey === treeNodeKey;
+ }
+ return draggingRecordId === record._id;
+ };
+
handleDragEnter = (e) => {
- // Prevent default to allow drop
e.preventDefault();
- const { index, groupRecordIndex, cellMetaData: { onDragEnter } } = this.props;
- onDragEnter({ overRecordIdx: index, overGroupRecordIndex: groupRecordIndex });
+ if (this.checkHasDraggedRecord() && !this.checkOverDraggingRecord()) {
+ this.setState({ canDropTip: true });
+ }
+ };
+
+ handleDragLeave = (e) => {
+ const { clientX, clientY } = e;
+ const { left, top, width, height } = this.rowRef.getBoundingClientRect();
+ if (clientX > left && clientX < left + width && clientY > top && clientY < top + height - 2) return;
+ this.setState({ canDropTip: false });
};
handleDragOver = (e) => {
e.preventDefault();
- e.dataTransfer.dropEffect = 'copy';
+ e.dataTransfer.dropEffect = this.checkHasDraggedRecord() ? 'move' : 'copy';
+ if (this.checkHasDraggedRecord() && !this.checkOverDraggingRecord()) {
+ this.setState({ canDropTip: true });
+ }
};
handleDrop = (e) => {
- // The default in Firefox is to treat data in dataTransfer as a URL and perform navigation on it, even if the data type used is 'text'
- // To bypass this, we need to capture and prevent the drop event.
e.preventDefault();
+ e.stopPropagation();
+ this.setState({ canDropTip: false });
+ if (!this.checkHasDraggedRecord() || this.checkOverDraggingRecord()) {
+ this.props.recordDragDropEvents.onDragEnd();
+ return;
+ }
+ const { record, treeNodeKey } = this.props;
+ const dropTarget = { dropRecordId: record._id, dropTreeNodeKey: treeNodeKey };
+ this.props.recordDragDropEvents.onDrop(dropTarget);
+ };
+
+ onDragEnd = () => {
+ this.props.recordDragDropEvents.onDragEnd();
};
render() {
@@ -253,15 +305,19 @@ class Record extends React.Component {
return (
this.rowRef = rowRef}
className={classnames('sf-table-row', {
'sf-table-last-row': isLastRecord,
'row-selected': isSelected,
- 'row-locked': isLocked
+ 'row-locked': isLocked,
+ 'can-drop-tip': this.state.canDropTip,
})}
style={this.getRecordStyle()}
onDragEnter={this.handleDragEnter}
+ onDragLeave={this.handleDragLeave}
onDragOver={this.handleDragOver}
onDrop={this.handleDrop}
+ onDragEnd={this.onDragEnd}
>
{/* frozen */}
}
{frozenCells}
@@ -311,6 +369,7 @@ Record.propTypes = {
top: PropTypes.number,
left: PropTypes.number,
height: PropTypes.number,
+ recordDraggable: PropTypes.bool,
selectNoneCells: PropTypes.func,
onSelectRecord: PropTypes.func,
checkCanModifyRecord: PropTypes.func,
diff --git a/frontend/src/components/sf-table/table-main/records/tree-body.js b/frontend/src/components/sf-table/table-main/records/tree-body.js
index f6d809e238..96323fed50 100644
--- a/frontend/src/components/sf-table/table-main/records/tree-body.js
+++ b/frontend/src/components/sf-table/table-main/records/tree-body.js
@@ -598,6 +598,9 @@ class TreeBody extends Component {
colOverScanStartIdx={this.props.colOverScanStartIdx}
colOverScanEndIdx={this.props.colOverScanEndIdx}
lastFrozenColumnKey={this.props.lastFrozenColumnKey}
+ recordDraggable={this.props.recordDraggable}
+ recordDragDropEvents={this.props.recordDragDropEvents}
+ draggingRecordSource={this.props.draggingRecordSource}
scrollLeft={scrollLeft}
height={rowHeight}
cellMetaData={cellMetaData}
@@ -693,6 +696,9 @@ TreeBody.propTypes = {
treeNodeKeyRecordIdMap: PropTypes.object,
keyTreeNodeFoldedMap: PropTypes.object,
treeMetrics: PropTypes.object,
+ recordDraggable: PropTypes.bool,
+ recordDragDropEvents: PropTypes.object,
+ draggingRecordSource: PropTypes.object,
columns: PropTypes.array.isRequired,
CellOperationBtn: PropTypes.object,
colOverScanStartIdx: PropTypes.number,
@@ -727,7 +733,6 @@ TreeBody.propTypes = {
frozenColumnsWidth: PropTypes.number,
editMobileCell: PropTypes.func,
reloadRecords: PropTypes.func,
- appPage: PropTypes.object,
showCellColoring: PropTypes.bool,
columnColors: PropTypes.object,
onFillingDragRows: PropTypes.func,
diff --git a/frontend/src/components/sf-table/utils/record-metrics.js b/frontend/src/components/sf-table/utils/record-metrics.js
index fa4e4ad5d0..656e39b79a 100644
--- a/frontend/src/components/sf-table/utils/record-metrics.js
+++ b/frontend/src/components/sf-table/utils/record-metrics.js
@@ -42,6 +42,14 @@ function isSelectedAll(recordIds, recordMetrics) {
return recordIds.every(recordId => isRecordSelected(recordId, recordMetrics));
}
+function getDraggedRecordsIds(draggingRecordId, recordMetrics) {
+ const selectedRecordIds = getSelectedIds(recordMetrics);
+ if (selectedRecordIds.includes(draggingRecordId)) {
+ return selectedRecordIds;
+ }
+ return [draggingRecordId];
+}
+
export const RecordMetrics = {
selectRecord,
selectRecordsById,
@@ -51,4 +59,5 @@ export const RecordMetrics = {
getSelectedIds,
hasSelectedRecords,
isSelectedAll,
+ getDraggedRecordsIds,
};
diff --git a/frontend/src/components/sf-table/utils/tree-metrics.js b/frontend/src/components/sf-table/utils/tree-metrics.js
index 97c30ed69f..41b7078219 100644
--- a/frontend/src/components/sf-table/utils/tree-metrics.js
+++ b/frontend/src/components/sf-table/utils/tree-metrics.js
@@ -49,6 +49,14 @@ const checkIsSelectedAll = (nodeKeys, treeMetrics) => {
return nodeKeys.every(nodeKey => checkIsTreeNodeSelected(nodeKey, treeMetrics));
};
+const getDraggedTreeNodesKeys = (draggingTreeNodeKey, treeMetrics) => {
+ const selectedNodeKeys = getSelectedTreeNodesKeys(treeMetrics);
+ if (selectedNodeKeys.includes(draggingTreeNodeKey)) {
+ return selectedNodeKeys;
+ }
+ return [draggingTreeNodeKey];
+};
+
export const TreeMetrics = {
checkIsTreeNodeSelected,
selectTreeNode,
@@ -59,4 +67,5 @@ export const TreeMetrics = {
getSelectedIds,
checkHasSelectedTreeNodes,
checkIsSelectedAll,
+ getDraggedTreeNodesKeys,
};
diff --git a/frontend/src/tag/hooks/tags.js b/frontend/src/tag/hooks/tags.js
index 291c112083..f2c2a9de13 100644
--- a/frontend/src/tag/hooks/tags.js
+++ b/frontend/src/tag/hooks/tags.js
@@ -200,6 +200,10 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
storeRef.current.deleteTagLinks(columnKey, tagId, otherTagsIds, success_callback, fail_callback);
}, [storeRef]);
+ const deleteTagsLinks = useCallback((columnKey, tagId, idLinkedRowsIdsMap, { success_callback, fail_callback } = {}) => {
+ storeRef.current.deleteTagsLinks(columnKey, tagId, idLinkedRowsIdsMap, success_callback, fail_callback);
+ }, [storeRef]);
+
const mergeTags = useCallback((target_tag_id, merged_tags_ids, { success_callback, fail_callback } = {}) => {
storeRef.current.mergeTags(target_tag_id, merged_tags_ids, success_callback, fail_callback);
}, [storeRef]);
@@ -284,6 +288,7 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
updateTag,
addTagLinks,
deleteTagLinks,
+ deleteTagsLinks,
mergeTags,
updateLocalTag,
selectTag: handleSelectTag,
diff --git a/frontend/src/tag/store/data-processor.js b/frontend/src/tag/store/data-processor.js
index fa7c10bcc7..3dbb3ab7f6 100644
--- a/frontend/src/tag/store/data-processor.js
+++ b/frontend/src/tag/store/data-processor.js
@@ -237,6 +237,7 @@ class DataProcessor {
}
case OPERATION_TYPE.ADD_TAG_LINKS:
case OPERATION_TYPE.DELETE_TAG_LINKS:
+ case OPERATION_TYPE.DELETE_TAGS_LINKS:
case OPERATION_TYPE.MERGE_TAGS: {
this.buildTagsTree(table.rows, table);
break;
diff --git a/frontend/src/tag/store/index.js b/frontend/src/tag/store/index.js
index d80be19577..a62438d492 100644
--- a/frontend/src/tag/store/index.js
+++ b/frontend/src/tag/store/index.js
@@ -386,6 +386,19 @@ class Store {
this.applyOperation(operation);
}
+ deleteTagsLinks(column_key, id_linked_rows_ids_map, success_callback, fail_callback) {
+ const type = OPERATION_TYPE.DELETE_TAGS_LINKS;
+ const operation = this.createOperation({
+ type,
+ repo_id: this.repoId,
+ column_key,
+ id_linked_rows_ids_map,
+ success_callback,
+ fail_callback,
+ });
+ this.applyOperation(operation);
+ }
+
mergeTags(target_tag_id, merged_tags_ids, success_callback, fail_callback) {
const type = OPERATION_TYPE.MERGE_TAGS;
const operation = this.createOperation({
diff --git a/frontend/src/tag/store/operations/apply.js b/frontend/src/tag/store/operations/apply.js
index 9c02b139bf..93e47ffe2b 100644
--- a/frontend/src/tag/store/operations/apply.js
+++ b/frontend/src/tag/store/operations/apply.js
@@ -219,6 +219,56 @@ export default function apply(data, operation) {
}
return data;
}
+ case OPERATION_TYPE.DELETE_TAGS_LINKS: {
+ const { column_key, id_linked_rows_ids_map } = operation;
+ const operatedIds = id_linked_rows_ids_map && Object.keys(id_linked_rows_ids_map);
+ if (!operatedIds || operatedIds.length === 0) {
+ return data;
+ }
+ data.rows = [...data.rows];
+ if (column_key === PRIVATE_COLUMN_KEY.PARENT_LINKS) {
+ data.rows.forEach((row, index) => {
+ const currentRowId = row._id;
+ const other_rows_ids = id_linked_rows_ids_map[currentRowId];
+ let updatedRow = { ...row };
+ if (other_rows_ids) {
+ // remove parent tags from current tag
+ updatedRow = removeRowLinks(updatedRow, PRIVATE_COLUMN_KEY.PARENT_LINKS, other_rows_ids);
+ }
+
+ // remove current tag as child tag from related tags
+ operatedIds.forEach((operatedId) => {
+ const other_rows_ids = id_linked_rows_ids_map[operatedId];
+ if (other_rows_ids && other_rows_ids.includes(currentRowId)) {
+ updatedRow = removeRowLinks(updatedRow, PRIVATE_COLUMN_KEY.SUB_LINKS, [operatedId]);
+ }
+ });
+ data.rows[index] = updatedRow;
+ data.id_row_map[currentRowId] = updatedRow;
+ });
+ } else if (column_key === PRIVATE_COLUMN_KEY.SUB_LINKS) {
+ data.rows.forEach((row, index) => {
+ const currentRowId = row._id;
+ const other_rows_ids = id_linked_rows_ids_map[currentRowId];
+ let updatedRow = { ...row };
+ if (other_rows_ids) {
+ // remove child tags from current tag
+ updatedRow = removeRowLinks(updatedRow, PRIVATE_COLUMN_KEY.SUB_LINKS, other_rows_ids);
+ }
+
+ // remove current tag as parent tag from related tags
+ operatedIds.forEach((operatedId) => {
+ const other_rows_ids = id_linked_rows_ids_map[operatedId];
+ if (other_rows_ids && other_rows_ids.includes(currentRowId)) {
+ updatedRow = removeRowLinks(updatedRow, PRIVATE_COLUMN_KEY.PARENT_LINKS, [operatedId]);
+ }
+ });
+ data.rows[index] = updatedRow;
+ data.id_row_map[currentRowId] = updatedRow;
+ });
+ }
+ return data;
+ }
case OPERATION_TYPE.MERGE_TAGS: {
const { target_tag_id, merged_tags_ids } = operation;
const targetTag = getRowById(data, target_tag_id);
diff --git a/frontend/src/tag/store/operations/constants.js b/frontend/src/tag/store/operations/constants.js
index 9419618597..2b0df564db 100644
--- a/frontend/src/tag/store/operations/constants.js
+++ b/frontend/src/tag/store/operations/constants.js
@@ -7,6 +7,7 @@ export const OPERATION_TYPE = {
RELOAD_RECORDS: 'reload_records',
ADD_TAG_LINKS: 'add_tag_links',
DELETE_TAG_LINKS: 'delete_tag_links',
+ DELETE_TAGS_LINKS: 'delete_tags_links',
MERGE_TAGS: 'merge_tags',
MODIFY_LOCAL_RECORDS: 'modify_local_records',
@@ -24,6 +25,7 @@ export const OPERATION_ATTRIBUTES = {
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
[OPERATION_TYPE.ADD_TAG_LINKS]: ['repo_id', 'column_key', 'row_id', 'other_rows_ids'],
[OPERATION_TYPE.DELETE_TAG_LINKS]: ['repo_id', 'column_key', 'row_id', 'other_rows_ids'],
+ [OPERATION_TYPE.DELETE_TAGS_LINKS]: ['repo_id', 'column_key', 'id_linked_rows_ids_map'],
[OPERATION_TYPE.MERGE_TAGS]: ['repo_id', 'target_tag_id', 'merged_tags_ids'],
[OPERATION_TYPE.MODIFY_LOCAL_RECORDS]: ['repo_id', 'row_ids', 'id_row_updates', 'id_original_row_updates', 'id_old_row_data', 'id_original_old_row_data', 'is_copy_paste', 'is_rename', 'id_obj_id'],
[OPERATION_TYPE.MODIFY_LOCAL_FILE_TAGS]: ['file_id', 'tags_ids'],
diff --git a/frontend/src/tag/store/server-operator.js b/frontend/src/tag/store/server-operator.js
index b6b075bf10..3944a50f18 100644
--- a/frontend/src/tag/store/server-operator.js
+++ b/frontend/src/tag/store/server-operator.js
@@ -97,6 +97,15 @@ class ServerOperator {
});
break;
}
+ case OPERATION_TYPE.DELETE_TAGS_LINKS: {
+ const { column_key, id_linked_rows_ids_map } = operation;
+ this.context.deleteTagLinks(column_key, id_linked_rows_ids_map).then(res => {
+ callback({ operation });
+ }).catch(error => {
+ callback({ error: gettext('Failed to delete linked tags') });
+ });
+ break;
+ }
case OPERATION_TYPE.MERGE_TAGS: {
const { target_tag_id, merged_tags_ids } = operation;
this.context.mergeTags(target_tag_id, merged_tags_ids).then((res) => {
diff --git a/frontend/src/tag/views/all-tags/tags-table/dragged-tags-layer.js b/frontend/src/tag/views/all-tags/tags-table/dragged-tags-layer.js
new file mode 100644
index 0000000000..6f73464e5f
--- /dev/null
+++ b/frontend/src/tag/views/all-tags/tags-table/dragged-tags-layer.js
@@ -0,0 +1,27 @@
+import React, { useMemo } from 'react';
+import { getTreeNodeByKey, getTreeNodeId } from '../../../../components/sf-table/utils/tree';
+import { useTags } from '../../../hooks';
+import { getRowById } from '../../../../components/sf-table/utils/table';
+import TagNameFormatter from './formatter/tag-name';
+
+const DraggedTagsLayer = ({ draggedNodesKeys }) => {
+ const { tagsData } = useTags();
+
+ const keyTreeNodeMap = useMemo(() => {
+ return tagsData.key_tree_node_map || [];
+ }, [tagsData]);
+
+ return draggedNodesKeys.map((nodeKey) => {
+ const node = getTreeNodeByKey(nodeKey, keyTreeNodeMap);
+ const tagId = getTreeNodeId(node);
+ const tag = getRowById(tagsData, tagId);
+ if (!tag) return null;
+ return (
+
+ |
+
+ );
+ });
+};
+
+export default DraggedTagsLayer;
diff --git a/frontend/src/tag/views/all-tags/tags-table/formatter/tag-name.js b/frontend/src/tag/views/all-tags/tags-table/formatter/tag-name.js
index f4f096ec69..d2ce9bfef0 100644
--- a/frontend/src/tag/views/all-tags/tags-table/formatter/tag-name.js
+++ b/frontend/src/tag/views/all-tags/tags-table/formatter/tag-name.js
@@ -4,6 +4,7 @@ import { useTags } from '../../../../hooks';
import { PRIVATE_COLUMN_KEY } from '../../../../constants';
import { getRecordIdFromRecord } from '../../../../../metadata/utils/cell';
import { getTreeNodeKey } from '../../../../../components/sf-table/utils/tree';
+import { isNumber } from '../../../../../utils/number';
const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex }) => {
const { tagsData } = useTags();
@@ -13,7 +14,7 @@ const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex
}, [tagsData]);
const currentNode = useMemo(() => {
- return tree[treeNodeIndex];
+ return isNumber(treeNodeIndex) ? tree[treeNodeIndex] : null;
}, [tree, treeNodeIndex]);
const tagColor = useMemo(() => {
@@ -28,7 +29,7 @@ const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex
if (!isCellSelected) return;
const tagId = getRecordIdFromRecord(record);
const nodeKey = getTreeNodeKey(currentNode);
- setDisplayTag(tagId, nodeKey);
+ setDisplayTag && setDisplayTag(tagId, nodeKey);
}, [isCellSelected, record, currentNode, setDisplayTag]);
return (
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 455ac83622..2037204c74 100644
--- a/frontend/src/tag/views/all-tags/tags-table/index.js
+++ b/frontend/src/tag/views/all-tags/tags-table/index.js
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import SFTable from '../../../../components/sf-table';
import EditTagDialog from '../../../components/dialog/edit-tag-dialog';
import MergeTagsSelector from '../../../components/merge-tags-selector';
+import DraggedTagsLayer from './dragged-tags-layer';
import { createTableColumns } from './columns-factory';
import { createContextMenuOptions } from './context-menu-options';
import { gettext } from '../../../../utils/constants';
@@ -13,6 +14,9 @@ import { EVENT_BUS_TYPE } from '../../../../metadata/constants';
import { EVENT_BUS_TYPE as TABLE_EVENT_BUS_TYPE } from '../../../../components/sf-table/constants/event-bus-type';
import { LOCAL_KEY_TREE_NODE_FOLDED } from '../../../../components/sf-table/constants/tree';
import { isNumber } from '../../../../utils/number';
+import { getTreeNodeByKey, getTreeNodeId } from '../../../../components/sf-table/utils/tree';
+import { getRowById } from '../../../../components/sf-table/utils/table';
+import { getParentLinks } from '../../../utils/cell';
import './index.css';
@@ -34,7 +38,7 @@ const TagsTable = ({
loadMore,
getTagsTableWrapperOffsets,
}) => {
- const { tagsData, updateTag, deleteTags, addTagLinks, deleteTagLinks, addChildTag, mergeTags } = useTags();
+ const { tagsData, updateTag, deleteTags, addTagLinks, deleteTagLinks, deleteTagsLinks, addChildTag, mergeTags } = useTags();
const [isShowNewSubTagDialog, setIsShowNewSubTagDialog] = useState(false);
const [isShowMergeTagsSelector, setIsShowMergeTagsSelector] = useState(false);
@@ -208,6 +212,56 @@ const TagsTable = ({
}
}, [scrollToCurrentSelectedCell]);
+ const renderCustomDraggedRows = useCallback((draggedNodesKeys) => {
+ if (!Array.isArray(draggedNodesKeys) || draggedNodesKeys.length === 0) return null;
+ return (
+
+ );
+ }, []);
+
+ const moveTags = useCallback(({ draggingSource, dropTarget }) => {
+ const targetNode = getTreeNodeByKey(dropTarget, table.key_tree_node_map);
+ if (!Array.isArray(draggingSource) || draggingSource.length === 0 || !targetNode) return;
+ let draggingTagsIds = [];
+ let idNeedDeleteChildIds = {}; // { [parent_tag._id]: [child_tag._id] }
+ draggingSource.forEach((nodeKey) => {
+ const node = getTreeNodeByKey(nodeKey, table.key_tree_node_map);
+ const nodeId = getTreeNodeId(node);
+ const tag = getRowById(table, nodeId);
+
+ // find the child tags to delete which related to dragging tags
+ const parentLinks = getParentLinks(tag);
+ if (Array.isArray(parentLinks) && parentLinks.length > 0) {
+ parentLinks.forEach((link) => {
+ const parentTagId = link.row_id;
+ if (nodeKey.includes(parentTagId)) {
+ if (!idNeedDeleteChildIds[parentTagId]) {
+ idNeedDeleteChildIds[parentTagId] = [nodeId];
+ } else if (!idNeedDeleteChildIds[parentTagId].includes(nodeId)) {
+ idNeedDeleteChildIds[parentTagId].push(nodeId);
+ }
+ }
+ });
+ }
+
+ // get none-repeat dragging tags ids
+ if (!draggingTagsIds.includes(nodeId)) {
+ draggingTagsIds.push(nodeId);
+ }
+ });
+ if (draggingTagsIds.length === 0) return;
+
+ const targetTagId = getTreeNodeId(targetNode);
+ if (Object.keys(idNeedDeleteChildIds).length > 0) {
+ // need to delete child tags first
+ deleteTagsLinks(PRIVATE_COLUMN_KEY.SUB_LINKS, idNeedDeleteChildIds, () => {
+ addTagLinks(PRIVATE_COLUMN_KEY.SUB_LINKS, targetTagId, draggingTagsIds);
+ });
+ } else {
+ addTagLinks(PRIVATE_COLUMN_KEY.SUB_LINKS, targetTagId, draggingTagsIds);
+ }
+ }, [table, addTagLinks, deleteTagsLinks]);
+
useEffect(() => {
const eventBus = EventBus.getInstance();
const unsubscribeUpdateSearchResult = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SEARCH_RESULT, updateSearchResult);
@@ -242,6 +296,8 @@ const TagsTable = ({
checkCellValueChanged={checkCellValueChanged}
modifyColumnWidth={modifyColumnWidth}
loadMore={loadMore}
+ renderCustomDraggedRows={renderCustomDraggedRows}
+ moveRecords={moveTags}
/>
{isShowNewSubTagDialog && (