mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-03 16:10:26 +00:00
1142 lines
40 KiB
JavaScript
1142 lines
40 KiB
JavaScript
import React, { isValidElement, cloneElement } from 'react';
|
|
import PropTypes from 'prop-types';
|
|
import deepCopy from 'deep-copy';
|
|
import toaster from '../../../toast';
|
|
import EditorPortal from '../../editors/editor-portal';
|
|
import EditorContainer from '../../editors/editor-container';
|
|
import DragHandler from '../drag-handler';
|
|
import DragMask from '../drag-mask';
|
|
import SelectionRangeMask from '../selection-range-mask';
|
|
import SelectionMask from '../selection-mask';
|
|
import { gettext } from '../../../../utils/constants';
|
|
import { Utils } from '../../../../utils/utils';
|
|
import { isEmptyObject } from '../../../../utils/object';
|
|
import { KeyCodes } from '../../../../constants';
|
|
import { GROUP_ROW_TYPE } from '../../constants/group';
|
|
import TRANSFER_TYPES from '../../constants/transfer-types';
|
|
import { GRID_HEADER_DOUBLE_HEIGHT, GRID_HEADER_DEFAULT_HEIGHT, HEADER_HEIGHT_TYPE, PASTE_SOURCE, EDITOR_TYPE } from '../../constants/grid';
|
|
import {
|
|
getNewSelectedRange, getSelectedDimensions, selectedRangeIsSingleCell,
|
|
getSelectedRangeDimensions, getSelectedRow, getSelectedColumn,
|
|
getRecordsFromSelectedRange, getSelectedCellValue, checkIsSelectedCellEditable,
|
|
} from '../../utils/selected-cell-utils';
|
|
import RecordMetrics from '../../utils/record-metrics';
|
|
import setEventTransfer from '../../utils/set-event-transfer';
|
|
import getEventTransfer from '../../utils/get-event-transfer';
|
|
import { getGroupRecordByIndex } from '../../utils/group-metrics';
|
|
import { isSpace } from '../../../../utils/hotkey';
|
|
import EventBus from '../../../common/event-bus';
|
|
import { EVENT_BUS_TYPE } from '../../constants/event-bus-type';
|
|
import { isCtrlKeyHeldDown, isKeyPrintable } from '../../../../utils/keyboard-utils';
|
|
import { checkIsColumnSupportPreview, getColumnIndexByKey } from '../../utils/column';
|
|
|
|
import './index.css';
|
|
|
|
class InteractionMasks extends React.Component {
|
|
|
|
static defaultProps = {
|
|
enableCellSelect: true,
|
|
isGroupView: false,
|
|
groupOffsetLeft: 0,
|
|
};
|
|
|
|
throttle = null;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
const initPosition = { idx: -1, rowIdx: -1, groupRecordIndex: -1 };
|
|
this.state = {
|
|
selectedPosition: initPosition,
|
|
selectedRange: {
|
|
topLeft: initPosition,
|
|
bottomRight: initPosition,
|
|
startCell: null,
|
|
cursorCell: null,
|
|
isDragging: false,
|
|
},
|
|
draggedRange: null,
|
|
isEditorEnabled: false,
|
|
openEditorMode: '',
|
|
};
|
|
this.eventBus = EventBus.getInstance();
|
|
this.pasteSource = PASTE_SOURCE.COPY;
|
|
this.cutPosition = null;
|
|
this.selectionMask = null;
|
|
}
|
|
|
|
componentDidMount() {
|
|
this.unsubscribeSelectColumn = this.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_COLUMN, this.onColumnSelect);
|
|
this.unsubscribeDragEnter = this.eventBus.subscribe(EVENT_BUS_TYPE.DRAG_ENTER, this.handleDragEnter);
|
|
this.unsubscribeSelectCell = this.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, this.onSelectCell);
|
|
this.unsubscribeSelectNone = this.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone);
|
|
this.unsubscribeSelectStart = this.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_START, this.onSelectCellRangeStarted);
|
|
this.unsubscribeSelectUpdate = this.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_UPDATE, this.onSelectCellRangeUpdated);
|
|
this.unsubscribeSelectEnd = this.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_END, this.onSelectCellRangeEnded);
|
|
this.unsubscribeOpenEditorEvent = this.eventBus.subscribe(EVENT_BUS_TYPE.OPEN_EDITOR, this.onOpenEditorEvent);
|
|
this.unsubscribeCloseEditorEvent = this.eventBus.subscribe(EVENT_BUS_TYPE.CLOSE_EDITOR, this.onCloseEditorEvent);
|
|
this.unsubscribeCopy = this.eventBus.subscribe(EVENT_BUS_TYPE.COPY_CELLS, this.onCopy);
|
|
this.unsubscribePaste = this.eventBus.subscribe(EVENT_BUS_TYPE.PASTE_CELLS, this.onPaste);
|
|
this.unsubscribeCut = this.eventBus.subscribe(EVENT_BUS_TYPE.CUT_CELLS, this.onCut);
|
|
}
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
const { selectedRange, isEditorEnabled } = this.state;
|
|
const { selectedRange: prevSelectedRange, isEditorEnabled: prevIsEditorEnabled } = prevState;
|
|
const isEditorClosed = isEditorEnabled !== prevIsEditorEnabled && !isEditorEnabled;
|
|
const isSelectedRangeChanged = selectedRange !== prevSelectedRange && (selectedRange.topLeft !== prevSelectedRange.topLeft || selectedRange.bottomRight !== prevSelectedRange.bottomRight);
|
|
if (isSelectedRangeChanged || isEditorClosed) {
|
|
this.focus();
|
|
}
|
|
}
|
|
|
|
componentWillUnmount() {
|
|
this.unsubscribeSelectColumn();
|
|
this.unsubscribeSelectCell();
|
|
this.unsubscribeSelectStart();
|
|
this.unsubscribeSelectUpdate();
|
|
this.unsubscribeSelectEnd();
|
|
this.unsubscribeOpenEditorEvent();
|
|
this.unsubscribeCloseEditorEvent();
|
|
this.unsubscribeCopy();
|
|
this.unsubscribePaste();
|
|
this.unsubscribeCut();
|
|
this.setState = (state, callback) => {
|
|
return;
|
|
};
|
|
}
|
|
|
|
onColumnSelect = (column) => {
|
|
const { columns, isGroupView, recordsCount } = this.props;
|
|
if (isGroupView) return;
|
|
const selectColumnIndex = getColumnIndexByKey(column.key, columns);
|
|
this.setState({
|
|
selectedPosition: { ...this.state.selectedPosition, idx: selectColumnIndex, rowIdx: 0 },
|
|
selectedRange: {
|
|
startCell: { idx: selectColumnIndex, rowIdx: 0 },
|
|
topLeft: { idx: selectColumnIndex, rowIdx: 0 },
|
|
bottomRight: { idx: selectColumnIndex, rowIdx: recordsCount - 1 },
|
|
isDragging: false,
|
|
}
|
|
});
|
|
};
|
|
|
|
onOpenEditorEvent = (mode) => {
|
|
this.setState({ openEditorMode: mode }, () => {
|
|
this.openEditor(null);
|
|
});
|
|
};
|
|
|
|
onCloseEditorEvent = () => {
|
|
if (this.state.isEditorEnabled) {
|
|
this.closeEditor();
|
|
}
|
|
};
|
|
|
|
onSelectCell = (cell, openEditor) => {
|
|
const { selectedPosition, isEditorEnabled } = this.state;
|
|
const callback = openEditor ? this.openEditor : () => null;
|
|
|
|
if (isEditorEnabled) {
|
|
this.closeEditor();
|
|
}
|
|
|
|
this.setState((prevState) => {
|
|
const next = { ...selectedPosition, ...cell };
|
|
if (this.isCellWithinBounds(next)) {
|
|
return {
|
|
selectedPosition: next,
|
|
selectedRange: {
|
|
topLeft: next,
|
|
bottomRight: next,
|
|
startCell: next,
|
|
cursorCell: next,
|
|
isDragging: false,
|
|
}
|
|
};
|
|
}
|
|
return prevState;
|
|
}, callback);
|
|
};
|
|
|
|
selectNone = () => {
|
|
const initPosition = { idx: -1, rowIdx: -1, groupRecordIndex: -1 };
|
|
this.setState({
|
|
selectedPosition: initPosition,
|
|
selectedRange: {
|
|
topLeft: initPosition,
|
|
bottomRight: initPosition,
|
|
startCell: null,
|
|
cursorCell: null,
|
|
},
|
|
});
|
|
this.props.selectNone();
|
|
};
|
|
|
|
getSelectedPosition = () => {
|
|
const { topLeft, bottomRight } = this.state.selectedRange;
|
|
return {
|
|
top: topLeft.rowIdx,
|
|
bottom: bottomRight.rowIdx,
|
|
left: topLeft.idx,
|
|
right: bottomRight.idx,
|
|
};
|
|
};
|
|
|
|
getSelectedRange = () => {
|
|
return this.state.selectedRange;
|
|
};
|
|
|
|
selectCell = (groupRecordIndex, rowIdx, idx) => {
|
|
const selectedPosition = { idx, groupRecordIndex, rowIdx };
|
|
this.setState({
|
|
selectedPosition,
|
|
selectedRange: {
|
|
topLeft: selectedPosition,
|
|
bottomRight: selectedPosition,
|
|
startCell: selectedPosition,
|
|
cursorCell: selectedPosition,
|
|
},
|
|
});
|
|
};
|
|
|
|
// onCellSelect || onKeyDown
|
|
openEditor = (event = null) => {
|
|
const { key } = event || {};
|
|
const { isEditorEnabled, selectedPosition, openEditorMode } = this.state;
|
|
const { columns } = this.props;
|
|
const selectedColumn = getSelectedColumn({ selectedPosition, columns });
|
|
|
|
// how to open editors?
|
|
// 1. editor is closed
|
|
// 2. record-cell is editable or open editor with preview mode
|
|
if (!isEditorEnabled && (this.checkIsSelectedCellEditable() || (openEditorMode === EDITOR_TYPE.PREVIEWER && checkIsColumnSupportPreview(selectedColumn)))) {
|
|
this.setState({
|
|
isEditorEnabled: true,
|
|
firstEditorKeyDown: key,
|
|
editorPosition: this.getEditorPosition()
|
|
});
|
|
}
|
|
};
|
|
|
|
closeEditor = () => {
|
|
this.setState({
|
|
isEditorEnabled: false,
|
|
firstEditorKeyDown: null,
|
|
editorPosition: null,
|
|
openEditorMode: ''
|
|
});
|
|
};
|
|
|
|
onSelectCellRangeStarted = (selectedPosition) => {
|
|
if (!this.isCellWithinBounds(selectedPosition)) return;
|
|
|
|
const selectedRange = this.createSingleCellSelectedRange(selectedPosition, true);
|
|
this.setState({ selectedRange }, () => {
|
|
if (Utils.isFunction(this.props.onCellRangeSelectionStarted)) {
|
|
this.props.onCellRangeSelectionStarted(this.state.selectedRange);
|
|
}
|
|
});
|
|
};
|
|
|
|
onSelectCellRangeUpdated = (cellPosition, isFromKeyboard, callback) => {
|
|
if (!this.state.selectedRange.isDragging && !isFromKeyboard) {
|
|
return;
|
|
}
|
|
|
|
if (!this.isCellWithinBounds(cellPosition)) {
|
|
return;
|
|
}
|
|
|
|
const startCell = this.state.selectedRange.startCell || this.state.selectedPosition;
|
|
const { topLeft, bottomRight } = getNewSelectedRange(startCell, cellPosition);
|
|
const selectedRange = {
|
|
// default the startCell to the selected cell, in case we've just started via keyboard
|
|
startCell: this.state.selectedPosition,
|
|
// assign the previous state (which will override the startCell if we already have one)
|
|
...this.state.selectedRange,
|
|
// assign the new state - the bounds of the range, and the new cursor cell
|
|
topLeft,
|
|
bottomRight,
|
|
cursorCell: cellPosition
|
|
};
|
|
|
|
this.setState({ selectedRange }, () => {
|
|
if (Utils.isFunction(this.props.onCellRangeSelectionUpdated)) {
|
|
this.props.onCellRangeSelectionUpdated(this.state.selectedRange);
|
|
}
|
|
if (Utils.isFunction(callback)) {
|
|
callback(this.state.selectedRange);
|
|
}
|
|
});
|
|
};
|
|
|
|
onSelectCellRangeEnded = () => {
|
|
const selectedRange = { ...this.state.selectedRange, isDragging: false };
|
|
this.setState({ selectedRange }, () => {
|
|
if (Utils.isFunction(this.props.onCellRangeSelectionCompleted)) {
|
|
this.props.onCellRangeSelectionCompleted(this.state.selectedRange);
|
|
}
|
|
});
|
|
};
|
|
|
|
createSingleCellSelectedRange(cellPosition, isDragging) {
|
|
return {
|
|
topLeft: cellPosition,
|
|
bottomRight: cellPosition,
|
|
startCell: cellPosition,
|
|
cursorCell: cellPosition,
|
|
isDragging
|
|
};
|
|
}
|
|
|
|
focus = () => {
|
|
if (this.selectionMask && !this.isFocused()) {
|
|
this.selectionMask.focus();
|
|
}
|
|
};
|
|
|
|
isFocused = () => {
|
|
return document.activeElement === this.selectionMask;
|
|
};
|
|
|
|
checkIsCellSelected = () => {
|
|
const { selectedPosition } = this.state;
|
|
return selectedPosition.idx !== -1 && selectedPosition.rowIdx !== -1;
|
|
};
|
|
|
|
isCellWithinBounds = ({ idx, rowIdx }) => {
|
|
const { columns, recordsCount } = this.props;
|
|
const maxRowIdx = recordsCount;
|
|
return rowIdx >= 0 && rowIdx < maxRowIdx && idx >= 0 && idx < columns.length;
|
|
};
|
|
|
|
checkIsSelectedCellEditable = () => {
|
|
const { enableCellSelect, columns, isGroupView, recordGetterByIndex, checkCanModifyRecord } = this.props;
|
|
const { selectedPosition } = this.state;
|
|
return checkIsSelectedCellEditable({ enableCellSelect, columns, isGroupView, selectedPosition, recordGetterByIndex, checkCanModifyRecord });
|
|
};
|
|
|
|
isGridSelected = () => {
|
|
return this.isCellWithinBounds(this.state.selectedPosition);
|
|
};
|
|
|
|
getSelectedDimensions = (selectedPosition) => {
|
|
const { columns, rowHeight, isGroupView, groupOffsetLeft, getRowTop: getRecordTopFromRecordsBody } = this.props;
|
|
const scrollLeft = this.props.getScrollLeft();
|
|
return {
|
|
...getSelectedDimensions({
|
|
selectedPosition, columns, scrollLeft, rowHeight, isGroupView, groupOffsetLeft, getRecordTopFromRecordsBody
|
|
})
|
|
};
|
|
};
|
|
|
|
getSelectedRangeDimensions = (selectedRange) => {
|
|
const { columns, rowHeight, isGroupView, groups, groupMetrics, groupOffsetLeft, getRowTop: getRecordTopFromRecordsBody } = this.props;
|
|
return {
|
|
...getSelectedRangeDimensions({
|
|
selectedRange, columns, rowHeight, isGroupView, groups, groupMetrics, groupOffsetLeft, getRecordTopFromRecordsBody,
|
|
})
|
|
};
|
|
};
|
|
|
|
setScrollLeft = (scrollLeft, scrollTop) => {
|
|
const { selectionMask, state: { selectedPosition } } = this;
|
|
this.setMaskScrollLeft(selectionMask, selectedPosition, scrollLeft, scrollTop);
|
|
};
|
|
|
|
geHeaderHeight = () => {
|
|
// const { table } = this.props;
|
|
const settings = {}; // table.header_settings || {};
|
|
const heightMode = isEmptyObject(settings) ? HEADER_HEIGHT_TYPE.DEFAULT : settings.header_height;
|
|
const containerHeight = heightMode === HEADER_HEIGHT_TYPE.DOUBLE ? GRID_HEADER_DOUBLE_HEIGHT : GRID_HEADER_DEFAULT_HEIGHT;
|
|
// 1: header border-bottom
|
|
return containerHeight + 1;
|
|
};
|
|
|
|
setMaskScrollLeft = (mask, position, scrollLeft, scrollTop) => {
|
|
const headerHeight = this.geHeaderHeight();
|
|
if (mask) {
|
|
const { idx, rowIdx, groupRecordIndex } = position;
|
|
if (idx >= 0 && rowIdx >= 0) {
|
|
const { columns, getRowTop, isGroupView, groupOffsetLeft } = this.props;
|
|
const column = columns[idx];
|
|
const frozen = !!column.frozen;
|
|
if (frozen) {
|
|
// use fixed
|
|
let top = -scrollTop + getRowTop(isGroupView ? groupRecordIndex : rowIdx) + headerHeight;
|
|
let left = column.left;
|
|
if (isGroupView) {
|
|
top += 1;
|
|
left += groupOffsetLeft;
|
|
}
|
|
mask.style.position = 'fixed';
|
|
mask.style.top = top + 'px';
|
|
mask.style.left = left + 'px';
|
|
mask.style.transform = 'none';
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
cancelSetScrollLeft = () => {
|
|
if (this.selectionMask) {
|
|
this.cancelSetMaskScrollLeft(this.selectionMask, this.state.selectedPosition);
|
|
}
|
|
};
|
|
|
|
cancelSetMaskScrollLeft = (mask, position) => {
|
|
const { left, top } = this.getSelectedDimensions(position);
|
|
mask.style.position = 'absolute';
|
|
mask.style.top = 0;
|
|
mask.style.left = 0;
|
|
mask.style.transform = `translate(${left}px, ${top}px)`;
|
|
};
|
|
|
|
getEditorPosition = () => {
|
|
if (this.selectionMask) {
|
|
const { editorPortalTarget } = this.props;
|
|
const { left: selectionMaskLeft, top: selectionMaskTop } = this.selectionMask.getBoundingClientRect();
|
|
if (editorPortalTarget === document.body) {
|
|
const { scrollLeft, scrollTop } = document.scrollingElement || document.documentElement;
|
|
return {
|
|
left: selectionMaskLeft + scrollLeft,
|
|
top: selectionMaskTop + scrollTop
|
|
};
|
|
}
|
|
|
|
const { left: portalTargetLeft, top: portalTargetTop } = editorPortalTarget.getBoundingClientRect();
|
|
const { scrollLeft, scrollTop } = editorPortalTarget;
|
|
return {
|
|
left: selectionMaskLeft - portalTargetLeft + scrollLeft,
|
|
top: selectionMaskTop - portalTargetTop + scrollTop
|
|
};
|
|
}
|
|
};
|
|
|
|
onCommit = (updated, closeEditor = true) => {
|
|
if (this.props.modifyRecord) {
|
|
this.props.modifyRecord(updated);
|
|
}
|
|
if (closeEditor) {
|
|
this.closeEditor();
|
|
}
|
|
};
|
|
|
|
onCommitCancel = () => {
|
|
this.closeEditor();
|
|
};
|
|
|
|
handleSpaceKeyDown = (e) => {
|
|
e.stopPropagation();
|
|
e.nativeEvent.stopImmediatePropagation();
|
|
const { selectedPosition } = this.state;
|
|
const { isGroupView, recordGetterByIndex } = this.props;
|
|
const record = getSelectedRow({ selectedPosition, isGroupView, recordGetterByIndex });
|
|
if (this.props.handleSpaceKeyDown) {
|
|
this.props.handleSpaceKeyDown(record);
|
|
}
|
|
};
|
|
|
|
onKeyDown = (e) => {
|
|
const keyCode = e.keyCode;
|
|
if (isCtrlKeyHeldDown(e)) {
|
|
this.onPressKeyWithCtrl(e);
|
|
} else if (keyCode === KeyCodes.Escape) {
|
|
this.onPressEscape(e);
|
|
} else if (keyCode === KeyCodes.Tab) {
|
|
this.onPressTab(e);
|
|
} else if (this.isKeyboardNavigationEvent(e)) {
|
|
this.changeCellFromEvent(e);
|
|
} else if (isSpace(e)) {
|
|
this.handleSpaceKeyDown(e);
|
|
} else if (isKeyPrintable(keyCode) || keyCode === KeyCodes.Enter) {
|
|
this.openEditor(e);
|
|
} else if (keyCode === KeyCodes.Backspace || keyCode === KeyCodes.Delete) {
|
|
const name = e.target.className;
|
|
if (name === 'rdg-selected') {
|
|
e.preventDefault();
|
|
this.handleSelectCellsDelete();
|
|
}
|
|
}
|
|
};
|
|
|
|
handleSelectCellsDelete = () => {
|
|
if (this.props.handleSelectCellsDelete) {
|
|
const { isGroupView, recordGetterByIndex, columns } = this.props;
|
|
const { selectedRange } = this.state;
|
|
const { topLeft, bottomRight } = selectedRange;
|
|
const recordsFromSelectedRange = getRecordsFromSelectedRange({ selectedRange, isGroupView, recordGetterByIndex });
|
|
if (recordsFromSelectedRange.length === 0) {
|
|
return;
|
|
}
|
|
|
|
const { idx: startColumnIdx } = topLeft;
|
|
const { idx: endColumnIdx } = bottomRight;
|
|
const columnsFromSelectedRange = columns.slice(startColumnIdx, endColumnIdx);
|
|
if (columnsFromSelectedRange.length === 0) {
|
|
return;
|
|
}
|
|
this.props.handleSelectCellsDelete(recordsFromSelectedRange, columnsFromSelectedRange);
|
|
}
|
|
};
|
|
|
|
onCopySelected = () => {
|
|
this.onCopyCells();
|
|
};
|
|
|
|
onCopy = (e) => {
|
|
e.preventDefault();
|
|
const { recordMetrics } = this.props;
|
|
|
|
// select the records to copy
|
|
const selectedRecordIds = RecordMetrics.getSelectedIds(recordMetrics);
|
|
if (selectedRecordIds.length > 0) {
|
|
this.copyRows(e, selectedRecordIds);
|
|
return;
|
|
}
|
|
|
|
// window.getSelection() doesn't work on the content of <input> in FireFox, Edge and IE.
|
|
// The selectionStart and selectionEnd properties could be used to work around this.
|
|
let selectTxt = window.getSelection().toString();
|
|
if (!selectTxt && e.target.value) {
|
|
const { selectionStart, selectionEnd } = e.target;
|
|
selectTxt = e.target.value.substring(selectionStart, selectionEnd);
|
|
}
|
|
if (selectTxt) {
|
|
this.copyText(e, selectTxt);
|
|
return;
|
|
}
|
|
|
|
// when activeElement is not cellMask, can't copy cell
|
|
if (!this.isCellMaskActive()) {
|
|
return;
|
|
}
|
|
this.onCopyCells(e);
|
|
};
|
|
|
|
onPaste = (e) => {
|
|
// when activeElement is not cellMask or has no permission, can't paste cell
|
|
if (!this.isCellMaskActive() || !this.props.canModifyRecords) return;
|
|
const { columns, isGroupView } = this.props;
|
|
const { selectedPosition, selectedRange } = this.state;
|
|
const { idx, rowIdx } = selectedPosition;
|
|
if (idx === -1 || rowIdx === -1) return; // prevent paste when no cell selected
|
|
const cliperData = getEventTransfer(e);
|
|
if (!cliperData) return;
|
|
|
|
const cliperDataType = cliperData.type;
|
|
const copied = cliperData[TRANSFER_TYPES.METADATA_FRAGMENT];
|
|
let copiedRecordsCount = 0;
|
|
let copiedColumnsCount = 0;
|
|
if (cliperDataType === TRANSFER_TYPES.METADATA_FRAGMENT) {
|
|
const { selectedRecordIds, copiedRange } = copied;
|
|
if (Array.isArray(selectedRecordIds) && selectedRecordIds.length > 0) {
|
|
// copy from selected records
|
|
copiedRecordsCount = selectedRecordIds.length;
|
|
copiedColumnsCount = columns.length;
|
|
} else {
|
|
// copy from selected range
|
|
const { topLeft: copiedTopLeft, bottomRight: copiedBottomRight } = copiedRange;
|
|
const { idx: startCopiedColumnIndex, rowIdx: startCopiedRecordIndex } = copiedTopLeft;
|
|
const { idx: endCopiedColumnIndex, rowIdx: endCopiedRecordIndex } = copiedBottomRight;
|
|
copiedRecordsCount = endCopiedRecordIndex - startCopiedRecordIndex + 1;
|
|
copiedColumnsCount = endCopiedColumnIndex - startCopiedColumnIndex + 1;
|
|
}
|
|
} else {
|
|
const { copiedRecords, copiedColumns } = copied;
|
|
copiedRecordsCount = copiedRecords.length;
|
|
copiedColumnsCount = copiedColumns.length;
|
|
}
|
|
const multiplePaste = this.isMultiplePaste(copiedRecordsCount, copiedColumnsCount);
|
|
if (this.props.paste) {
|
|
this.props.paste({
|
|
copied,
|
|
multiplePaste,
|
|
type: cliperDataType,
|
|
pasteRange: selectedRange,
|
|
columns,
|
|
isGroupView,
|
|
pasteSource: this.pasteSource,
|
|
cutPosition: this.cutPosition,
|
|
});
|
|
if (!multiplePaste) {
|
|
this.setPasteRange(copiedRecordsCount, copiedColumnsCount);
|
|
}
|
|
}
|
|
};
|
|
|
|
onCut = (event) => {
|
|
// when activeElement is not cellMask or has no permission, can't paste cell
|
|
if (!this.isCellMaskActive() || !this.props.canModifyRecords) return;
|
|
const { selectedPosition, selectedRange } = this.state;
|
|
const { idx, rowIdx } = selectedPosition;
|
|
if (idx === -1 || rowIdx === -1) return; // prevent paste when no cell selected
|
|
event.preventDefault();
|
|
const { tableId: copiedTableId, columns, isGroupView, recordGetterByIndex, getCopiedRecordsAndColumnsFromRange, getClientCellValueDisplayString } = this.props;
|
|
if (rowIdx < 0 || idx < 0) {
|
|
return; // can not copy when no cell select
|
|
}
|
|
const { topLeft, bottomRight } = selectedRange;
|
|
const copiedCellsCount = (bottomRight.rowIdx - topLeft.rowIdx + 1) * (bottomRight.idx - topLeft.idx + 1);
|
|
const type = copiedCellsCount <= 0 ? 'text' : TRANSFER_TYPES.METADATA_FRAGMENT;
|
|
const tip = copiedCellsCount > 1 ? gettext('xxx cells cut').replace('xxx', copiedCellsCount) : gettext('1 cell cut');
|
|
toaster.success(tip);
|
|
const copied = { copiedRange: selectedRange };
|
|
const { copiedRecords, copiedColumns } = getCopiedRecordsAndColumnsFromRange({ type, copied, columns, isGroupView });
|
|
setEventTransfer({
|
|
type,
|
|
event,
|
|
copiedRange: { ...selectedRange },
|
|
copiedRecords,
|
|
copiedColumns,
|
|
copiedTableId,
|
|
tableData: {
|
|
columns,
|
|
},
|
|
isGroupView,
|
|
recordGetterByIndex,
|
|
getClientCellValueDisplayString,
|
|
});
|
|
if (copiedCellsCount > 0) {
|
|
this.pasteSource = PASTE_SOURCE.CUT;
|
|
this.cutPosition = { ...selectedPosition };
|
|
}
|
|
};
|
|
|
|
copyText = (event, copiedText) => {
|
|
const type = 'text';
|
|
setEventTransfer({
|
|
type,
|
|
event,
|
|
copiedText,
|
|
});
|
|
};
|
|
|
|
copyRows = (event, selectedRecordIds) => {
|
|
const { tableId: copiedTableId, columns, recordGetterById, isGroupView, getCopiedRecordsAndColumnsFromRange, getClientCellValueDisplayString } = this.props;
|
|
const copiedRowsCount = selectedRecordIds.length;
|
|
toaster.success(
|
|
copiedRowsCount > 1 ? gettext('xxx rows are copied.').replace('xxx', copiedRowsCount) : gettext('1 row is copied.')
|
|
);
|
|
const type = TRANSFER_TYPES.METADATA_FRAGMENT;
|
|
const copied = { selectedRecordIds };
|
|
const { copiedRecords, copiedColumns } = getCopiedRecordsAndColumnsFromRange({ type, copied, columns, isGroupView });
|
|
setEventTransfer({
|
|
type,
|
|
event,
|
|
selectedRecordIds,
|
|
copiedRecords,
|
|
copiedColumns,
|
|
copiedTableId,
|
|
tableData: {
|
|
columns,
|
|
},
|
|
recordGetterById,
|
|
getClientCellValueDisplayString,
|
|
});
|
|
};
|
|
|
|
onCopyCells = (event) => {
|
|
const { tableId: copiedTableId, columns, isGroupView, recordGetterByIndex, getCopiedRecordsAndColumnsFromRange, getClientCellValueDisplayString } = this.props;
|
|
const { selectedPosition, selectedRange } = this.state;
|
|
const { rowIdx, idx } = selectedPosition;
|
|
if (rowIdx < 0 || idx < 0) {
|
|
return; // can not copy when no cell select
|
|
}
|
|
const { topLeft, bottomRight } = selectedRange;
|
|
const type = TRANSFER_TYPES.METADATA_FRAGMENT;
|
|
const copiedCellsCount = (bottomRight.rowIdx - topLeft.rowIdx + 1) * (bottomRight.idx - topLeft.idx + 1);
|
|
toaster.success(
|
|
copiedCellsCount > 1 ? gettext('xxx cells copied').replace('xxx', copiedCellsCount) : gettext('1 cell copied')
|
|
);
|
|
const copied = { copiedRange: selectedRange };
|
|
const { copiedRecords, copiedColumns } = getCopiedRecordsAndColumnsFromRange({ type, copied, columns, isGroupView });
|
|
setEventTransfer({
|
|
type,
|
|
event,
|
|
copiedRange: { ...selectedRange },
|
|
copiedRecords,
|
|
copiedColumns,
|
|
copiedTableId,
|
|
tableData: {
|
|
columns,
|
|
},
|
|
isGroupView,
|
|
recordGetterByIndex,
|
|
getClientCellValueDisplayString,
|
|
});
|
|
this.cutPosition = null;
|
|
this.pasteSource = PASTE_SOURCE.COPY;
|
|
};
|
|
|
|
isMultiplePaste = (copiedRecordsCount, copiedColumnsCount) => {
|
|
const { selectedRange } = this.state;
|
|
const { topLeft, bottomRight } = selectedRange;
|
|
const { idx: startColumnIndex, rowIdx: startRecordIndex } = topLeft;
|
|
const { idx: endColumnIndex, rowIdx: endRecordIndex } = bottomRight;
|
|
return Number.isInteger((endColumnIndex - startColumnIndex + 1) / copiedColumnsCount) && Number.isInteger((endRecordIndex - startRecordIndex + 1) / copiedRecordsCount);
|
|
};
|
|
|
|
setPasteRange = (copiedRecordsCount, copiedColumnsCount) => {
|
|
const { recordsCount, columns } = this.props;
|
|
const { selectedPosition, selectedRange } = this.state;
|
|
const { topLeft } = selectedRange;
|
|
const { idx, rowIdx } = topLeft;
|
|
const columnsLen = columns.length;
|
|
const groupRecordIndex = selectedPosition.groupRecordIndex;
|
|
let nextColumnIndex = idx + copiedColumnsCount - 1;
|
|
let nextRecordIndex = rowIdx + copiedRecordsCount - 1;
|
|
if (nextColumnIndex >= columnsLen) {
|
|
nextColumnIndex = columnsLen - 1;
|
|
}
|
|
if (nextRecordIndex >= recordsCount) {
|
|
nextRecordIndex = recordsCount - 1;
|
|
}
|
|
const nextSelectedRange = {
|
|
topLeft,
|
|
startCell: selectedPosition,
|
|
bottomRight: {
|
|
idx: nextColumnIndex,
|
|
rowIdx: nextRecordIndex,
|
|
groupRecordIndex,
|
|
},
|
|
cursorCell: {
|
|
idx: selectedPosition.idx,
|
|
rowIdx: selectedPosition.rowIdx,
|
|
groupRecordIndex,
|
|
}
|
|
};
|
|
this.setState({
|
|
selectedRange: {
|
|
...selectedRange,
|
|
...nextSelectedRange
|
|
}
|
|
}, () => {
|
|
this.focus();
|
|
});
|
|
return nextSelectedRange;
|
|
};
|
|
|
|
onPressKeyWithCtrl = () => {
|
|
|
|
};
|
|
|
|
onPressEscape = () => {
|
|
|
|
};
|
|
|
|
onPressTab = (e) => {
|
|
this.changeCellFromEvent(e);
|
|
};
|
|
|
|
getLeftInterval = () => {
|
|
const { isGroupView, columns, groupOffsetLeft, frozenColumnsWidth } = this.props;
|
|
const firstColumnFrozen = columns[0] ? columns[0].frozen : false;
|
|
let leftInterval = 0;
|
|
if (firstColumnFrozen) {
|
|
leftInterval = groupOffsetLeft + frozenColumnsWidth;
|
|
if (isGroupView) {
|
|
leftInterval += groupOffsetLeft;
|
|
}
|
|
} else {
|
|
leftInterval = 0;
|
|
}
|
|
return leftInterval;
|
|
};
|
|
|
|
handleVerticalArrowAction = (current, actionType) => {
|
|
const { isGroupView, groupMetrics, rowHeight } = this.props;
|
|
const step = actionType === 'ArrowDown' ? 1 : -1;
|
|
if (isGroupView) {
|
|
const groupRows = groupMetrics.groupRows || [];
|
|
const groupRowsLen = groupRows.length;
|
|
const { groupRecordIndex: currentGroupRowIndex } = current;
|
|
let nextGroupRowIndex = currentGroupRowIndex + step;
|
|
let nextGroupRow;
|
|
while (nextGroupRowIndex > 0 && nextGroupRowIndex < groupRowsLen) {
|
|
nextGroupRow = getGroupRecordByIndex(nextGroupRowIndex, groupMetrics);
|
|
if (nextGroupRow.type === GROUP_ROW_TYPE.ROW) {
|
|
break;
|
|
}
|
|
nextGroupRowIndex += step;
|
|
}
|
|
if (!nextGroupRow || nextGroupRow.type !== GROUP_ROW_TYPE.ROW) {
|
|
return;
|
|
}
|
|
|
|
const currentScrollTop = this.props.getGroupCanvasScrollTop() || 0;
|
|
const { rowIdx: nextRowIdx, top: nextRowTop } = nextGroupRow;
|
|
let newScrollTop;
|
|
|
|
// 32: footerHeight; 16: preview of next row.
|
|
const HEADER_HEIGHT = 150;
|
|
if (nextRowTop <= currentScrollTop + 16) {
|
|
newScrollTop = nextRowTop - 16;
|
|
} else if (nextRowTop + HEADER_HEIGHT - currentScrollTop >= window.innerHeight - 32 - 16) {
|
|
newScrollTop = nextRowTop + HEADER_HEIGHT - window.innerHeight + 32 + rowHeight + 16;
|
|
}
|
|
if (newScrollTop !== undefined) {
|
|
this.props.setGroupCanvasScrollTop(newScrollTop);
|
|
}
|
|
return { ...current, rowIdx: nextRowIdx, groupRecordIndex: nextGroupRowIndex };
|
|
} else {
|
|
return { ...current, rowIdx: current.rowIdx + step };
|
|
}
|
|
};
|
|
|
|
handleLeftArrowAction = (current) => {
|
|
let cellContainer = this.selectionMask;
|
|
if (!cellContainer) return;
|
|
const { columns } = this.props;
|
|
const rect = cellContainer.getBoundingClientRect();
|
|
const leftInterval = this.getLeftInterval();
|
|
const nextColumnWidth = columns[current.idx - 1] ? columns[current.idx - 1].width : 0;
|
|
const { left: tableContentLeft, right } = this.props.getTableContentRect();
|
|
const viewLeft = tableContentLeft + 130;
|
|
|
|
// selectMask is outside the viewport, scroll to next column
|
|
if (rect.x < 0 || rect.x > right) {
|
|
this.props.scrollToColumn(current.idx - 1);
|
|
} else if (nextColumnWidth > rect.x - leftInterval - viewLeft) {
|
|
// selectMask is part of the viewport, newScrollLeft = columnWidth - visibleWidth
|
|
const newScrollLeft = nextColumnWidth - (rect.x - leftInterval - viewLeft);
|
|
this.props.setRecordsScrollLeft(this.props.getScrollLeft() - newScrollLeft);
|
|
}
|
|
return ({ ...current, idx: current.idx === 0 ? 0 : current.idx - 1 });
|
|
};
|
|
|
|
handleRightArrowAction = (current) => {
|
|
let cellContainer = this.selectionMask;
|
|
if (!cellContainer) return;
|
|
const { columns } = this.props;
|
|
const rect = cellContainer.getBoundingClientRect();
|
|
const columnIdx = current.idx;
|
|
const column = columns[columnIdx];
|
|
if (columnIdx === 1 && column.frozen === true) {
|
|
this.props.scrollToColumn(1);
|
|
} else {
|
|
const { right: tableContentRight } = this.props.getTableContentRect();
|
|
const nextColumnWidth = columns[columnIdx + 1] ? columns[columnIdx + 1].width : 0;
|
|
// selectMask is outside the viewport, scroll to next column
|
|
if (rect.x < 0 || rect.x > tableContentRight) {
|
|
this.props.scrollToColumn(columnIdx + 1);
|
|
} else if (rect.x + rect.width + nextColumnWidth > tableContentRight) {
|
|
// selectMask is part of the viewport, newScrollLeft = columnWidth - visibleWidth
|
|
const newScrollLeft = nextColumnWidth - (tableContentRight - rect.x - rect.width);
|
|
this.props.setRecordsScrollLeft(this.props.getScrollLeft() + newScrollLeft);
|
|
}
|
|
}
|
|
return ({ ...current, idx: current.idx + 1 });
|
|
};
|
|
|
|
isKeyboardNavigationEvent(e) {
|
|
return this.getKeyNavActionFromEvent(e) != null;
|
|
}
|
|
|
|
getKeyNavActionFromEvent = (e) => {
|
|
const { getVisibleIndex, onHitBottomBoundary, onHitTopBoundary } = this.props;
|
|
|
|
const { rowVisibleStartIdx, rowVisibleEndIdx } = getVisibleIndex();
|
|
const isCellAtBottomBoundary = cell => cell.rowIdx >= rowVisibleEndIdx - 1;
|
|
const isCellAtTopBoundary = cell => cell.rowIdx !== 0 && cell.rowIdx <= rowVisibleStartIdx;
|
|
const keyNavActions = {
|
|
ArrowDown: {
|
|
getNext: (current) => {
|
|
return this.handleVerticalArrowAction(current, 'ArrowDown');
|
|
},
|
|
isCellAtBoundary: isCellAtBottomBoundary,
|
|
onHitBoundary: onHitBottomBoundary
|
|
},
|
|
ArrowUp: {
|
|
getNext: (current) => {
|
|
return this.handleVerticalArrowAction(current, 'ArrowUp');
|
|
},
|
|
isCellAtBoundary: isCellAtTopBoundary,
|
|
onHitBoundary: onHitTopBoundary
|
|
},
|
|
ArrowRight: {
|
|
getNext: (current) => {
|
|
return this.handleRightArrowAction(current);
|
|
},
|
|
isCellAtBoundary: () => {
|
|
return false;
|
|
}
|
|
},
|
|
ArrowLeft: {
|
|
getNext: (current) => {
|
|
return this.handleLeftArrowAction(current);
|
|
},
|
|
isCellAtBoundary: () => {
|
|
return false;
|
|
}
|
|
}
|
|
};
|
|
if (e.keyCode === KeyCodes.Tab) {
|
|
return e.shiftKey === true ? keyNavActions.ArrowLeft : keyNavActions.ArrowRight;
|
|
}
|
|
return keyNavActions[e.key];
|
|
};
|
|
|
|
changeCellFromEvent = (e) => {
|
|
e.preventDefault();
|
|
if (e.keyCode === KeyCodes.ChineseInputMethod && this.state.isEditorEnabled) {
|
|
return;
|
|
}
|
|
if (this.throttle) return;
|
|
const currentPosition = this.state.selectedPosition;
|
|
const keyNavAction = this.getKeyNavActionFromEvent(e);
|
|
const next = keyNavAction.getNext(currentPosition);
|
|
if (!next) return;
|
|
this.checkIsAtGridBoundary(keyNavAction, next);
|
|
this.props.onCellClick(next);
|
|
this.onSelectCell({ ...next });
|
|
this.throttle = true;
|
|
setTimeout(() => {
|
|
this.throttle = false;
|
|
}, 30);
|
|
};
|
|
|
|
checkIsAtGridBoundary(keyNavAction, next) {
|
|
const { isCellAtBoundary, onHitBoundary } = keyNavAction;
|
|
if (isCellAtBoundary(next)) {
|
|
onHitBoundary(next);
|
|
}
|
|
}
|
|
|
|
onFocus = () => {
|
|
|
|
};
|
|
|
|
onScroll = (e) => {
|
|
e.stopPropagation();
|
|
};
|
|
|
|
setSelectionMaskRef = (ref) => {
|
|
this.selectionMask = ref;
|
|
};
|
|
|
|
setSelectionRangeMaskRef = (ref) => {
|
|
this.selectedRangeMask = ref;
|
|
};
|
|
|
|
setContainerRef = (ref) => {
|
|
this.container = ref;
|
|
};
|
|
|
|
isCellMaskActive = () => {
|
|
const activeElement = document.activeElement;
|
|
return (activeElement &&
|
|
(activeElement.getAttribute('data-test') === 'cell-mask' ||
|
|
activeElement.getAttribute('data-test') === 'active-editor')
|
|
);
|
|
};
|
|
|
|
handleDragCopy = (draggedRange) => {
|
|
const { columns, groupMetrics } = this.props;
|
|
// compute the new records
|
|
const newRecords = this.props.getUpdateDraggedRecords(draggedRange, columns, groupMetrics);
|
|
if (this.props.modifyRecords) {
|
|
this.props.modifyRecords({ ...newRecords, isCopyPaste: true });
|
|
}
|
|
};
|
|
|
|
handleDragStart = (e) => {
|
|
const { selectedRange: { topLeft, bottomRight, startCell, cursorCell } } = this.state;
|
|
// To prevent dragging down/up when reordering rows. (TODO: is this required)
|
|
const isViewportDragging = e && e.target && e.target.className;
|
|
if (topLeft.idx > -1 && isViewportDragging) {
|
|
try {
|
|
e.dataTransfer.setData('text/plain', '');
|
|
} catch (ex) {
|
|
// IE only supports 'text' and 'URL' for the 'type' argument
|
|
e.dataTransfer.setData('text', '');
|
|
}
|
|
this.setState({
|
|
draggedRange: { topLeft, bottomRight, startCell, cursorCell }
|
|
});
|
|
}
|
|
};
|
|
|
|
handleDragEnter = ({ overRecordIdx, overGroupRecordIndex }) => {
|
|
if (this.state.draggedRange != null) {
|
|
this.setState(({ draggedRange }) => ({
|
|
draggedRange: { ...draggedRange, overRecordIdx, overGroupRecordIndex }
|
|
}));
|
|
}
|
|
};
|
|
|
|
handleDragEnd = () => {
|
|
const { draggedRange, selectedRange } = this.state;
|
|
let newSelectedRange = deepCopy(selectedRange);
|
|
if (draggedRange !== null) {
|
|
const { overRecordIdx, overGroupRecordIndex, bottomRight } = draggedRange;
|
|
if (overRecordIdx !== null && bottomRight.rowIdx < overRecordIdx) {
|
|
this.handleDragCopy(draggedRange);
|
|
newSelectedRange.bottomRight.rowIdx = overRecordIdx;
|
|
newSelectedRange.cursorCell.rowIdx = overRecordIdx;
|
|
newSelectedRange.bottomRight.groupRecordIndex = overGroupRecordIndex;
|
|
newSelectedRange.cursorCell.groupRecordIndex = overGroupRecordIndex;
|
|
}
|
|
this.setState({ draggedRange: null, selectedRange: newSelectedRange });
|
|
}
|
|
};
|
|
|
|
renderSingleCellSelectView = () => {
|
|
const { isEditorEnabled, selectedPosition } = this.state;
|
|
const isDragEnabled = this.checkIsSelectedCellEditable();
|
|
const showDragHandle = (isDragEnabled && this.props.canModifyRecords);
|
|
if (isEditorEnabled) {
|
|
return null;
|
|
}
|
|
if (!this.isGridSelected()) return null;
|
|
|
|
const props = {
|
|
innerRef: this.setSelectionMaskRef,
|
|
selectedPosition,
|
|
getSelectedDimensions: this.getSelectedDimensions,
|
|
};
|
|
return (
|
|
<SelectionMask {...props}>
|
|
{showDragHandle ? <DragHandler onDragStart={this.handleDragStart} onDragEnd={this.handleDragEnd} /> : null}
|
|
</SelectionMask>
|
|
);
|
|
};
|
|
|
|
renderCellRangeSelectView = () => {
|
|
const { selectedRange } = this.state;
|
|
const { columns, rowHeight } = this.props;
|
|
const isDragEnabled = this.checkIsSelectedCellEditable();
|
|
const showDragHandle = (isDragEnabled && this.props.canModifyRecords);
|
|
return [
|
|
<SelectionRangeMask
|
|
key="range-mask"
|
|
innerRef={this.setSelectionRangeMaskRef}
|
|
selectedRange={selectedRange}
|
|
columns={columns}
|
|
rowHeight={rowHeight}
|
|
getSelectedRangeDimensions={this.getSelectedRangeDimensions}
|
|
>
|
|
{showDragHandle ? <DragHandler onDragStart={this.handleDragStart} onDragEnd={this.handleDragEnd} /> : null}
|
|
</SelectionRangeMask>,
|
|
<SelectionMask
|
|
key="selection-mask"
|
|
innerRef={this.setSelectionMaskRef}
|
|
selectedPosition={selectedRange.startCell}
|
|
getSelectedDimensions={this.getSelectedDimensions}
|
|
/>
|
|
];
|
|
};
|
|
|
|
render() {
|
|
const { selectedRange, isEditorEnabled, draggedRange, selectedPosition, firstEditorKeyDown, openEditorMode, editorPosition } = this.state;
|
|
const { columns, isGroupView, recordGetterByIndex, scrollTop, getScrollLeft, editorPortalTarget, contextMenu } = this.props;
|
|
const isSelectedSingleCell = selectedRangeIsSingleCell(selectedRange);
|
|
return (
|
|
<div
|
|
className='interaction-mask'
|
|
ref={this.setContainerRef}
|
|
onKeyDown={this.onKeyDown}
|
|
onFocus={this.onFocus}
|
|
onScroll={this.onScroll}
|
|
>
|
|
{draggedRange && (
|
|
<DragMask
|
|
draggedRange={draggedRange}
|
|
getSelectedDimensions={this.getSelectedDimensions}
|
|
getSelectedRangeDimensions={this.getSelectedRangeDimensions}
|
|
/>
|
|
)}
|
|
{isSelectedSingleCell ? this.renderSingleCellSelectView() : this.renderCellRangeSelectView()}
|
|
{isEditorEnabled && (
|
|
<EditorPortal target={editorPortalTarget}>
|
|
<EditorContainer
|
|
columns={columns}
|
|
scrollTop={scrollTop}
|
|
firstEditorKeyDown={firstEditorKeyDown}
|
|
openEditorMode={openEditorMode}
|
|
portalTarget={editorPortalTarget}
|
|
scrollLeft={getScrollLeft()}
|
|
record={getSelectedRow({ selectedPosition, isGroupView, recordGetterByIndex })}
|
|
column={getSelectedColumn({ selectedPosition, columns })}
|
|
value={getSelectedCellValue({ selectedPosition, columns, isGroupView, recordGetterByIndex })}
|
|
editorPosition={editorPosition}
|
|
onCommit={this.onCommit}
|
|
onCommitCancel={this.onCommitCancel}
|
|
modifyColumnData={this.props.modifyColumnData}
|
|
{...{
|
|
...this.getSelectedDimensions(selectedPosition),
|
|
...this.state.editorPosition
|
|
}}
|
|
/>
|
|
</EditorPortal>
|
|
)}
|
|
|
|
{isValidElement(contextMenu) && cloneElement(contextMenu, {
|
|
selectedPosition: isSelectedSingleCell ? selectedPosition : null,
|
|
selectedRange: !isSelectedSingleCell ? selectedRange : null,
|
|
onClearSelected: this.handleSelectCellsDelete,
|
|
onCopySelected: this.onCopySelected,
|
|
getTableContentRect: this.props.getTableContentRect,
|
|
getTableCanvasContainerRect: this.props.getTableCanvasContainerRect,
|
|
})}
|
|
</div>
|
|
);
|
|
}
|
|
}
|
|
|
|
InteractionMasks.propTypes = {
|
|
contextmenu: PropTypes.element,
|
|
tableId: PropTypes.string,
|
|
columns: PropTypes.array,
|
|
canAddRow: PropTypes.bool,
|
|
isGroupView: PropTypes.bool,
|
|
recordsCount: PropTypes.number,
|
|
recordMetrics: PropTypes.object,
|
|
groups: PropTypes.array,
|
|
groupMetrics: PropTypes.object,
|
|
rowHeight: PropTypes.number,
|
|
groupOffsetLeft: PropTypes.number,
|
|
frozenColumnsWidth: PropTypes.number,
|
|
enableCellSelect: PropTypes.bool,
|
|
canModifyRecords: PropTypes.bool,
|
|
getRowTop: PropTypes.func,
|
|
scrollTop: PropTypes.number,
|
|
getScrollLeft: PropTypes.func,
|
|
getTableContentRect: PropTypes.func,
|
|
getMobileFloatIconStyle: PropTypes.func,
|
|
onToggleMobileMoreOperations: PropTypes.func,
|
|
onToggleInsertRecordDialog: PropTypes.func,
|
|
onCellRangeSelectionStarted: PropTypes.func,
|
|
onCellRangeSelectionUpdated: PropTypes.func,
|
|
onCellRangeSelectionCompleted: PropTypes.func,
|
|
selectNone: PropTypes.func,
|
|
checkCanModifyRecord: PropTypes.func,
|
|
editorPortalTarget: PropTypes.instanceOf(Element).isRequired,
|
|
modifyRecord: PropTypes.func,
|
|
modifyColumnData: PropTypes.func,
|
|
recordGetterByIndex: PropTypes.func,
|
|
recordGetterById: PropTypes.func,
|
|
modifyRecords: PropTypes.func,
|
|
deleteRecordsLinks: PropTypes.func,
|
|
paste: PropTypes.func,
|
|
editMobileCell: PropTypes.func,
|
|
getVisibleIndex: PropTypes.func,
|
|
onHitBottomBoundary: PropTypes.func,
|
|
onHitTopBoundary: PropTypes.func,
|
|
onCellClick: PropTypes.func,
|
|
scrollToColumn: PropTypes.func,
|
|
setRecordsScrollLeft: PropTypes.func,
|
|
getGroupCanvasScrollTop: PropTypes.func,
|
|
setGroupCanvasScrollTop: PropTypes.func,
|
|
appPage: PropTypes.object,
|
|
onFillingDragRows: PropTypes.func,
|
|
onCellsDragged: PropTypes.func,
|
|
getUpdateDraggedRecords: PropTypes.func,
|
|
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
|
|
onCommit: PropTypes.func,
|
|
getTableCanvasContainerRect: PropTypes.func,
|
|
handleSpaceKeyDown: PropTypes.func,
|
|
};
|
|
|
|
export default InteractionMasks;
|