From b32b3fe9049e46d01debedd5f8ed785ba83c7bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=A8=E5=9B=BD=E7=92=87?= <37972689+YangGuoXuan-0503@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:11:24 +0800 Subject: [PATCH] Feat metadata column order (#6622) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: metdata column order * feat: hidden column support adjust column order * feat: optimzie code --------- Co-authored-by: 杨国璇 --- .../hide-column-setter.jsx | 4 +- .../hidden-columns/hide-column.js | 118 +++++++++++-- .../hidden-columns/index.js | 31 +++- .../popover/hidden-column-popover/index.css | 39 ++++- .../popover/hidden-column-popover/index.js | 5 +- .../components/table/container.js | 16 +- .../table/table-main/records/index.js | 4 +- .../records/records-header/cell/index.css | 8 + .../records/records-header/cell/index.js | 155 ++++++++++++++---- .../records/records-header/index.js | 20 ++- .../components/view-toolbar/index.js | 5 + .../metadata-view/constants/event-bus-type.js | 3 + .../src/metadata/metadata-view/context.js | 5 + .../metadata-view/model/metadata/view.js | 20 ++- .../src/metadata/metadata-view/store/index.js | 13 ++ .../metadata-view/store/operations/apply.js | 5 + .../store/operations/constants.js | 6 +- .../metadata-view/store/server-operator.js | 9 + 18 files changed, 401 insertions(+), 65 deletions(-) diff --git a/frontend/src/metadata/metadata-view/components/data-process-setter/hide-column-setter.jsx b/frontend/src/metadata/metadata-view/components/data-process-setter/hide-column-setter.jsx index a07aa9fb85..1528568167 100644 --- a/frontend/src/metadata/metadata-view/components/data-process-setter/hide-column-setter.jsx +++ b/frontend/src/metadata/metadata-view/components/data-process-setter/hide-column-setter.jsx @@ -6,7 +6,7 @@ import { CommonlyUsedHotkey } from '../../_basic'; import { gettext } from '../../utils'; import { HideColumnPopover } from '../popover'; -const HideColumnSetter = ({ readOnly, columns, wrapperClass, target, hiddenColumns, modifyHiddenColumns }) => { +const HideColumnSetter = ({ readOnly, columns, wrapperClass, target, hiddenColumns, modifyHiddenColumns, modifyColumnOrder }) => { const [isShowSetter, setShowSetter] = useState(false); const validHiddenColumns = useMemo(() => { @@ -57,6 +57,7 @@ const HideColumnSetter = ({ readOnly, columns, wrapperClass, target, hiddenColum columns={columns} hidePopover={onSetterToggle} onChange={onChange} + modifyColumnOrder={modifyColumnOrder} /> )} @@ -70,6 +71,7 @@ HideColumnSetter.propTypes = { hiddenColumns: PropTypes.array, columns: PropTypes.array, modifyHiddenColumns: PropTypes.func, + modifyColumnOrder: PropTypes.func, }; export default HideColumnSetter; diff --git a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/hide-column.js b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/hide-column.js index b3d0d56b06..60a70bdcb6 100644 --- a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/hide-column.js +++ b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/hide-column.js @@ -1,10 +1,66 @@ import React, { useCallback } from 'react'; import PropTypes from 'prop-types'; +import { DragSource, DropTarget } from 'react-dnd'; import { Icon, Switch } from '@seafile/sf-metadata-ui-component'; import { COLUMNS_ICON_CONFIG } from '../../../../_basic'; import classNames from 'classnames'; -const HideColumnItem = ({ readOnly, column, isHidden, onChange }) => { +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 { columnIndex, currentIndex } = props; + return currentIndex > columnIndex; + } +}; +const dropTarget = { + drop(props, monitor) { + const source = monitor.getItem(); + const { column: targetColumn } = props; + if (targetColumn.key !== source.key && source.column.frozen === targetColumn.frozen) { + const target = { key: targetColumn.key }; + props.onMove(source.key, target.key); + } + } +}; + +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 HideColumnItem = ({ + isOver, + isDragging, + canDrop, + connectDragSource, + connectDragPreview, + connectDropTarget, + readOnly, + column, + columnIndex, + isHidden, + onChange, + onMouseEnter, + onMouseLeave, +}) => { const update = useCallback(() => { if (readOnly) return; @@ -12,28 +68,58 @@ const HideColumnItem = ({ readOnly, column, isHidden, onChange }) => { }, [readOnly, column, onChange]); return ( -
- - - {column.name} - - )} - onChange={update} - switchClassName="hide-column-item-switch" - /> -
+ <> + {connectDropTarget( + connectDragPreview( +
onMouseEnter(columnIndex)} + onMouseLeave={onMouseLeave} + > + {!readOnly && ( + <> + {connectDragSource( +
+ +
+ )} + + )} + + + {column.name} + + )} + onChange={update} + switchClassName="hide-column-item-switch" + /> +
+ ) + )} + ); }; HideColumnItem.propTypes = { readOnly: PropTypes.bool, isHidden: PropTypes.bool, + columnIndex: PropTypes.number, + currentIndex: PropTypes.number, column: PropTypes.object.isRequired, onChange: PropTypes.func.isRequired, + onMove: PropTypes.func, + onMouseEnter: PropTypes.func, + onMouseLeave: PropTypes.func, }; -export default HideColumnItem; +export default DropTarget('sfMetadataHiddenColumns', dropTarget, dropCollect)( + DragSource('sfMetadataHiddenColumns', dragSource, dragCollect)(HideColumnItem) +); diff --git a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/index.js b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/index.js index 434ca7defd..eff315ac4f 100644 --- a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/hidden-columns/index.js @@ -1,26 +1,44 @@ -import React, { useMemo } from 'react'; +import React, { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; +import { DropTarget } from 'react-dnd'; import { gettext } from '../../../../utils'; import HideColumn from './hide-column'; +import html5DragDropContext from '../../../../../../pages/wiki2/wiki-nav/html5DragDropContext'; + +const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange, modifyColumnOrder }) => { + const [currentIndex, setCurrentIndex] = useState(-1); -const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange }) => { const isEmpty = useMemo(() => { if (!Array.isArray(columns) || columns.length === 0) return true; return false; }, [columns]); + const onMouseEnter = useCallback((columnIndex) => { + if (currentIndex === columnIndex) return; + setCurrentIndex(columnIndex); + }, [currentIndex]); + + const onMouseLeave = useCallback(() => { + setCurrentIndex(-1); + }, []); + return (
{isEmpty &&
{gettext('No properties available to be hidden')}
} - {!isEmpty && columns.map((column) => { + {!isEmpty && columns.map((column, columnIndex) => { return ( ); })} @@ -33,6 +51,11 @@ HiddenColumns.propTypes = { hiddenColumns: PropTypes.array, columns: PropTypes.array, onChange: PropTypes.func, + modifyColumnOrder: PropTypes.func, }; -export default HiddenColumns; +const DndHiddenColumns = DropTarget('sfMetadataHiddenColumns', {}, connect => ({ + connectDropTarget: connect.dropTarget() +}))(HiddenColumns); + +export default html5DragDropContext(DndHiddenColumns); diff --git a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.css b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.css index 98b9e18783..00438adf5a 100644 --- a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.css +++ b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.css @@ -64,7 +64,11 @@ cursor: pointer; } -.sf-metadata-hide-columns-popover .hide-column-item .hide-column-item-switch, +.sf-metadata-hide-columns-popover .hide-column-item .hide-column-item-switch { + flex: 1; + overflow: hidden; +} + .sf-metadata-hide-columns-popover .hide-column-item .hide-column-item-switch .custom-switch { width: 100%; } @@ -79,7 +83,7 @@ .sf-metadata-hide-columns-popover .hide-column-item .custom-switch .custom-switch-description { margin-left: 0; padding-right: 5px; - width: 212px; + flex: 1; color: #212529; transition: .3s color; } @@ -127,3 +131,34 @@ color: #212529; cursor: pointer; } + +.sf-metadata-hide-columns-popover .hide-column-item .drag-hide-column-handle { + width: 14px; + margin-right: 10px; +} + +.sf-metadata-hide-columns-popover .hide-column-item .drag-hide-column-handle .sf-metadata-icon { + margin-right: 0px; +} + +.sf-metadata-hide-columns-popover .hide-columns-list .hide-column-item.disabled .drag-hide-column-handle { + display: none; +} + +.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop::after, +.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop-top::before { + content: ''; + height: 1px; + width: 100%; + position: absolute; + left: 0; + background: #666; +} + +.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop::after { + bottom: 0; +} + +.sf-metadata-hide-columns-popover .hide-column-item.hide-column-can-drop-top::before { + top: 0; +} diff --git a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.js b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.js index 4f76f22db5..c4768f7fd1 100644 --- a/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.js +++ b/frontend/src/metadata/metadata-view/components/popover/hidden-column-popover/index.js @@ -9,7 +9,7 @@ import { getEventClassName, gettext } from '../../../utils'; import './index.css'; -const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement, columns, hiddenColumns: oldHiddenColumns }) => { +const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement, columns, hiddenColumns: oldHiddenColumns, modifyColumnOrder }) => { const [searchValue, setSearchValue] = useState(''); const [hiddenColumns, setHiddenColumns] = useState(oldHiddenColumns); const displayColumns = useMemo(() => { @@ -104,7 +104,7 @@ const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement,
- + {!readOnly && !searchValue && (
{gettext('Hide all')}
@@ -125,6 +125,7 @@ HideColumnPopover.propTypes = { columns: PropTypes.array.isRequired, onChange: PropTypes.func.isRequired, hidePopover: PropTypes.func.isRequired, + modifyColumnOrder: PropTypes.func, }; export default HideColumnPopover; diff --git a/frontend/src/metadata/metadata-view/components/table/container.js b/frontend/src/metadata/metadata-view/components/table/container.js index 865e891209..38f60d278d 100644 --- a/frontend/src/metadata/metadata-view/components/table/container.js +++ b/frontend/src/metadata/metadata-view/components/table/container.js @@ -132,6 +132,10 @@ const Container = () => { store.modifyColumnWidth(columnKey, newWidth); }, [store]); + const modifyColumnOrder = useCallback((sourceColumnKey, targetColumnKey) => { + store.modifyColumnOrder(sourceColumnKey, targetColumnKey); + }, [store]); + const recordGetterById = useCallback((recordId) => { return metadata.id_row_map[recordId]; }, [metadata]); @@ -159,16 +163,19 @@ const Container = () => { useEffect(() => { document.addEventListener('keydown', onKeyDown); - const unsubscribeModifyFilters = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_FILTERS, modifyFilters); - const unsubscribeModifySorts = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SORTS, modifySorts); - const unsubscribeModifyGroupbys = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_GROUPBYS, modifyGroupbys); - const unsubscribeModifyHiddenColumns = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, modifyHiddenColumns); + const eventBus = window.sfMetadataContext.eventBus; + const unsubscribeModifyFilters = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_FILTERS, modifyFilters); + const unsubscribeModifySorts = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_SORTS, modifySorts); + const unsubscribeModifyGroupbys = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_GROUPBYS, modifyGroupbys); + const unsubscribeModifyHiddenColumns = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, modifyHiddenColumns); + const unsubscribeModifyColumnOrder = eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_COLUMN_ORDER, modifyColumnOrder); return () => { document.removeEventListener('keydown', onKeyDown); unsubscribeModifyFilters(); unsubscribeModifySorts(); unsubscribeModifyGroupbys(); unsubscribeModifyHiddenColumns(); + unsubscribeModifyColumnOrder(); }; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -197,6 +204,7 @@ const Container = () => { deleteColumn={deleteColumn} modifyColumnData={modifyColumnData} modifyColumnWidth={modifyColumnWidth} + modifyColumnOrder={modifyColumnOrder} /> )} {metadata.view.type === 'image' && ()} diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js index 1c0aa2c674..e05f81add3 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/index.js @@ -655,7 +655,7 @@ class Records extends Component { render() { const { recordIds, recordsCount, table, isGroupView, groupOffsetLeft, renameColumn, modifyColumnData, - deleteColumn } = this.props; + deleteColumn, modifyColumnOrder } = this.props; const { recordMetrics, columnMetrics, selectedRange, colOverScanStartIdx, colOverScanEndIdx } = this.state; const { columns, totalWidth, lastFrozenColumnKey } = columnMetrics; const containerWidth = totalWidth + SEQUENCE_COLUMN_WIDTH + CANVAS_RIGHT_INTERVAL + groupOffsetLeft; @@ -689,6 +689,7 @@ class Records extends Component { renameColumn={renameColumn} deleteColumn={deleteColumn} modifyColumnData={modifyColumnData} + modifyColumnOrder={modifyColumnOrder} /> {this.renderRecordsBody({ containerWidth })}
@@ -746,6 +747,7 @@ Records.propTypes = { deleteColumn: PropTypes.func, modifyColumnData: PropTypes.func, modifyColumnWidth: PropTypes.func, + modifyColumnOrder: PropTypes.func, getCopiedRecordsAndColumnsFromRange: PropTypes.func, }; diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.css b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.css index 724ab9ab32..dc09218d00 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.css +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.css @@ -7,3 +7,11 @@ font-size: 12px; transform: scale(.8); } + +.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; +} diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.js index 190c2bccb4..699042e03a 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/cell/index.js @@ -2,8 +2,9 @@ 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 { COLUMNS_ICON_CONFIG, COLUMNS_ICON_NAME } from '../../../../../../_basic'; +import { COLUMNS_ICON_CONFIG, COLUMNS_ICON_NAME, PRIVATE_COLUMN_KEY } from '../../../../../../_basic'; import ResizeColumnHandle from './resize-column-handle'; import { EVENT_BUS_TYPE } from '../../../../../../constants'; import DropdownMenu from './dropdown-menu'; @@ -11,7 +12,73 @@ import { gettext } from '../../../../../../utils'; 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; + const defaultColumnWidth = 200; + const offsetX = monitor.getClientOffset().x; + const width = document.querySelector('.sf-metadata-wrapper')?.clientWidth; + const left = window.innerWidth - width; + if (offsetX > width - 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, @@ -68,41 +135,67 @@ const Cell = ({ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SELECT_COLUMN, column); }, []); + const onContextMenu = useCallback((event) => { + event.preventDefault(); + event.stopPropagation(); + }, []); + const { key, name, type } = column; const headerIconTooltip = COLUMNS_ICON_NAME[type]; - return ( -
-
handleHeaderCellClick(column, frozen)} - > -
- - - - - {gettext(headerIconTooltip)} - -
- {name} -
+ const canModifyColumnOrder = window.sfMetadataContext.canModifyColumnOrder(); + const cell = ( +
handleHeaderCellClick(column, frozen)} + onContextMenu={onContextMenu} + > +
+ + + + + {gettext(headerIconTooltip)} + +
+ {name}
- {canEditColumnInfo && ( - - )} -
+ {canEditColumnInfo && ( + + )} +
); + if (!canModifyColumnOrder || column.key === PRIVATE_COLUMN_KEY.FILE_NAME) { + return ( +
+ {cell} +
+ ); + } + + return ( +
+ {connectDropTarget( + connectDragPreview( + connectDragSource( +
+ {cell} +
+ ) + ) + )} +
+ ); }; Cell.defaultProps = { @@ -123,4 +216,6 @@ Cell.propTypes = { modifyLocalColumnWidth: PropTypes.func, }; -export default Cell; +export default DropTarget('sfMetadataRecordHeaderCell', dropTarget, dropCollect)( + DragSource('sfMetadataRecordHeaderCell', dragSource, dragCollect)(Cell) +); diff --git a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/index.js b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/index.js index 86be6fe943..7ccb9342e9 100644 --- a/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/index.js +++ b/frontend/src/metadata/metadata-view/components/table/table-main/records/records-header/index.js @@ -1,5 +1,6 @@ import React, { useCallback, useMemo, useState } from 'react'; import PropTypes from 'prop-types'; +import { DropTarget } from 'react-dnd'; import { HEADER_HEIGHT_TYPE, isEmptyObject, @@ -12,6 +13,7 @@ import { getFrozenColumns } from '../../../../../utils/table-utils'; import { isFrozen, recalculateColumnMetricsByResizeColumn } from '../../../../../utils/column-utils'; import { GRID_HEADER_DEFAULT_HEIGHT, GRID_HEADER_DOUBLE_HEIGHT } from '../../../../../constants'; import InsertColumn from './insert-column'; +import html5DragDropContext from '../../../../../../../pages/wiki2/wiki-nav/html5DragDropContext'; const RecordsHeader = ({ isGroupView, @@ -28,6 +30,7 @@ const RecordsHeader = ({ selectNoneRecords, selectAllRecords, modifyColumnWidth: modifyColumnWidthAPI, + modifyColumnOrder: modifyColumnOrderAPI, ...props }) => { const [resizingColumnMetrics, setResizingColumnMetrics] = useState(null); @@ -74,8 +77,13 @@ const RecordsHeader = ({ modifyColumnWidthAPI && modifyColumnWidthAPI(column, newWidth); }, [modifyColumnWidthAPI]); + const modifyColumnOrder = useCallback((source, target) => { + modifyColumnOrderAPI && modifyColumnOrderAPI(source.key, target.key); + }, [modifyColumnOrderAPI]); + const frozenColumns = getFrozenColumns(columnMetrics.columns); const displayColumns = columnMetrics.columns.slice(colOverScanStartIdx, colOverScanEndIdx); + const frozenColumnsWidth = frozenColumns.reduce((total, c) => total + c.width, 0); return (
@@ -104,9 +112,11 @@ const RecordsHeader = ({ column={column} style={style} isLastFrozenCell={isLastFrozenCell} + frozenColumnsWidth={frozenColumnsWidth} isHideTriangle={isHideTriangle} modifyLocalColumnWidth={modifyLocalColumnWidth} modifyColumnWidth={modifyColumnWidth} + onMove={modifyColumnOrder} {...props} /> ); @@ -121,8 +131,10 @@ const RecordsHeader = ({ groupOffsetLeft={groupOffsetLeft} height={height} column={column} + frozenColumnsWidth={frozenColumnsWidth} modifyLocalColumnWidth={modifyLocalColumnWidth} modifyColumnWidth={modifyColumnWidth} + onMove={modifyColumnOrder} {...props} /> ); @@ -131,7 +143,6 @@ const RecordsHeader = ({
); - }; RecordsHeader.propTypes = { @@ -150,4 +161,9 @@ RecordsHeader.propTypes = { selectAllRecords: PropTypes.func, }; -export default RecordsHeader; +const DndRecordHeaderContainer = DropTarget('sfMetadataRecordHeaderCell', {}, connect => ({ + connectDropTarget: connect.dropTarget() +}))(RecordsHeader); + +export default html5DragDropContext(DndRecordHeaderContainer); + diff --git a/frontend/src/metadata/metadata-view/components/view-toolbar/index.js b/frontend/src/metadata/metadata-view/components/view-toolbar/index.js index 06ed020859..af33c9e939 100644 --- a/frontend/src/metadata/metadata-view/components/view-toolbar/index.js +++ b/frontend/src/metadata/metadata-view/components/view-toolbar/index.js @@ -39,6 +39,10 @@ const ViewToolBar = ({ viewId }) => { window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_HIDDEN_COLUMNS, hiddenColumns); }, []); + const modifyColumnOrder = useCallback((sourceColumnKey, targetColumnKey) => { + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_COLUMN_ORDER, sourceColumnKey, targetColumnKey); + }, []); + const viewChange = useCallback((view) => { setView(view); }, []); @@ -109,6 +113,7 @@ const ViewToolBar = ({ viewId }) => { columns={viewColumns.slice(1)} hiddenColumns={view.hidden_columns || []} modifyHiddenColumns={modifyHiddenColumns} + modifyColumnOrder={modifyColumnOrder} />
diff --git a/frontend/src/metadata/metadata-view/constants/event-bus-type.js b/frontend/src/metadata/metadata-view/constants/event-bus-type.js index 927de118c4..7df837c39f 100644 --- a/frontend/src/metadata/metadata-view/constants/event-bus-type.js +++ b/frontend/src/metadata/metadata-view/constants/event-bus-type.js @@ -42,6 +42,9 @@ export const EVENT_BUS_TYPE = { // change VIEW_CHANGED: 'view_changed', + // column + MODIFY_COLUMN_ORDER: 'modify_column_order', + // data status SAVING: 'saving', SAVED: 'saved', diff --git a/frontend/src/metadata/metadata-view/context.js b/frontend/src/metadata/metadata-view/context.js index d68b3d7511..37db751a9c 100644 --- a/frontend/src/metadata/metadata-view/context.js +++ b/frontend/src/metadata/metadata-view/context.js @@ -125,6 +125,11 @@ class Context { return true; }; + canModifyColumnOrder = () => { + if (this.permission === 'r') return false; + return true; + }; + canModifyView = (view) => { if (this.permission === 'r') return false; return true; diff --git a/frontend/src/metadata/metadata-view/model/metadata/view.js b/frontend/src/metadata/metadata-view/model/metadata/view.js index 12fa61ea04..ed4846628a 100644 --- a/frontend/src/metadata/metadata-view/model/metadata/view.js +++ b/frontend/src/metadata/metadata-view/model/metadata/view.js @@ -1,4 +1,4 @@ -import { VIEW_NOT_DISPLAY_COLUMN_KEYS } from '../../_basic'; +import { getColumnByKey, VIEW_NOT_DISPLAY_COLUMN_KEYS } from '../../_basic'; class View { constructor(object, columns) { @@ -25,7 +25,23 @@ class View { // columns this.available_columns = columns || []; - this.columns = this.available_columns.filter(column => !VIEW_NOT_DISPLAY_COLUMN_KEYS.includes(column.key)); + this.display_available_columns = this.available_columns.filter(column => !VIEW_NOT_DISPLAY_COLUMN_KEYS.includes(column.key)); + this.columns = this.display_available_columns; + + // order + let columnsKeys = object.columns_keys || []; + if (columnsKeys.length === 0) { + this.columns_keys = this.display_available_columns.map(c => c.key); + } else { + let columns = columnsKeys.map(key => getColumnByKey(this.display_available_columns, key)).filter(c => c); + this.display_available_columns.forEach(column => { + if (!getColumnByKey(columns, column.key)) { + columns.push(column); + } + }); + this.columns_keys = columns.map(c => c.key); + this.columns = columns; + } } } diff --git a/frontend/src/metadata/metadata-view/store/index.js b/frontend/src/metadata/metadata-view/store/index.js index b5296d4f86..2023be2846 100644 --- a/frontend/src/metadata/metadata-view/store/index.js +++ b/frontend/src/metadata/metadata-view/store/index.js @@ -431,6 +431,19 @@ class Store { this.applyOperation(operation); }; + modifyColumnOrder = (sourceColumnKey, targetColumnKey) => { + const type = OPERATION_TYPE.MODIFY_COLUMN_ORDER; + const { columns_keys } = this.data.view; + const targetColumnIndex = columns_keys.indexOf(targetColumnKey); + let newColumnsKeys = columns_keys.slice(0); + newColumnsKeys = newColumnsKeys.filter(key => key !== sourceColumnKey); + newColumnsKeys.splice(targetColumnIndex, 0, sourceColumnKey); + const operation = this.createOperation({ + type, repo_id: this.repoId, view_id: this.viewId, new_columns_keys: newColumnsKeys, old_columns_keys: columns_keys + }); + this.applyOperation(operation); + }; + } export default Store; diff --git a/frontend/src/metadata/metadata-view/store/operations/apply.js b/frontend/src/metadata/metadata-view/store/operations/apply.js index 87988f24ce..80e843038a 100644 --- a/frontend/src/metadata/metadata-view/store/operations/apply.js +++ b/frontend/src/metadata/metadata-view/store/operations/apply.js @@ -171,6 +171,11 @@ export default function apply(data, operation) { data.view = new View(data.view, data.columns); return data; } + case OPERATION_TYPE.MODIFY_COLUMN_ORDER: { + const { new_columns_keys } = operation; + data.view = new View({ ...data.view, columns_keys: new_columns_keys }, data.columns); + return data; + } default: { return data; } diff --git a/frontend/src/metadata/metadata-view/store/operations/constants.js b/frontend/src/metadata/metadata-view/store/operations/constants.js index d79a80895c..fea2cfa805 100644 --- a/frontend/src/metadata/metadata-view/store/operations/constants.js +++ b/frontend/src/metadata/metadata-view/store/operations/constants.js @@ -16,6 +16,7 @@ export const OPERATION_TYPE = { RENAME_COLUMN: 'rename_column', MODIFY_COLUMN_DATA: 'modify_column_data', MODIFY_COLUMN_WIDTH: 'modify_column_width', + MODIFY_COLUMN_ORDER: 'modify_column_order', }; export const OPERATION_ATTRIBUTES = { @@ -33,7 +34,8 @@ export const OPERATION_ATTRIBUTES = { [OPERATION_TYPE.RENAME_COLUMN]: ['repo_id', 'column_key', 'new_name', 'old_name'], [OPERATION_TYPE.MODIFY_COLUMN_DATA]: ['repo_id', 'column_key', 'new_data', 'old_data'], [OPERATION_TYPE.DELETE_COLUMN]: ['repo_id', 'column_key', 'column'], - [OPERATION_TYPE.MODIFY_COLUMN_WIDTH]: ['repo_id', 'column_key', 'new_width', 'old_width'], + [OPERATION_TYPE.MODIFY_COLUMN_WIDTH]: ['column_key', 'new_width', 'old_width'], + [OPERATION_TYPE.MODIFY_COLUMN_ORDER]: ['repo_id', 'view_id', 'new_columns_keys', 'old_columns_keys'], }; export const UNDO_OPERATION_TYPE = [ @@ -61,6 +63,7 @@ export const NEED_APPLY_AFTER_SERVER_OPERATION = [ OPERATION_TYPE.RENAME_COLUMN, OPERATION_TYPE.MODIFY_COLUMN_DATA, OPERATION_TYPE.MODIFY_COLUMN_WIDTH, + OPERATION_TYPE.MODIFY_COLUMN_ORDER, ]; export const VIEW_OPERATION = [ @@ -76,4 +79,5 @@ export const COLUMN_OPERATION = [ OPERATION_TYPE.RENAME_COLUMN, OPERATION_TYPE.MODIFY_COLUMN_DATA, OPERATION_TYPE.MODIFY_COLUMN_WIDTH, + OPERATION_TYPE.MODIFY_COLUMN_ORDER, ]; diff --git a/frontend/src/metadata/metadata-view/store/server-operator.js b/frontend/src/metadata/metadata-view/store/server-operator.js index d608eed35c..4659b68cce 100644 --- a/frontend/src/metadata/metadata-view/store/server-operator.js +++ b/frontend/src/metadata/metadata-view/store/server-operator.js @@ -97,6 +97,15 @@ class ServerOperator { } break; } + case OPERATION_TYPE.MODIFY_COLUMN_ORDER: { + const { repo_id, view_id, new_columns_keys } = operation; + window.sfMetadataContext.modifyView(repo_id, view_id, { columns_keys: new_columns_keys }).then(res => { + callback({ operation }); + }).catch(error => { + callback({ error: gettext('Failed to modify property order') }); + }); + break; + } case OPERATION_TYPE.MODIFY_FILTERS: { const { repo_id, view_id, filter_conjunction, filters } = operation; window.sfMetadataContext.modifyView(repo_id, view_id, { filters, filter_conjunction }).then(res => {