mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-21 03:18:23 +00:00
Feature/modify tag links by drag and drop (#7469)
* change tags link by drag and drop * optimize * optimize drag image * optimize drag effect * update codes --------- Co-authored-by: zhouwenxuan <aries@Mac.local> Co-authored-by: renjie-run <rj.aiyayao@gmail.com>
This commit is contained in:
@@ -158,6 +158,8 @@ SFTable.propTypes = {
|
|||||||
onGridKeyUp: PropTypes.func,
|
onGridKeyUp: PropTypes.func,
|
||||||
loadMore: PropTypes.func,
|
loadMore: PropTypes.func,
|
||||||
loadAll: PropTypes.func,
|
loadAll: PropTypes.func,
|
||||||
|
moveRecords: PropTypes.func,
|
||||||
|
renderCustomDraggedRows: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SFTable;
|
export default SFTable;
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import classnames from 'classnames';
|
||||||
import { HorizontalScrollbar } from '../../scrollbar';
|
import { HorizontalScrollbar } from '../../scrollbar';
|
||||||
import RecordsHeader from '../records-header';
|
import RecordsHeader from '../records-header';
|
||||||
import Body from './body';
|
import Body from './body';
|
||||||
@@ -7,6 +8,7 @@ import TreeBody from './tree-body';
|
|||||||
import GroupBody from './group-body';
|
import GroupBody from './group-body';
|
||||||
import RecordsFooter from '../records-footer';
|
import RecordsFooter from '../records-footer';
|
||||||
import ContextMenu from '../../context-menu';
|
import ContextMenu from '../../context-menu';
|
||||||
|
import RecordDragLayer from './record-drag-layer';
|
||||||
import { RecordMetrics } from '../../utils/record-metrics';
|
import { RecordMetrics } from '../../utils/record-metrics';
|
||||||
import { TreeMetrics } from '../../utils/tree-metrics';
|
import { TreeMetrics } from '../../utils/tree-metrics';
|
||||||
import { recalculate } from '../../utils/column';
|
import { recalculate } from '../../utils/column';
|
||||||
@@ -43,6 +45,7 @@ class Records extends Component {
|
|||||||
const { width: tableContentWidth } = props.getTableContentRect();
|
const { width: tableContentWidth } = props.getTableContentRect();
|
||||||
const initHorizontalScrollState = this.getHorizontalScrollState({ gridWidth: tableContentWidth, columnMetrics, scrollLeft: 0 });
|
const initHorizontalScrollState = this.getHorizontalScrollState({ gridWidth: tableContentWidth, columnMetrics, scrollLeft: 0 });
|
||||||
this.state = {
|
this.state = {
|
||||||
|
draggingRecordSource: null,
|
||||||
columnMetrics,
|
columnMetrics,
|
||||||
recordMetrics: this.createRowMetrics(),
|
recordMetrics: this.createRowMetrics(),
|
||||||
treeMetrics: this.createTreeMetrics(),
|
treeMetrics: this.createTreeMetrics(),
|
||||||
@@ -832,13 +835,66 @@ class Records extends Component {
|
|||||||
return columnVisibleEnd;
|
return columnVisibleEnd;
|
||||||
};
|
};
|
||||||
|
|
||||||
renderRecordsBody = ({ containerWidth }) => {
|
handleDragRecordsEnd = () => {
|
||||||
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx } = this.state;
|
this.setState({ draggingRecordSource: null });
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDragRecordStart = (event, { draggingRecordId, draggingTreeNodeKey }) => {
|
||||||
|
this.setState({
|
||||||
|
draggingRecordSource: {
|
||||||
|
event,
|
||||||
|
draggingRecordId,
|
||||||
|
draggingTreeNodeKey
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleDropRecords = ({ dropRecordId, dropTreeNodeKey } = {}) => {
|
||||||
|
const { showRecordAsTree } = this.props;
|
||||||
|
const { draggingRecordSource, treeMetrics, recordMetrics } = this.state;
|
||||||
|
const dropTarget = showRecordAsTree ? dropTreeNodeKey : dropRecordId;
|
||||||
|
if (!dropTarget) return;
|
||||||
|
|
||||||
|
const { draggingRecordId, draggingTreeNodeKey } = draggingRecordSource;
|
||||||
|
const draggingSource = showRecordAsTree ? TreeMetrics.getDraggedTreeNodesKeys(draggingTreeNodeKey, treeMetrics) : RecordMetrics.getDraggedRecordsIds(draggingRecordId, recordMetrics);
|
||||||
|
this.props.moveRecords({ draggingSource, dropTarget });
|
||||||
|
this.handleDragRecordsEnd();
|
||||||
|
};
|
||||||
|
|
||||||
|
getRecordDragDropEvents = () => {
|
||||||
|
if (!this.props.moveRecords) return null;
|
||||||
|
if (!this.recordDragDropEvents) {
|
||||||
|
this.recordDragDropEvents = {
|
||||||
|
onDragStart: this.handleDragRecordStart,
|
||||||
|
onDrop: this.handleDropRecords,
|
||||||
|
onDragEnd: this.handleDragRecordsEnd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.recordDragDropEvents;
|
||||||
|
};
|
||||||
|
|
||||||
|
createRecordsDragLayer = () => {
|
||||||
|
const { draggingRecordSource, recordMetrics, treeMetrics } = this.state;
|
||||||
|
if (!draggingRecordSource) return null;
|
||||||
|
return (
|
||||||
|
<RecordDragLayer
|
||||||
|
showRecordAsTree={this.props.showRecordAsTree}
|
||||||
|
draggingRecordSource={draggingRecordSource}
|
||||||
|
recordMetrics={recordMetrics}
|
||||||
|
treeMetrics={treeMetrics}
|
||||||
|
renderCustomDraggedRows={this.props.renderCustomDraggedRows}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderRecordsBody = ({ containerWidth, recordDraggable }) => {
|
||||||
|
const { recordMetrics, columnMetrics, colOverScanStartIdx, colOverScanEndIdx, draggingRecordSource } = this.state;
|
||||||
const { columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth } = columnMetrics;
|
const { columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth } = columnMetrics;
|
||||||
|
const recordDragDropEvents = this.getRecordDragDropEvents();
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
...this.props,
|
...this.props,
|
||||||
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
columns, allColumns, totalWidth, lastFrozenColumnKey, frozenColumnsWidth,
|
||||||
recordMetrics, colOverScanStartIdx, colOverScanEndIdx,
|
recordMetrics, colOverScanStartIdx, colOverScanEndIdx, recordDraggable, recordDragDropEvents, draggingRecordSource,
|
||||||
contextMenu: (
|
contextMenu: (
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
{...this.props}
|
{...this.props}
|
||||||
@@ -900,11 +956,12 @@ class Records extends Component {
|
|||||||
const containerWidth = this.getContainerWidth();
|
const containerWidth = this.getContainerWidth();
|
||||||
const hasSelectedRecord = this.checkHasSelectedRecord();
|
const hasSelectedRecord = this.checkHasSelectedRecord();
|
||||||
const isSelectedAll = this.checkIsSelectAll();
|
const isSelectedAll = this.checkIsSelectAll();
|
||||||
|
const recordDraggable = !!this.props.moveRecords;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={`sf-table-result-container ${this.isWindows ? 'windows-browser' : ''}`}
|
className={classnames('sf-table-result-container', { 'windows-browser': this.isWindows, 'record-draggable': recordDraggable })}
|
||||||
ref={this.setResultContainerRef}
|
ref={this.setResultContainerRef}
|
||||||
onScroll={this.onContentScroll}
|
onScroll={this.onContentScroll}
|
||||||
onClick={this.onClickContainer}
|
onClick={this.onClickContainer}
|
||||||
@@ -933,9 +990,10 @@ class Records extends Component {
|
|||||||
modifyColumnWidth={this.props.modifyColumnWidth}
|
modifyColumnWidth={this.props.modifyColumnWidth}
|
||||||
insertColumn={this.props.insertColumn}
|
insertColumn={this.props.insertColumn}
|
||||||
/>
|
/>
|
||||||
{this.renderRecordsBody({ containerWidth })}
|
{this.renderRecordsBody({ containerWidth, recordDraggable })}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{this.createRecordsDragLayer()}
|
||||||
{this.isWindows && this.isWebkit && (
|
{this.isWindows && this.isWebkit && (
|
||||||
<HorizontalScrollbar
|
<HorizontalScrollbar
|
||||||
ref={this.setHorizontalScrollbarRef}
|
ref={this.setHorizontalScrollbarRef}
|
||||||
@@ -1010,6 +1068,7 @@ Records.propTypes = {
|
|||||||
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
|
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
|
||||||
moveRecord: PropTypes.func,
|
moveRecord: PropTypes.func,
|
||||||
addFolder: PropTypes.func,
|
addFolder: PropTypes.func,
|
||||||
|
moveRecords: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Records;
|
export default Records;
|
||||||
|
@@ -0,0 +1,36 @@
|
|||||||
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
|
import { TreeMetrics } from '../../utils/tree-metrics';
|
||||||
|
import { RecordMetrics } from '../../utils/record-metrics';
|
||||||
|
|
||||||
|
const RecordDragLayer = ({ showRecordAsTree, draggingRecordSource, recordMetrics, treeMetrics, renderCustomDraggedRows }) => {
|
||||||
|
const layerRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (layerRef.current && draggingRecordSource.event) {
|
||||||
|
draggingRecordSource.event.dataTransfer.setDragImage(layerRef.current, 15, 15);
|
||||||
|
}
|
||||||
|
}, [draggingRecordSource]);
|
||||||
|
|
||||||
|
const getDraggedRowsIds = useCallback(() => {
|
||||||
|
const { draggingRecordId, draggingTreeNodeKey } = draggingRecordSource;
|
||||||
|
return showRecordAsTree ? TreeMetrics.getDraggedTreeNodesKeys(draggingTreeNodeKey, treeMetrics) : RecordMetrics.getDraggedRecordsIds(draggingRecordId, recordMetrics);
|
||||||
|
}, [showRecordAsTree, draggingRecordSource, treeMetrics, recordMetrics]);
|
||||||
|
|
||||||
|
const renderDraggedRows = useCallback(() => {
|
||||||
|
const draggedRowsIds = getDraggedRowsIds();
|
||||||
|
if (renderCustomDraggedRows) {
|
||||||
|
return renderCustomDraggedRows(draggedRowsIds);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}, [getDraggedRowsIds, renderCustomDraggedRows]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="sf-table-record-drag-layer" ref={layerRef}>
|
||||||
|
<table className='record-drag-layer-table'>
|
||||||
|
<tbody>{renderDraggedRows()}</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RecordDragLayer;
|
@@ -70,6 +70,14 @@ class ActionsCell extends Component {
|
|||||||
onMouseEnter={this.onCellMouseEnter}
|
onMouseEnter={this.onCellMouseEnter}
|
||||||
onMouseLeave={this.onCellMouseLeave}
|
onMouseLeave={this.onCellMouseLeave}
|
||||||
>
|
>
|
||||||
|
{this.props.recordDraggable &&
|
||||||
|
<div
|
||||||
|
draggable
|
||||||
|
className="drag-handler"
|
||||||
|
onDragStart={this.props.handleDragStart}
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
{!isSelected && <div className="sf-table-column-content row-index text-truncate">{this.getRecordNo()}</div>}
|
{!isSelected && <div className="sf-table-column-content row-index text-truncate">{this.getRecordNo()}</div>}
|
||||||
<div className="sf-table-column-content actions-checkbox">
|
<div className="sf-table-column-content actions-checkbox">
|
||||||
<div className="select-cell-checkbox-container" onClick={this.props.onSelectRecord}>
|
<div className="select-cell-checkbox-container" onClick={this.props.onSelectRecord}>
|
||||||
@@ -100,10 +108,12 @@ ActionsCell.propTypes = {
|
|||||||
isLocked: PropTypes.bool,
|
isLocked: PropTypes.bool,
|
||||||
isSelected: PropTypes.bool,
|
isSelected: PropTypes.bool,
|
||||||
isLastFrozenCell: PropTypes.bool,
|
isLastFrozenCell: PropTypes.bool,
|
||||||
|
recordDraggable: PropTypes.bool,
|
||||||
recordId: PropTypes.string,
|
recordId: PropTypes.string,
|
||||||
index: PropTypes.number,
|
index: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
onSelectRecord: PropTypes.func,
|
onSelectRecord: PropTypes.func,
|
||||||
|
handleDragStart: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ActionsCell;
|
export default ActionsCell;
|
||||||
|
@@ -24,3 +24,62 @@
|
|||||||
.frozen-columns {
|
.frozen-columns {
|
||||||
background-color: #fff;
|
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;
|
||||||
|
}
|
||||||
|
@@ -10,11 +10,18 @@ import './index.css';
|
|||||||
|
|
||||||
class Record extends React.Component {
|
class Record extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
canDropTip: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.checkScroll();
|
this.checkScroll();
|
||||||
}
|
}
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
return (
|
return (
|
||||||
nextProps.isGroupView !== this.props.isGroupView ||
|
nextProps.isGroupView !== this.props.isGroupView ||
|
||||||
nextProps.hasSelectedCell !== this.props.hasSelectedCell ||
|
nextProps.hasSelectedCell !== this.props.hasSelectedCell ||
|
||||||
@@ -40,7 +47,8 @@ class Record extends React.Component {
|
|||||||
nextProps.treeNodeKey !== this.props.treeNodeKey ||
|
nextProps.treeNodeKey !== this.props.treeNodeKey ||
|
||||||
nextProps.treeNodeDepth !== this.props.treeNodeDepth ||
|
nextProps.treeNodeDepth !== this.props.treeNodeDepth ||
|
||||||
nextProps.hasChildNodes !== this.props.hasChildNodes ||
|
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;
|
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) => {
|
handleDragEnter = (e) => {
|
||||||
// Prevent default to allow drop
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const { index, groupRecordIndex, cellMetaData: { onDragEnter } } = this.props;
|
if (this.checkHasDraggedRecord() && !this.checkOverDraggingRecord()) {
|
||||||
onDragEnter({ overRecordIdx: index, overGroupRecordIndex: groupRecordIndex });
|
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) => {
|
handleDragOver = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.dataTransfer.dropEffect = 'copy';
|
e.dataTransfer.dropEffect = this.checkHasDraggedRecord() ? 'move' : 'copy';
|
||||||
|
if (this.checkHasDraggedRecord() && !this.checkOverDraggingRecord()) {
|
||||||
|
this.setState({ canDropTip: true });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDrop = (e) => {
|
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.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() {
|
render() {
|
||||||
@@ -253,15 +305,19 @@ class Record extends React.Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
ref={rowRef => this.rowRef = rowRef}
|
||||||
className={classnames('sf-table-row', {
|
className={classnames('sf-table-row', {
|
||||||
'sf-table-last-row': isLastRecord,
|
'sf-table-last-row': isLastRecord,
|
||||||
'row-selected': isSelected,
|
'row-selected': isSelected,
|
||||||
'row-locked': isLocked
|
'row-locked': isLocked,
|
||||||
|
'can-drop-tip': this.state.canDropTip,
|
||||||
})}
|
})}
|
||||||
style={this.getRecordStyle()}
|
style={this.getRecordStyle()}
|
||||||
onDragEnter={this.handleDragEnter}
|
onDragEnter={this.handleDragEnter}
|
||||||
|
onDragLeave={this.handleDragLeave}
|
||||||
onDragOver={this.handleDragOver}
|
onDragOver={this.handleDragOver}
|
||||||
onDrop={this.handleDrop}
|
onDrop={this.handleDrop}
|
||||||
|
onDragEnd={this.onDragEnd}
|
||||||
>
|
>
|
||||||
{/* frozen */}
|
{/* frozen */}
|
||||||
<div
|
<div
|
||||||
@@ -280,6 +336,8 @@ class Record extends React.Component {
|
|||||||
onSelectRecord={this.onSelectRecord}
|
onSelectRecord={this.onSelectRecord}
|
||||||
isLastFrozenCell={!lastFrozenColumnKey}
|
isLastFrozenCell={!lastFrozenColumnKey}
|
||||||
height={cellHeight}
|
height={cellHeight}
|
||||||
|
recordDraggable={this.props.recordDraggable}
|
||||||
|
handleDragStart={this.handleDragStart}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
{frozenCells}
|
{frozenCells}
|
||||||
@@ -311,6 +369,7 @@ Record.propTypes = {
|
|||||||
top: PropTypes.number,
|
top: PropTypes.number,
|
||||||
left: PropTypes.number,
|
left: PropTypes.number,
|
||||||
height: PropTypes.number,
|
height: PropTypes.number,
|
||||||
|
recordDraggable: PropTypes.bool,
|
||||||
selectNoneCells: PropTypes.func,
|
selectNoneCells: PropTypes.func,
|
||||||
onSelectRecord: PropTypes.func,
|
onSelectRecord: PropTypes.func,
|
||||||
checkCanModifyRecord: PropTypes.func,
|
checkCanModifyRecord: PropTypes.func,
|
||||||
|
@@ -598,6 +598,9 @@ class TreeBody extends Component {
|
|||||||
colOverScanStartIdx={this.props.colOverScanStartIdx}
|
colOverScanStartIdx={this.props.colOverScanStartIdx}
|
||||||
colOverScanEndIdx={this.props.colOverScanEndIdx}
|
colOverScanEndIdx={this.props.colOverScanEndIdx}
|
||||||
lastFrozenColumnKey={this.props.lastFrozenColumnKey}
|
lastFrozenColumnKey={this.props.lastFrozenColumnKey}
|
||||||
|
recordDraggable={this.props.recordDraggable}
|
||||||
|
recordDragDropEvents={this.props.recordDragDropEvents}
|
||||||
|
draggingRecordSource={this.props.draggingRecordSource}
|
||||||
scrollLeft={scrollLeft}
|
scrollLeft={scrollLeft}
|
||||||
height={rowHeight}
|
height={rowHeight}
|
||||||
cellMetaData={cellMetaData}
|
cellMetaData={cellMetaData}
|
||||||
@@ -693,6 +696,9 @@ TreeBody.propTypes = {
|
|||||||
treeNodeKeyRecordIdMap: PropTypes.object,
|
treeNodeKeyRecordIdMap: PropTypes.object,
|
||||||
keyTreeNodeFoldedMap: PropTypes.object,
|
keyTreeNodeFoldedMap: PropTypes.object,
|
||||||
treeMetrics: PropTypes.object,
|
treeMetrics: PropTypes.object,
|
||||||
|
recordDraggable: PropTypes.bool,
|
||||||
|
recordDragDropEvents: PropTypes.object,
|
||||||
|
draggingRecordSource: PropTypes.object,
|
||||||
columns: PropTypes.array.isRequired,
|
columns: PropTypes.array.isRequired,
|
||||||
CellOperationBtn: PropTypes.object,
|
CellOperationBtn: PropTypes.object,
|
||||||
colOverScanStartIdx: PropTypes.number,
|
colOverScanStartIdx: PropTypes.number,
|
||||||
@@ -727,7 +733,6 @@ TreeBody.propTypes = {
|
|||||||
frozenColumnsWidth: PropTypes.number,
|
frozenColumnsWidth: PropTypes.number,
|
||||||
editMobileCell: PropTypes.func,
|
editMobileCell: PropTypes.func,
|
||||||
reloadRecords: PropTypes.func,
|
reloadRecords: PropTypes.func,
|
||||||
appPage: PropTypes.object,
|
|
||||||
showCellColoring: PropTypes.bool,
|
showCellColoring: PropTypes.bool,
|
||||||
columnColors: PropTypes.object,
|
columnColors: PropTypes.object,
|
||||||
onFillingDragRows: PropTypes.func,
|
onFillingDragRows: PropTypes.func,
|
||||||
|
@@ -42,6 +42,14 @@ function isSelectedAll(recordIds, recordMetrics) {
|
|||||||
return recordIds.every(recordId => isRecordSelected(recordId, 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 = {
|
export const RecordMetrics = {
|
||||||
selectRecord,
|
selectRecord,
|
||||||
selectRecordsById,
|
selectRecordsById,
|
||||||
@@ -51,4 +59,5 @@ export const RecordMetrics = {
|
|||||||
getSelectedIds,
|
getSelectedIds,
|
||||||
hasSelectedRecords,
|
hasSelectedRecords,
|
||||||
isSelectedAll,
|
isSelectedAll,
|
||||||
|
getDraggedRecordsIds,
|
||||||
};
|
};
|
||||||
|
@@ -49,6 +49,14 @@ const checkIsSelectedAll = (nodeKeys, treeMetrics) => {
|
|||||||
return nodeKeys.every(nodeKey => checkIsTreeNodeSelected(nodeKey, 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 = {
|
export const TreeMetrics = {
|
||||||
checkIsTreeNodeSelected,
|
checkIsTreeNodeSelected,
|
||||||
selectTreeNode,
|
selectTreeNode,
|
||||||
@@ -59,4 +67,5 @@ export const TreeMetrics = {
|
|||||||
getSelectedIds,
|
getSelectedIds,
|
||||||
checkHasSelectedTreeNodes,
|
checkHasSelectedTreeNodes,
|
||||||
checkIsSelectedAll,
|
checkIsSelectedAll,
|
||||||
|
getDraggedTreeNodesKeys,
|
||||||
};
|
};
|
||||||
|
@@ -200,6 +200,10 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
|
|||||||
storeRef.current.deleteTagLinks(columnKey, tagId, otherTagsIds, success_callback, fail_callback);
|
storeRef.current.deleteTagLinks(columnKey, tagId, otherTagsIds, success_callback, fail_callback);
|
||||||
}, [storeRef]);
|
}, [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 } = {}) => {
|
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.current.mergeTags(target_tag_id, merged_tags_ids, success_callback, fail_callback);
|
||||||
}, [storeRef]);
|
}, [storeRef]);
|
||||||
@@ -284,6 +288,7 @@ export const TagsProvider = ({ repoID, currentPath, selectTagsView, children, ..
|
|||||||
updateTag,
|
updateTag,
|
||||||
addTagLinks,
|
addTagLinks,
|
||||||
deleteTagLinks,
|
deleteTagLinks,
|
||||||
|
deleteTagsLinks,
|
||||||
mergeTags,
|
mergeTags,
|
||||||
updateLocalTag,
|
updateLocalTag,
|
||||||
selectTag: handleSelectTag,
|
selectTag: handleSelectTag,
|
||||||
|
@@ -237,6 +237,7 @@ class DataProcessor {
|
|||||||
}
|
}
|
||||||
case OPERATION_TYPE.ADD_TAG_LINKS:
|
case OPERATION_TYPE.ADD_TAG_LINKS:
|
||||||
case OPERATION_TYPE.DELETE_TAG_LINKS:
|
case OPERATION_TYPE.DELETE_TAG_LINKS:
|
||||||
|
case OPERATION_TYPE.DELETE_TAGS_LINKS:
|
||||||
case OPERATION_TYPE.MERGE_TAGS: {
|
case OPERATION_TYPE.MERGE_TAGS: {
|
||||||
this.buildTagsTree(table.rows, table);
|
this.buildTagsTree(table.rows, table);
|
||||||
break;
|
break;
|
||||||
|
@@ -386,6 +386,19 @@ class Store {
|
|||||||
this.applyOperation(operation);
|
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) {
|
mergeTags(target_tag_id, merged_tags_ids, success_callback, fail_callback) {
|
||||||
const type = OPERATION_TYPE.MERGE_TAGS;
|
const type = OPERATION_TYPE.MERGE_TAGS;
|
||||||
const operation = this.createOperation({
|
const operation = this.createOperation({
|
||||||
|
@@ -219,6 +219,56 @@ export default function apply(data, operation) {
|
|||||||
}
|
}
|
||||||
return data;
|
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: {
|
case OPERATION_TYPE.MERGE_TAGS: {
|
||||||
const { target_tag_id, merged_tags_ids } = operation;
|
const { target_tag_id, merged_tags_ids } = operation;
|
||||||
const targetTag = getRowById(data, target_tag_id);
|
const targetTag = getRowById(data, target_tag_id);
|
||||||
|
@@ -7,6 +7,7 @@ export const OPERATION_TYPE = {
|
|||||||
RELOAD_RECORDS: 'reload_records',
|
RELOAD_RECORDS: 'reload_records',
|
||||||
ADD_TAG_LINKS: 'add_tag_links',
|
ADD_TAG_LINKS: 'add_tag_links',
|
||||||
DELETE_TAG_LINKS: 'delete_tag_links',
|
DELETE_TAG_LINKS: 'delete_tag_links',
|
||||||
|
DELETE_TAGS_LINKS: 'delete_tags_links',
|
||||||
MERGE_TAGS: 'merge_tags',
|
MERGE_TAGS: 'merge_tags',
|
||||||
|
|
||||||
MODIFY_LOCAL_RECORDS: 'modify_local_records',
|
MODIFY_LOCAL_RECORDS: 'modify_local_records',
|
||||||
@@ -24,6 +25,7 @@ export const OPERATION_ATTRIBUTES = {
|
|||||||
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
|
[OPERATION_TYPE.RELOAD_RECORDS]: ['repo_id', 'row_ids'],
|
||||||
[OPERATION_TYPE.ADD_TAG_LINKS]: ['repo_id', 'column_key', 'row_id', 'other_rows_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_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.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_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'],
|
[OPERATION_TYPE.MODIFY_LOCAL_FILE_TAGS]: ['file_id', 'tags_ids'],
|
||||||
|
@@ -97,6 +97,15 @@ class ServerOperator {
|
|||||||
});
|
});
|
||||||
break;
|
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: {
|
case OPERATION_TYPE.MERGE_TAGS: {
|
||||||
const { target_tag_id, merged_tags_ids } = operation;
|
const { target_tag_id, merged_tags_ids } = operation;
|
||||||
this.context.mergeTags(target_tag_id, merged_tags_ids).then((res) => {
|
this.context.mergeTags(target_tag_id, merged_tags_ids).then((res) => {
|
||||||
|
@@ -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 (
|
||||||
|
<tr key={`rdg-dragged-record-${nodeKey}`} className="rdg-dragged-record">
|
||||||
|
<td className="rdg-dragged-record-cell"><TagNameFormatter record={tag} /></td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DraggedTagsLayer;
|
@@ -4,6 +4,7 @@ import { useTags } from '../../../../hooks';
|
|||||||
import { PRIVATE_COLUMN_KEY } from '../../../../constants';
|
import { PRIVATE_COLUMN_KEY } from '../../../../constants';
|
||||||
import { getRecordIdFromRecord } from '../../../../../metadata/utils/cell';
|
import { getRecordIdFromRecord } from '../../../../../metadata/utils/cell';
|
||||||
import { getTreeNodeKey } from '../../../../../components/sf-table/utils/tree';
|
import { getTreeNodeKey } from '../../../../../components/sf-table/utils/tree';
|
||||||
|
import { isNumber } from '../../../../../utils/number';
|
||||||
|
|
||||||
const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex }) => {
|
const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex }) => {
|
||||||
const { tagsData } = useTags();
|
const { tagsData } = useTags();
|
||||||
@@ -13,7 +14,7 @@ const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex
|
|||||||
}, [tagsData]);
|
}, [tagsData]);
|
||||||
|
|
||||||
const currentNode = useMemo(() => {
|
const currentNode = useMemo(() => {
|
||||||
return tree[treeNodeIndex];
|
return isNumber(treeNodeIndex) ? tree[treeNodeIndex] : null;
|
||||||
}, [tree, treeNodeIndex]);
|
}, [tree, treeNodeIndex]);
|
||||||
|
|
||||||
const tagColor = useMemo(() => {
|
const tagColor = useMemo(() => {
|
||||||
@@ -28,7 +29,7 @@ const TagNameFormatter = ({ record, isCellSelected, setDisplayTag, treeNodeIndex
|
|||||||
if (!isCellSelected) return;
|
if (!isCellSelected) return;
|
||||||
const tagId = getRecordIdFromRecord(record);
|
const tagId = getRecordIdFromRecord(record);
|
||||||
const nodeKey = getTreeNodeKey(currentNode);
|
const nodeKey = getTreeNodeKey(currentNode);
|
||||||
setDisplayTag(tagId, nodeKey);
|
setDisplayTag && setDisplayTag(tagId, nodeKey);
|
||||||
}, [isCellSelected, record, currentNode, setDisplayTag]);
|
}, [isCellSelected, record, currentNode, setDisplayTag]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import SFTable from '../../../../components/sf-table';
|
import SFTable from '../../../../components/sf-table';
|
||||||
import EditTagDialog from '../../../components/dialog/edit-tag-dialog';
|
import EditTagDialog from '../../../components/dialog/edit-tag-dialog';
|
||||||
import MergeTagsSelector from '../../../components/merge-tags-selector';
|
import MergeTagsSelector from '../../../components/merge-tags-selector';
|
||||||
|
import DraggedTagsLayer from './dragged-tags-layer';
|
||||||
import { createTableColumns } from './columns-factory';
|
import { createTableColumns } from './columns-factory';
|
||||||
import { createContextMenuOptions } from './context-menu-options';
|
import { createContextMenuOptions } from './context-menu-options';
|
||||||
import { gettext } from '../../../../utils/constants';
|
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 { 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 { LOCAL_KEY_TREE_NODE_FOLDED } from '../../../../components/sf-table/constants/tree';
|
||||||
import { isNumber } from '../../../../utils/number';
|
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';
|
import './index.css';
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ const TagsTable = ({
|
|||||||
loadMore,
|
loadMore,
|
||||||
getTagsTableWrapperOffsets,
|
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 [isShowNewSubTagDialog, setIsShowNewSubTagDialog] = useState(false);
|
||||||
const [isShowMergeTagsSelector, setIsShowMergeTagsSelector] = useState(false);
|
const [isShowMergeTagsSelector, setIsShowMergeTagsSelector] = useState(false);
|
||||||
@@ -208,6 +212,56 @@ const TagsTable = ({
|
|||||||
}
|
}
|
||||||
}, [scrollToCurrentSelectedCell]);
|
}, [scrollToCurrentSelectedCell]);
|
||||||
|
|
||||||
|
const renderCustomDraggedRows = useCallback((draggedNodesKeys) => {
|
||||||
|
if (!Array.isArray(draggedNodesKeys) || draggedNodesKeys.length === 0) return null;
|
||||||
|
return (
|
||||||
|
<DraggedTagsLayer draggedNodesKeys={draggedNodesKeys} />
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
const eventBus = EventBus.getInstance();
|
const eventBus = EventBus.getInstance();
|
||||||
const unsubscribeUpdateSearchResult = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SEARCH_RESULT, updateSearchResult);
|
const unsubscribeUpdateSearchResult = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SEARCH_RESULT, updateSearchResult);
|
||||||
@@ -242,6 +296,8 @@ const TagsTable = ({
|
|||||||
checkCellValueChanged={checkCellValueChanged}
|
checkCellValueChanged={checkCellValueChanged}
|
||||||
modifyColumnWidth={modifyColumnWidth}
|
modifyColumnWidth={modifyColumnWidth}
|
||||||
loadMore={loadMore}
|
loadMore={loadMore}
|
||||||
|
renderCustomDraggedRows={renderCustomDraggedRows}
|
||||||
|
moveRecords={moveTags}
|
||||||
/>
|
/>
|
||||||
{isShowNewSubTagDialog && (
|
{isShowNewSubTagDialog && (
|
||||||
<EditTagDialog tags={table.rows} title={gettext('New child tag')} onToggle={closeNewSubTagDialog} onSubmit={handelAddChildTag} />
|
<EditTagDialog tags={table.rows} title={gettext('New child tag')} onToggle={closeNewSubTagDialog} onSubmit={handelAddChildTag} />
|
||||||
|
Reference in New Issue
Block a user