1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-07-14 07:24:58 +00:00

Feat metadata column order (#6622)

* feat: metdata column order

* feat: hidden column support adjust column order

* feat: optimzie code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇 2024-08-23 17:11:24 +08:00 committed by GitHub
parent 645099a405
commit b32b3fe904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 401 additions and 65 deletions

View File

@ -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;

View File

@ -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 (
<div className={classNames('hide-column-item', { 'disabled': readOnly })}>
<Switch
disabled={readOnly}
checked={isHidden}
placeholder={(
<>
<Icon iconName={COLUMNS_ICON_CONFIG[column.type]} />
<span className="text-truncate">{column.name}</span>
</>
)}
onChange={update}
switchClassName="hide-column-item-switch"
/>
</div>
<>
{connectDropTarget(
connectDragPreview(
<div
className={classNames('hide-column-item', {
'disabled': readOnly,
'hide-column-can-drop-top': isOver && canDrop && isDragging,
'hide-column-can-drop': isOver && canDrop && !isDragging
})}
onMouseEnter={() => onMouseEnter(columnIndex)}
onMouseLeave={onMouseLeave}
>
{!readOnly && (
<>
{connectDragSource(
<div className="drag-hide-column-handle">
<Icon iconName="drag" />
</div>
)}
</>
)}
<Switch
disabled={readOnly}
checked={isHidden}
placeholder={(
<>
<Icon iconName={COLUMNS_ICON_CONFIG[column.type]} />
<span className="text-truncate">{column.name}</span>
</>
)}
onChange={update}
switchClassName="hide-column-item-switch"
/>
</div>
)
)}
</>
);
};
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)
);

View File

@ -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 (
<div className={classnames('hide-columns-list', { 'empty-hide-columns-container': isEmpty })}>
{isEmpty && <div className="empty-hide-columns-list">{gettext('No properties available to be hidden')}</div>}
{!isEmpty && columns.map((column) => {
{!isEmpty && columns.map((column, columnIndex) => {
return (
<HideColumn
key={column.key}
readOnly={readOnly}
columnIndex={columnIndex}
currentIndex={currentIndex}
isHidden={!hiddenColumns.includes(column.key)}
column={column}
onChange={onChange}
onMove={modifyColumnOrder}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
})}
@ -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);

View File

@ -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;
}

View File

@ -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,
<div className="sf-metadata-hide-columns-search-container">
<SearchInput placeholder={gettext('Search property')} onKeyDown={onKeyDown} onChange={onChangeSearch} autoFocus={true}/>
</div>
<HiddenColumns readOnly={readOnly} columns={displayColumns} hiddenColumns={hiddenColumns} onChange={hideColumn} />
<HiddenColumns readOnly={readOnly} columns={displayColumns} hiddenColumns={hiddenColumns} onChange={hideColumn} modifyColumnOrder={modifyColumnOrder} />
{!readOnly && !searchValue && (
<div className="sf-metadata-hide-columns-operations">
<div className="sf-metadata-hide-columns-operation px-2" onClick={hideAll} aria-label={gettext('Hide all')}>{gettext('Hide all')}</div>
@ -125,6 +125,7 @@ HideColumnPopover.propTypes = {
columns: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
hidePopover: PropTypes.func.isRequired,
modifyColumnOrder: PropTypes.func,
};
export default HideColumnPopover;

View File

@ -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' && (<Gallery />)}

View File

@ -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 })}
</div>
@ -746,6 +747,7 @@ Records.propTypes = {
deleteColumn: PropTypes.func,
modifyColumnData: PropTypes.func,
modifyColumnWidth: PropTypes.func,
modifyColumnOrder: PropTypes.func,
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
};

View File

@ -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;
}

View File

@ -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 (
<div key={key} className="sf-metadata-record-header-cell">
<div
className={classnames('sf-metadata-result-table-cell column', { 'table-last--frozen': isLastFrozenCell })}
ref={headerCellRef}
style={style}
id={`sf-metadata-column-${key}`}
onClick={() => handleHeaderCellClick(column, frozen)}
>
<div className="sf-metadata-result-column-content sf-metadata-record-header-cell-left d-flex align-items-center text-truncate">
<span className="mr-2" id={`header-icon-${key}`}>
<Icon iconName={COLUMNS_ICON_CONFIG[type]} className="sf-metadata-column-icon" />
</span>
<UncontrolledTooltip placement="bottom" target={`header-icon-${key}`} fade={false} trigger="hover">
{gettext(headerIconTooltip)}
</UncontrolledTooltip>
<div className="header-name d-flex">
<span title={name} className={classnames('header-name-text', { 'double': height === 56 })}>{name}</span>
</div>
const canModifyColumnOrder = window.sfMetadataContext.canModifyColumnOrder();
const cell = (
<div
className={classnames('sf-metadata-result-table-cell column', { 'table-last--frozen': isLastFrozenCell })}
ref={headerCellRef}
style={style}
id={`sf-metadata-column-${key}`}
onClick={() => handleHeaderCellClick(column, frozen)}
onContextMenu={onContextMenu}
>
<div className="sf-metadata-result-column-content sf-metadata-record-header-cell-left d-flex align-items-center text-truncate">
<span className="mr-2" id={`header-icon-${key}`}>
<Icon iconName={COLUMNS_ICON_CONFIG[type]} className="sf-metadata-column-icon" />
</span>
<UncontrolledTooltip placement="bottom" target={`header-icon-${key}`} fade={false} trigger="hover">
{gettext(headerIconTooltip)}
</UncontrolledTooltip>
<div className="header-name d-flex">
<span title={name} className={classnames('header-name-text', { 'double': height === 56 })}>{name}</span>
</div>
{canEditColumnInfo && (
<DropdownMenu
column={column}
renameColumn={renameColumn}
deleteColumn={deleteColumn}
modifyColumnData={modifyColumnData}
/>
)}
<ResizeColumnHandle onDrag={onDrag} onDragEnd={onDragEnd} />
</div>
{canEditColumnInfo && (
<DropdownMenu
column={column}
renameColumn={renameColumn}
deleteColumn={deleteColumn}
modifyColumnData={modifyColumnData}
/>
)}
<ResizeColumnHandle onDrag={onDrag} onDragEnd={onDragEnd} />
</div>
);
if (!canModifyColumnOrder || column.key === PRIVATE_COLUMN_KEY.FILE_NAME) {
return (
<div key={key} className="sf-metadata-record-header-cell">
{cell}
</div>
);
}
return (
<div key={key} className="sf-metadata-record-header-cell">
{connectDropTarget(
connectDragPreview(
connectDragSource(
<div style={{ opacity: isDragging ? 0.2 : 1 }} className={classnames('rdg-can-drop', { 'rdg-dropping': isOver && canDrop })}>
{cell}
</div>
)
)
)}
</div>
);
};
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)
);

View File

@ -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 (
<div className="static-sf-metadata-result-content grid-header" style={{ height: height + 1 }}>
@ -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 = ({
</div>
</div>
);
};
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);

View File

@ -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}
/>
</div>
<div className="sf-metadata-tool-right-operations"></div>

View File

@ -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',

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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,
];

View File

@ -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 => {