diff --git a/frontend/src/metadata/views/table/table-main/records-header/cell/index.css b/frontend/src/metadata/views/table/table-main/records-header/cell/index.css index 1460e6f194..04ce4b8d56 100644 --- a/frontend/src/metadata/views/table/table-main/records-header/cell/index.css +++ b/frontend/src/metadata/views/table/table-main/records-header/cell/index.css @@ -8,10 +8,38 @@ transform: scale(.8); } +.sf-metadata-record-header-cell .sf-metadata-result-table-cell.column.name-column { + cursor: default; +} + .sf-metadata-record-header-cell .rdg-can-drop > .sf-metadata-result-table-cell.column { cursor: move; } .sf-metadata-record-header-cell .rdg-dropping > .sf-metadata-result-table-cell.column { background: #ececec; + position: relative; +} + +.sf-metadata-record-header-cell .rdg-dropping-position > .sf-metadata-result-table-cell.column::before { + content: ''; + position: absolute; + top: 10%; + height: 80%; + width: 1px; + background-color: #2d7ff9; + border-radius: 50%; + z-index: 1; +} + +.sf-metadata-record-header-cell .rdg-dropping-position-left > .sf-metadata-result-table-cell.column::before { + left: -1px; +} + +.sf-metadata-record-header-cell .rdg-dropping-position-right > .sf-metadata-result-table-cell.column::before { + right: -1px; +} + +.sf-metadata-record-header-cell .rdg-dropping-position-none > .sf-metadata-result-table-cell.column::before { + display: none; } diff --git a/frontend/src/metadata/views/table/table-main/records-header/cell/index.js b/frontend/src/metadata/views/table/table-main/records-header/cell/index.js index 8f5fab0569..ac5ffb42f4 100644 --- a/frontend/src/metadata/views/table/table-main/records-header/cell/index.js +++ b/frontend/src/metadata/views/table/table-main/records-header/cell/index.js @@ -2,98 +2,36 @@ import React, { useRef, useCallback, useMemo } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; import { UncontrolledTooltip } from 'reactstrap'; -import { DragSource, DropTarget } from 'react-dnd'; import { Icon } from '@seafile/sf-metadata-ui-component'; import ResizeColumnHandle from './resize-column-handle'; import DropdownMenu from './dropdown-menu'; import { gettext } from '../../../../../../utils/constants'; -import { COLUMNS_ICON_CONFIG, COLUMNS_ICON_NAME, EVENT_BUS_TYPE } from '../../../../../constants'; +import { COLUMNS_ICON_CONFIG, COLUMNS_ICON_NAME, EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY } from '../../../../../constants'; import './index.css'; - -const dragSource = { - beginDrag: props => { - return { key: props.column.key, column: props.column }; - }, - endDrag(props, monitor) { - const source = monitor.getItem(); - const didDrop = monitor.didDrop(); - let target = {}; - if (!didDrop) { - return { source, target }; - } - }, - isDragging(props) { - const { column, dragged } = props; - const { key } = dragged; - return key === column.key; - } -}; - -const dropTarget = { - hover(props, monitor, component) { - // This is fired very often and lets you perform side effects. - if (!window.sfMetadataBody) return; - let defaultColumnWidth = 200; - const offsetX = monitor.getClientOffset().x; - const width = document.querySelector('.sf-metadata-wrapper')?.clientWidth; - const left = window.innerWidth - width; - if (width <= 800) { - defaultColumnWidth = 20; - } - if (offsetX > window.innerWidth - defaultColumnWidth) { - window.sfMetadataBody.scrollToRight(); - } else if (offsetX < props.frozenColumnsWidth + defaultColumnWidth + left) { - window.sfMetadataBody.scrollToLeft(); - } else { - window.sfMetadataBody.clearHorizontalScroll(); - } - }, - drop(props, monitor) { - const source = monitor.getItem(); - const { column: targetColumn } = props; - if (targetColumn.key !== source.key && source.column.frozen === targetColumn.frozen) { - let target = { key: targetColumn.key }; - props.onMove(source, target); - window.sfMetadataBody.clearHorizontalScroll(); - } - } -}; - -const dragCollect = (connect, monitor) => ({ - connectDragSource: connect.dragSource(), - connectDragPreview: connect.dragPreview(), - isDragging: monitor.isDragging(), -}); - -const dropCollect = (connect, monitor) => ({ - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver(), - canDrop: monitor.canDrop(), - dragged: monitor.getItem(), -}); - const Cell = ({ - isOver, - isDragging, - canDrop, - connectDragSource, - connectDragPreview, - connectDropTarget, frozen, groupOffsetLeft, isLastFrozenCell, height, isHideTriangle, column, + columnIndex, style: propsStyle, + draggingColumnKey, + draggingColumnIndex, + dragOverColumnKey, view, + frozenColumnsWidth, renameColumn, deleteColumn, modifyColumnData, modifyLocalColumnWidth, modifyColumnWidth, + onMove, + updateDraggingKey, + updateDragOverKey, }) => { const headerCellRef = useRef(null); @@ -120,14 +58,14 @@ const Cell = ({ return right - left; }, []); - const onDrag = useCallback((e) => { + const onDraggingColumnWidth = useCallback((e) => { const width = getWidthFromMouseEvent(e); if (width > 0) { modifyLocalColumnWidth(column, width); } }, [column, getWidthFromMouseEvent, modifyLocalColumnWidth]); - const onDragEnd = useCallback((e) => { + const handleColumnWidth = useCallback((e) => { const width = getWidthFromMouseEvent(e); if (width > 0) { modifyColumnWidth(column, Math.max(width, 50)); @@ -143,12 +81,65 @@ const Cell = ({ event.stopPropagation(); }, []); + const onDragStart = useCallback((event) => { + const dragData = JSON.stringify({ type: 'sf-metadata-view-header-order', column_key: column.key, column }); + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('application/drag-sf-metadata-view-header-order', dragData); + updateDraggingKey(column.key); + }, [column, updateDraggingKey]); + + const onDragEnter = useCallback(() => { + updateDragOverKey(column.key); + }, [column, updateDragOverKey]); + + const onDragLeave = useCallback(() => { + updateDragOverKey(null); + }, [updateDragOverKey]); + + const onDragOver = useCallback((event) => { + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + updateDragOverKey(column.key); + if (!window.sfMetadataBody) return; + let defaultColumnWidth = 200; + const offsetX = event.clientX; + const width = document.querySelector('.sf-metadata-wrapper')?.clientWidth; + const left = window.innerWidth - width; + if (width <= 800) { + defaultColumnWidth = 20; + } + if (offsetX > window.innerWidth - defaultColumnWidth) { + window.sfMetadataBody.scrollToRight(); + } else if (offsetX < frozenColumnsWidth + defaultColumnWidth + left) { + window.sfMetadataBody.scrollToLeft(); + } else { + window.sfMetadataBody.clearHorizontalScroll(); + } + }, [column, frozenColumnsWidth, updateDragOverKey]); + + const onDrop = useCallback((event) => { + event.stopPropagation(); + let dragData = event.dataTransfer.getData('application/drag-sf-metadata-view-header-order'); + if (!dragData) return false; + dragData = JSON.parse(dragData); + if (dragData.type !== 'sf-metadata-view-header-order' || !dragData.column_key) return false; + if (dragData.column_key !== column.key && dragData.column.frozen === column.frozen) { + onMove && onMove({ key: dragData.column_key }, { key: column.key }); + } + }, [column, onMove]); + + const onDragEnd = useCallback(() => { + updateDraggingKey(null); + updateDragOverKey(null); + }, [updateDraggingKey, updateDragOverKey]); + const { key, name, type } = column; const headerIconTooltip = COLUMNS_ICON_NAME[type]; const canModifyColumnOrder = window.sfMetadataContext.canModifyColumnOrder(); + const isNameColumn = key === PRIVATE_COLUMN_KEY.FILE_NAME; const cell = (
)} - +
); - if (!canModifyColumnOrder) { + if (!canModifyColumnOrder || isNameColumn) { return (
{cell} @@ -187,17 +178,28 @@ const Cell = ({ ); } + const isOver = dragOverColumnKey === column.key; + return (
- {connectDropTarget( - connectDragPreview( - connectDragSource( -
- {cell} -
- ) - ) - )} +
columnIndex, + 'rdg-dropping-position-right': isOver && draggingColumnIndex < columnIndex, + 'rdg-dropping-position-none': isOver && draggingColumnIndex === columnIndex + })} + onDragStart={onDragStart} + onDragEnter={onDragEnter} + onDragLeave={onDragLeave} + onDragOver={onDragOver} + onDrop={onDrop} + onDragEnd={onDragEnd} + > + {cell} +
); }; @@ -210,17 +212,21 @@ Cell.propTypes = { groupOffsetLeft: PropTypes.number, height: PropTypes.number, column: PropTypes.object, + columnIndex: PropTypes.number, style: PropTypes.object, frozen: PropTypes.bool, isLastFrozenCell: PropTypes.bool, isHideTriangle: PropTypes.bool, + draggingColumnKey: PropTypes.string, + draggingColumnIndex: PropTypes.number, + dragOverColumnKey: PropTypes.string, view: PropTypes.object, renameColumn: PropTypes.func, deleteColumn: PropTypes.func, modifyColumnData: PropTypes.func, modifyLocalColumnWidth: PropTypes.func, + updateDraggingKey: PropTypes.func, + updateDragOverKey: PropTypes.func, }; -export default DropTarget('sfMetadataRecordHeaderCell', dropTarget, dropCollect)( - DragSource('sfMetadataRecordHeaderCell', dragSource, dragCollect)(Cell) -); +export default Cell; diff --git a/frontend/src/metadata/views/table/table-main/records-header/index.js b/frontend/src/metadata/views/table/table-main/records-header/index.js index 3ca1822703..9a0d2ca57b 100644 --- a/frontend/src/metadata/views/table/table-main/records-header/index.js +++ b/frontend/src/metadata/views/table/table-main/records-header/index.js @@ -1,7 +1,5 @@ import React, { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; -import { DropTarget } from 'react-dnd'; -import html5DragDropContext from '../../../../../pages/wiki2/wiki-nav/html5DragDropContext'; import Cell from './cell'; import ActionsCell from './actions-cell'; import InsertColumn from './insert-column'; @@ -29,6 +27,8 @@ const RecordsHeader = ({ ...props }) => { const [resizingColumnMetrics, setResizingColumnMetrics] = useState(null); + const [draggingColumnKey, setDraggingCellKey] = useState(null); + const [dragOverColumnKey, setDragOverCellKey] = useState(null); const settings = useMemo(() => table.header_settings || {}, [table]); const isHideTriangle = useMemo(() => settings && settings.is_hide_triangle, [settings]); const height = useMemo(() => { @@ -76,9 +76,20 @@ const RecordsHeader = ({ modifyColumnOrderAPI && modifyColumnOrderAPI(source.key, target.key); }, [modifyColumnOrderAPI]); + const updateDraggingKey = useCallback((cellKey) => { + if (cellKey === draggingColumnKey) return; + setDraggingCellKey(cellKey); + }, [draggingColumnKey]); + + const updateDragOverKey = useCallback((cellKey) => { + if (cellKey === dragOverColumnKey) return; + setDragOverCellKey(cellKey); + }, [dragOverColumnKey]); + const frozenColumns = getFrozenColumns(columnMetrics.columns); const displayColumns = columnMetrics.columns.slice(colOverScanStartIdx, colOverScanEndIdx); const frozenColumnsWidth = frozenColumns.reduce((total, c) => total + c.width, groupOffsetLeft + SEQUENCE_COLUMN_WIDTH); + const draggingColumnIndex = draggingColumnKey ? columnMetrics.columns.findIndex(c => c.key === draggingColumnKey) : -1; return (
@@ -95,7 +106,7 @@ const RecordsHeader = ({ selectNoneRecords={selectNoneRecords} selectAllRecords={selectAllRecords} /> - {frozenColumns.map(column => { + {frozenColumns.map((column, columnIndex) => { const { key } = column; const style = { backgroundColor: '#f9f9f9' }; const isLastFrozenCell = key === lastFrozenColumnKey; @@ -105,21 +116,27 @@ const RecordsHeader = ({ key={key} height={height} column={column} + columnIndex={columnIndex} style={style} isLastFrozenCell={isLastFrozenCell} frozenColumnsWidth={frozenColumnsWidth} isHideTriangle={isHideTriangle} + draggingColumnKey={draggingColumnKey} + draggingColumnIndex={draggingColumnIndex} + dragOverColumnKey={dragOverColumnKey} view={table.view} modifyLocalColumnWidth={modifyLocalColumnWidth} modifyColumnWidth={modifyColumnWidth} onMove={modifyColumnOrder} + updateDraggingKey={updateDraggingKey} + updateDragOverKey={updateDragOverKey} {...props} /> ); })}
{/* scroll */} - {displayColumns.map(column => { + {displayColumns.map((column, columnIndex) => { return ( ); @@ -158,8 +181,4 @@ RecordsHeader.propTypes = { selectAllRecords: PropTypes.func, }; -const DndRecordHeaderContainer = DropTarget('sfMetadataRecordHeaderCell', {}, connect => ({ - connectDropTarget: connect.dropTarget() -}))(RecordsHeader); - -export default html5DragDropContext(DndRecordHeaderContainer); +export default RecordsHeader;