1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-20 10:58:33 +00:00

metadata copy and paste cell (#6472)

This commit is contained in:
Michael An
2024-08-02 16:15:38 +08:00
committed by GitHub
parent 7934cfe2ed
commit 6ce8336e9a
13 changed files with 692 additions and 45 deletions

View File

@@ -273,10 +273,14 @@ const formatTextToNumber = (value) => {
return isNaN(newData) ? null : newData; return isNaN(newData) ? null : newData;
}; };
const isNumber = (number) => (number || number === 0) && Object.prototype.toString.call(number) === '[object Number]';
export { export {
getPrecisionNumber, getPrecisionNumber,
getNumberDisplayString, getNumberDisplayString,
replaceNumberNotAllowInput, replaceNumberNotAllowInput,
formatStringToNumber, formatStringToNumber,
formatTextToNumber, formatTextToNumber,
getFloatNumber,
isNumber,
}; };

View File

@@ -97,11 +97,10 @@ const createOption = (options, optionName, optionColor = '') => {
const generatorCellOption = (options, optionName) => { const generatorCellOption = (options, optionName) => {
const existOption = options.find((option) => option.name === optionName); const existOption = options.find((option) => option.name === optionName);
if (existOption) { if (existOption) {
return { selectedOptionId: existOption.id }; return existOption;
} }
const newOption = createOption(options, optionName) || {}; const newOption = createOption(options, optionName) || {};
return { cellOption: newOption, selectedOptionId: newOption.id }; return newOption;
}; };
/** /**

View File

@@ -8,10 +8,12 @@ import { useRecordDetails } from '../../../hooks';
import './index.css'; import './index.css';
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, ...params }) => { const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, ...params }) => {
const gridUtils = useMemo(() => { const gridUtils = useMemo(() => {
return new GridUtils(metadata, { modifyRecord, modifyRecords }); return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById });
}, [metadata, modifyRecord, modifyRecords]); }, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById]);
const { openRecordDetails } = useRecordDetails(); const { openRecordDetails } = useRecordDetails();
const groupbysCount = useMemo(() => { const groupbysCount = useMemo(() => {
@@ -28,6 +30,10 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
return columns.filter(column => !hidden_columns.includes(column.key)); return columns.filter(column => !hidden_columns.includes(column.key));
}, [metadata]); }, [metadata]);
const getCopiedRecordsAndColumnsFromRange = useCallback(({ type, copied, isGroupView }) => {
return gridUtils.getCopiedContent({ type, copied, isGroupView, columns });
}, [gridUtils, columns]);
const updateRecord = useCallback(({ rowId, updates, originalUpdates, oldRowData, originalOldRowData }) => { const updateRecord = useCallback(({ rowId, updates, originalUpdates, oldRowData, originalOldRowData }) => {
modifyRecord && modifyRecord(rowId, updates, oldRowData, originalUpdates, originalOldRowData); modifyRecord && modifyRecord(rowId, updates, oldRowData, originalUpdates, originalOldRowData);
}, [modifyRecord]); }, [modifyRecord]);
@@ -36,6 +42,10 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
modifyRecords && modifyRecords(recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste); modifyRecords && modifyRecords(recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
}, [modifyRecords]); }, [modifyRecords]);
const paste = useCallback(({ type, copied, multiplePaste, pasteRange, isGroupView }) => {
gridUtils.paste({ type, copied, multiplePaste, pasteRange, isGroupView, columns });
}, [gridUtils, columns]);
return ( return (
<div className={classnames('table-main-container container-fluid p-0', { [`group-level-${groupbysCount + 1}`]: groupbysCount > 0 })}> <div className={classnames('table-main-container container-fluid p-0', { [`group-level-${groupbysCount + 1}`]: groupbysCount > 0 })}>
<Records <Records
@@ -49,10 +59,14 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
gridUtils={gridUtils} gridUtils={gridUtils}
scrollToLoadMore={loadMore} scrollToLoadMore={loadMore}
loadAll={loadAll} loadAll={loadAll}
paste={paste}
groupOffsetLeft={groupOffset} groupOffsetLeft={groupOffset}
modifyRecord={updateRecord} modifyRecord={updateRecord}
updateRecords={updateRecords} updateRecords={updateRecords}
onRowExpand={openRecordDetails} onRowExpand={openRecordDetails}
getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange}
recordGetterById={recordGetterById}
recordGetterByIndex={recordGetterByIndex}
{...params} {...params}
/> />
</div> </div>

View File

@@ -720,6 +720,7 @@ Records.propTypes = {
renameColumn: PropTypes.func, renameColumn: PropTypes.func,
deleteColumn: PropTypes.func, deleteColumn: PropTypes.func,
modifyColumnData: PropTypes.func, modifyColumnData: PropTypes.func,
getCopiedRecordsAndColumnsFromRange: PropTypes.func,
}; };
export default Records; export default Records;

View File

@@ -1,13 +1,11 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import CellMask from './cell-mask'; import CellMask from './cell-mask';
function DragMask({ draggedRange, getSelectedRangeDimensions, getSelectedDimensions }) { function DragMask({ draggedRange, getSelectedRangeDimensions, getSelectedDimensions }) {
const { overRecordIdx, bottomRight } = draggedRange; const { overRecordIdx, bottomRight } = draggedRange;
const { idx: endColumnIdx, rowIdx: endRowIdx, groupRecordIndex: endGroupRowIndex } = bottomRight; const { idx: endColumnIdx, rowIdx: endRowIdx, groupRecordIndex: endGroupRowIndex } = bottomRight;
if (overRecordIdx !== null && endRowIdx < overRecordIdx) { if (overRecordIdx !== null && endRowIdx < overRecordIdx) {
const className = 'react-grid-cell-dragged-over-down';
let dimensions = getSelectedRangeDimensions(draggedRange); let dimensions = getSelectedRangeDimensions(draggedRange);
for (let currentRowIdx = endRowIdx + 1; currentRowIdx <= overRecordIdx; currentRowIdx++) { for (let currentRowIdx = endRowIdx + 1; currentRowIdx <= overRecordIdx; currentRowIdx++) {
const { height } = getSelectedDimensions({ idx: endColumnIdx, rowIdx: currentRowIdx, groupRecordIndex: endGroupRowIndex }); const { height } = getSelectedDimensions({ idx: endColumnIdx, rowIdx: currentRowIdx, groupRecordIndex: endGroupRowIndex });
@@ -16,7 +14,7 @@ function DragMask({ draggedRange, getSelectedRangeDimensions, getSelectedDimensi
return ( return (
<CellMask <CellMask
{...dimensions} {...dimensions}
className={className} className='react-grid-cell-dragged-over-down'
/> />
); );
} }

View File

@@ -72,17 +72,18 @@ class InteractionMasks extends React.Component {
} }
componentDidMount() { componentDidMount() {
this.unsubscribeSelectColumn = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_COLUMN, this.onColumnSelect); const eventBus = window.sfMetadataContext.eventBus;
this.unsubscribeDragEnter = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.DRAG_ENTER, this.handleDragEnter); this.unsubscribeSelectColumn = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_COLUMN, this.onColumnSelect);
this.unsubscribeSelectCell = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, this.onSelectCell); this.unsubscribeDragEnter = eventBus.subscribe(EVENT_BUS_TYPE.DRAG_ENTER, this.handleDragEnter);
this.unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone); this.unsubscribeSelectCell = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, this.onSelectCell);
this.unsubscribeSelectStart = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_START, this.onSelectCellRangeStarted); this.unsubscribeSelectNone = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone);
this.unsubscribeSelectUpdate = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_UPDATE, this.onSelectCellRangeUpdated); this.unsubscribeSelectStart = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_START, this.onSelectCellRangeStarted);
this.unsubscribeSelectEnd = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_END, this.onSelectCellRangeEnded); this.unsubscribeSelectUpdate = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_UPDATE, this.onSelectCellRangeUpdated);
this.unsubscribeOpenEditorEvent = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.OPEN_EDITOR, this.onOpenEditorEvent); this.unsubscribeSelectEnd = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_END, this.onSelectCellRangeEnded);
this.unsubscribeCloseEditorEvent = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.CLOSE_EDITOR, this.onCloseEditorEvent); this.unsubscribeOpenEditorEvent = eventBus.subscribe(EVENT_BUS_TYPE.OPEN_EDITOR, this.onOpenEditorEvent);
this.unsubscribeCopy = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.COPY_CELLS, this.onCopy); this.unsubscribeCloseEditorEvent = eventBus.subscribe(EVENT_BUS_TYPE.CLOSE_EDITOR, this.onCloseEditorEvent);
this.unsubscribePaste = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.PASTE_CELLS, this.onPaste); this.unsubscribeCopy = eventBus.subscribe(EVENT_BUS_TYPE.COPY_CELLS, this.onCopy);
this.unsubscribePaste = eventBus.subscribe(EVENT_BUS_TYPE.PASTE_CELLS, this.onPaste);
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
@@ -111,7 +112,7 @@ class InteractionMasks extends React.Component {
} }
onColumnSelect = (column) => { onColumnSelect = (column) => {
let { columns, isGroupView } = this.props; let { columns, isGroupView, recordsCount } = this.props;
if (isGroupView) return; if (isGroupView) return;
let selectColumnIndex = 0; let selectColumnIndex = 0;
for (let i = 0; i < columns.length; i++) { for (let i = 0; i < columns.length; i++) {
@@ -120,13 +121,12 @@ class InteractionMasks extends React.Component {
break; break;
} }
} }
const rowsCount = this.props.recordsCount;
this.setState({ this.setState({
selectedPosition: { ...this.state.selectedPosition, idx: selectColumnIndex, rowIdx: 0 }, selectedPosition: { ...this.state.selectedPosition, idx: selectColumnIndex, rowIdx: 0 },
selectedRange: { selectedRange: {
startCell: { idx: selectColumnIndex, rowIdx: 0 }, startCell: { idx: selectColumnIndex, rowIdx: 0 },
topLeft: { idx: selectColumnIndex, rowIdx: 0 }, topLeft: { idx: selectColumnIndex, rowIdx: 0 },
bottomRight: { idx: selectColumnIndex, rowIdx: rowsCount - 1 }, bottomRight: { idx: selectColumnIndex, rowIdx: recordsCount - 1 },
isDragging: false, isDragging: false,
} }
}); });

View File

@@ -3,6 +3,7 @@ import { UserService, LocalStorage, PRIVATE_COLUMN_KEYS, EDITABLE_DATA_PRIVATE_C
EDITABLE_PRIVATE_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS } from './_basic'; EDITABLE_PRIVATE_COLUMN_KEYS, PREDEFINED_COLUMN_KEYS } from './_basic';
import EventBus from '../../components/common/event-bus'; import EventBus from '../../components/common/event-bus';
import { username } from '../../utils/constants'; import { username } from '../../utils/constants';
import User from './model/user';
class Context { class Context {
@@ -14,6 +15,7 @@ class Context {
this.eventBus = null; this.eventBus = null;
this.hasInit = false; this.hasInit = false;
this.permission = 'r'; this.permission = 'r';
this.collaboratorsCache = {};
} }
async init({ otherSettings }) { async init({ otherSettings }) {
@@ -128,9 +130,22 @@ class Context {
return true; return true;
}; };
getCollaboratorsFromCache = () => { getCollaboratorFromCache(email) {
// return this.collaboratorsCache[email];
}; }
getCollaboratorsFromCache() {
const collaboratorsCache = this.collaboratorsCache;
return Object.values(collaboratorsCache).filter(item => item.email !== 'anonymous');
}
updateCollaboratorsCache(email, collaborator) {
if (collaborator instanceof User) {
this.collaboratorsCache[email] = collaborator;
return;
}
this.collaboratorsCache[email] = new User(collaborator);
}
restoreRows = () => { restoreRows = () => {
// todo // todo

View File

@@ -0,0 +1,98 @@
import { DateUtils, CellType, DEFAULT_DATE_FORMAT, getCollaboratorsName, getOptionName, getDateDisplayString, getLongtextDisplayString, getNumberDisplayString } from '../../_basic';
const getCellValueDisplayString = (row, type, key, { data, collaborators = [] } = {}) => {
if (!row) return '';
const cellValue = row[key];
switch (type) {
case CellType.LONG_TEXT: {
return getLongtextDisplayString(cellValue);
}
case CellType.NUMBER: {
return getNumberDisplayString(cellValue, data);
}
case CellType.SINGLE_SELECT: {
if (!data) return '';
const { options } = data;
return getOptionName(options, cellValue);
}
case CellType.DATE: {
const { format = DEFAULT_DATE_FORMAT } = data || {};
return getDateDisplayString(cellValue, format);
}
case CellType.CTIME:
case CellType.MTIME: {
return DateUtils.format(cellValue, 'YYYY-MM-DD HH:MM:SS');
}
case CellType.COLLABORATOR: {
return getCollaboratorsName(collaborators, cellValue);
}
case CellType.CREATOR:
case CellType.LAST_MODIFIER: {
return cellValue === 'anonymous' ? cellValue : getCollaboratorsName(collaborators, [cellValue]);
}
default: {
if (cellValue || typeof cellValue === 'boolean') {
return String(cellValue);
}
return '';
}
}
};
const getCellValueStringResult = (row, column, { collaborators = [] } = {}) => {
if (!row || !column) return '';
const { key, type, data } = column;
let cellValue = row[key];
switch (type) {
case CellType.TEXT:
case CellType.EMAIL:
case CellType.URL:
case CellType.AUTO_NUMBER: {
return cellValue || '';
}
case CellType.RATE: { // number
return cellValue ? String(cellValue) : '';
}
case CellType.CHECKBOX: {
if (typeof cellValue === 'boolean') {
return String(cellValue);
}
return cellValue === 'true' ? 'true' : 'false';
}
case CellType.LONG_TEXT: {
return getLongtextDisplayString(cellValue);
}
case CellType.NUMBER: {
return getNumberDisplayString(cellValue, data);
}
case CellType.SINGLE_SELECT: {
if (!data) return '';
return getOptionName(data.options, cellValue);
}
case CellType.DATE: {
const { format = DEFAULT_DATE_FORMAT } = data || {};
// patch: compatible with previous format
const normalizedFormat = format === 'D/M/YYYY' ? format.replace(/D\/M\/YYYY/, 'DD/MM/YYYY') : format;
return getDateDisplayString(cellValue, normalizedFormat);
}
case CellType.CTIME:
case CellType.MTIME: {
return DateUtils.format(cellValue, 'YYYY-MM-DD HH:MM:SS');
}
case CellType.COLLABORATOR: {
return getCollaboratorsName(collaborators, cellValue);
}
case CellType.CREATOR:
case CellType.LAST_MODIFIER: {
return cellValue === 'anonymous' ? cellValue : getCollaboratorsName(collaborators, [cellValue]);
}
default: {
return cellValue ? String(cellValue) : '';
}
}
};
export {
getCellValueDisplayString,
getCellValueStringResult,
};

View File

@@ -0,0 +1,9 @@
export {
getPrecisionNumber,
getNumberDisplayString,
replaceNumberNotAllowInput,
} from './number';
export {
getCellValueDisplayString,
getCellValueStringResult,
} from './cell-value';

View File

@@ -0,0 +1,189 @@
import { DEFAULT_NUMBER_FORMAT } from '../../_basic';
import { DISPLAY_INTERNAL_ERRORS } from '../../_basic/constants';
import { round } from '../../_basic/utils/number';
import { NPminus } from '../../_basic/utils/helper/number-precision';
const separatorMap = {
comma: ',',
dot: '.',
no: '',
space: ' ',
};
const _getDecimalDigits = (number) => {
if (Number.isInteger(number)) {
return 0;
}
const valueArr = String(number).split('.');
const digitsLength = valueArr[1] ? valueArr[1].length : 8;
return digitsLength > 8 ? 8 : digitsLength;
};
const getPrecisionNumber = (number, formats) => {
const { precision = 2, enable_precision = false } = formats || {};
const type = Object.prototype.toString.call(number);
if (type !== '[object Number]') {
if (type === '[object String]' && DISPLAY_INTERNAL_ERRORS.includes(number)) {
return number;
}
return null;
}
const decimalDigits = enable_precision ? precision : _getDecimalDigits(number);
return number.toFixed(decimalDigits);
};
const removeZerosFromEnd = (sNumber) => {
if (typeof sNumber !== 'string') return '';
if (sNumber.endsWith('0')) {
return sNumber.replace(/(?:\.0*|(\.\d+?)0+)$/, '$1');
}
return sNumber;
};
const getDecimalDigitsFromNumber = (number) => {
if (Number.isInteger(number)) {
return 0;
}
const decimalPart = String(number).split('.')[1];
const digitsLength = decimalPart ? decimalPart.length : 8;
return digitsLength > 8 ? 8 : digitsLength;
};
const toThousands = (number, { formats, isCurrency = true }) => {
const {
decimal = 'dot', thousands = 'no', precision = 2, enable_precision = false,
} = formats || {};
// handle numbers in scientific notation
if (String(number).includes('e')) {
if (number < 1 && number > -1) {
// 1.convert to non-scientific number
let numericString = number.toFixed(enable_precision ? precision : 8);
// 2.remove 0 from end of the number which not set precision. e.g. 0.100000
if (!enable_precision) {
numericString = removeZerosFromEnd(numericString);
}
// 3.remove minus from number which equal to 0. e.g. '-0.00'
if (parseFloat(numericString) === 0) {
return numericString.startsWith('-') ? numericString.substring(1) : numericString;
}
return numericString;
}
return String(number);
}
const decimalString = separatorMap[decimal];
const thousandsString = separatorMap[thousands];
const decimalDigits = enable_precision ? precision : getDecimalDigitsFromNumber(number);
const floatNumber = parseFloat(round(number, decimalDigits).toFixed(decimalDigits));
const isMinus = floatNumber < 0;
let integer = Math.trunc(floatNumber);
// format decimal part
let decimalPart = String(Math.abs(NPminus(floatNumber, integer)).toFixed(decimalDigits)).slice(1);
if (!enable_precision) {
decimalPart = removeZerosFromEnd(decimalPart);
}
if (isCurrency) {
if (!enable_precision) {
decimalPart = decimalPart.length === 2
? decimalPart = decimalPart.padEnd(3, '0')
: (decimalPart.substring(0, 3) || '.').padEnd(3, '0');
}
}
decimalPart = decimalPart.replace(/./, decimalString);
// format integer part
const integerNumbers = [];
let counter = 0;
integer = Math.abs(integer).toString();
for (let i = integer.length - 1; i > -1; i--) {
counter += 1;
integerNumbers.unshift(integer[i]);
if (!(counter % 3) && i !== 0) {
integerNumbers.unshift(thousandsString);
}
}
return `${isMinus ? '-' : ''}${integerNumbers.join('')}${decimalPart}`;
};
const getNumberDisplayString = (number, formats) => {
const type = Object.prototype.toString.call(number);
if (type !== '[object Number]') {
// return formula internal errors directly.
if (type === '[object String]' && number.startsWith('#')) {
return number;
}
return '';
}
if (isNaN(number) || number === Infinity || number === -Infinity) return String(number);
// formats: old version maybe 'null'
const { format = DEFAULT_NUMBER_FORMAT } = formats || {};
switch (format) {
case 'number': {
return toThousands(number, { formats, isCurrency: false });
}
case 'percent': {
return `${toThousands(Number.parseFloat((number * 100).toFixed(8)), { formats, isCurrency: false })}%`;
}
case 'yuan': {
return `${toThousands(number, { formats })}`;
}
case 'dollar': {
return `$${toThousands(number, { formats })}`;
}
case 'euro': {
return `${toThousands(number, { formats })}`;
}
case 'custom_currency': {
if (formats.currency_symbol_position === 'after') {
return `${toThousands(number, { formats })}${formats.currency_symbol || ''}`;
}
return `${formats.currency_symbol || ''}${toThousands(number, { formats })}`;
}
default: {
return String(number);
}
}
};
const replaceNumberNotAllowInput = (sNum, format, currencySymbol) => {
if (!sNum) {
return '';
}
const fixedSNum = sNum.replace(/。/g, '.');
switch (format) {
case 'percent': {
return fixedSNum.replace(/[^.-\d,%]/g, '');
}
case 'yuan': {
return fixedSNum.replace(/[^.-\d¥¥,]/g, '');
}
case 'dollar': {
return fixedSNum.replace(/[^.-\d$,]/g, '');
}
case 'euro': {
return fixedSNum.replace(/[^.-\d€,]/g, '');
}
case 'custom_currency': {
const reg = new RegExp('[^.-\\d' + currencySymbol + ',]', 'g');
return fixedSNum.replace(reg, '');
}
default: {
// default as number format
return fixedSNum.replace(/[^.-\d,]/g, '');
}
}
};
export {
getPrecisionNumber,
getNumberDisplayString,
replaceNumberNotAllowInput,
};

View File

@@ -0,0 +1,301 @@
import { CellType, DEFAULT_DATE_FORMAT, generatorCellOption, getCollaboratorsName, getOptionName, getDateDisplayString } from '../_basic';
import { formatTextToDate } from './date';
import { getFloatNumber, getNumberDisplayString, formatStringToNumber, isNumber } from '../_basic/utils/cell/column/number';
const SUPPORT_PASTE_FROM_COLUMN = {
[CellType.NUMBER]: [CellType.TEXT, CellType.NUMBER],
};
const reg_chinese_date_format = /(\d{4})年(\d{1,2})月(\d{1,2})日$/;
export function getSelectColumnOptions(column) {
if (!column || !column.data || !Array.isArray(column.data.options)) {
return [];
}
return column.data.options;
}
function convertCellValue(cellValue, oldCellValue, targetColumn, fromColumn) {
const { type: fromColumnType, data: fromColumnData } = fromColumn;
const { type: targetColumnType, data: targetColumnData } = targetColumn;
switch (targetColumnType) {
case CellType.CHECKBOX: {
return convert2Checkbox(cellValue, oldCellValue, fromColumnType);
}
case CellType.NUMBER: {
return convert2Number(cellValue, oldCellValue, fromColumnType, targetColumnData);
}
case CellType.DATE: {
return convert2Date(cellValue, oldCellValue, fromColumnType, fromColumnData, targetColumnData);
}
case CellType.SINGLE_SELECT: {
return convert2SingleSelect(cellValue, oldCellValue, fromColumn, targetColumn);
}
case CellType.LONG_TEXT: {
return convert2LongText(cellValue, oldCellValue, fromColumn);
}
case CellType.TEXT: {
return convert2Text(cellValue, oldCellValue, fromColumn);
}
case CellType.COLLABORATOR: {
return convert2Collaborator(cellValue, oldCellValue, fromColumnType);
}
default: {
return oldCellValue;
}
}
}
function convert2Checkbox(cellValue, oldCellValue, fromColumnType) {
switch (fromColumnType) {
case CellType.CHECKBOX: {
if (typeof cellValue === 'boolean') {
return cellValue;
}
return null;
}
case CellType.TEXT: {
if (cellValue && typeof cellValue === 'string' && cellValue.toLocaleLowerCase() === 'true') {
return true;
}
return false;
}
case CellType.NUMBER: {
return cellValue > 0;
}
default: {
return oldCellValue;
}
}
}
function convert2Number(cellValue, oldCellValue, fromColumnType, targetColumnData) {
if (!SUPPORT_PASTE_FROM_COLUMN[CellType.NUMBER].includes(fromColumnType)) {
return oldCellValue;
}
if (cellValue === 0) {
return cellValue;
}
if (!cellValue) {
return null;
}
switch (fromColumnType) {
case CellType.NUMBER:
case CellType.RATE:
case CellType.DURATION: {
return cellValue;
}
case CellType.TEXT: {
if (cellValue.includes('%')) {
return getFloatNumber(cellValue, 'percent');
}
break;
}
default: {
break;
}
}
const stringCellVal = typeof cellValue === 'string' ? cellValue : cellValue.toString();
return formatStringToNumber(stringCellVal, targetColumnData);
}
function convert2Date(cellValue, oldCellValue, fromColumnType, fromColumnData, targetColumnData) {
const currentFormat = targetColumnData?.format || DEFAULT_DATE_FORMAT;
const fromFormat = fromColumnData?.format || DEFAULT_DATE_FORMAT;
switch (fromColumnType) {
case CellType.DATE: {
const fromTimeIdx = fromFormat.indexOf('HH:mm');
const currentTimeIdx = currentFormat.indexOf('HH:mm');
if ((fromTimeIdx > -1 && currentTimeIdx > -1) || fromTimeIdx === currentTimeIdx) {
return cellValue;
}
return formatTextToDate(cellValue, currentFormat);
}
case CellType.CTIME:
case CellType.MTIME: {
return formatTextToDate(cellValue, currentFormat);
}
case CellType.TEXT: {
if (cellValue.indexOf('年') > -1) {
const zhCNDate = cellValue.replace(/\s*/g, '');
if (!reg_chinese_date_format.test(zhCNDate)) {
return '';
}
return formatTextToDate(zhCNDate.replace(reg_chinese_date_format, '$1-$2-$3'), currentFormat);
}
return formatTextToDate(cellValue, currentFormat);
}
default: {
return oldCellValue;
}
}
}
function convert2SingleSelect(cellValue, oldCellValue, fromColumn, targetColumn) {
if (!cellValue) {
return oldCellValue;
}
const { type: fromColumnType } = fromColumn;
let fromOptionName;
switch (fromColumnType) {
case CellType.SINGLE_SELECT: {
const fromOptions = getSelectColumnOptions(fromColumn);
fromOptionName = getOptionName(fromOptions, cellValue) || '';
break;
}
case CellType.TEXT: {
fromOptionName = cellValue;
break;
}
default: {
break;
}
}
if (!fromOptionName) {
return oldCellValue;
}
const currentOptions = getSelectColumnOptions(targetColumn);
const newOption = generatorCellOption(currentOptions, fromOptionName);
return newOption.name;
}
const LONG_TEXT_LENGTH_LIMIT = 10 * 10000;
export const isLongTextValueExceedLimit = (value) => {
const { text } = value;
return text ? text.length >= LONG_TEXT_LENGTH_LIMIT : false;
};
export const getValidLongTextValue = (value) => {
const newValue = { ...value };
const { text, preview } = newValue;
newValue.text = text ? text.slice(0, LONG_TEXT_LENGTH_LIMIT) : '';
newValue.preview = preview ? preview.slice(0, LONG_TEXT_LENGTH_LIMIT) : '';
return newValue;
};
function convert2LongText(cellValue, oldCellValue, fromColumn) {
const { type: fromColumnType, data: fromColumnData } = fromColumn;
switch (fromColumnType) {
case CellType.LONG_TEXT: {
// cell value is a string value from dtable-db
const value = { text: cellValue };
if (isLongTextValueExceedLimit(value)) {
const newValue = getValidLongTextValue(value);
return newValue.text;
}
return cellValue || null;
}
case CellType.TEXT: {
return cellValue || null;
}
case CellType.NUMBER: {
return getNumberDisplayString(cellValue, fromColumnData) || null;
}
case CellType.DATE: {
return getDateDisplayString(cellValue, fromColumnData.format || DEFAULT_DATE_FORMAT) || null;
}
default: {
return oldCellValue;
}
}
}
function convert2Text(cellValue, oldCellValue, fromColumn) {
const { type: fromColumnType, data: fromColumnData } = fromColumn;
switch (fromColumnType) {
case CellType.TEXT: {
if (isNumber(cellValue)) {
return String(cellValue);
}
if (!cellValue) {
return null;
}
if (typeof cellValue === 'string') {
return cellValue.replace(/\n/g, '').trim();
}
return String(cellValue);
}
case CellType.URL:
case CellType.EMAIL:
case CellType.AUTO_NUMBER: {
return cellValue;
}
case CellType.NUMBER: {
return getNumberDisplayString(cellValue, fromColumnData);
}
case CellType.DATE: {
return getDateDisplayString(cellValue, fromColumnData.format || DEFAULT_DATE_FORMAT);
}
case CellType.SINGLE_SELECT: {
const options = getSelectColumnOptions(fromColumn);
return getOptionName(options, cellValue) || null;
}
case CellType.COLLABORATOR: {
const collaborators = window.sfMetadata.collaborators || window.sfMetadataContext.getCollaboratorsFromCache();
return getCollaboratorsName(collaborators, cellValue);
}
case CellType.CREATOR:
case CellType.LAST_MODIFIER: {
if (!cellValue) {
return null;
}
const collaborators = window.sfMetadata.collaborators || window.sfMetadataContext.getCollaboratorsFromCache();
return getCollaboratorsName(collaborators, [cellValue]);
}
default: {
return oldCellValue;
}
}
}
function convert2Collaborator(cellValue, oldCellValue, fromColumnType) {
switch (fromColumnType) {
case CellType.COLLABORATOR: {
if (!Array.isArray(cellValue) || cellValue.length === 0) {
return null;
}
const collaborators = window.sfMetadata.collaborators || window.sfMetadataContext.getCollaboratorsFromCache();
let validEmailMap = {};
collaborators.forEach(collaborator => validEmailMap[collaborator.email] = true);
return cellValue.filter(email => !!validEmailMap[email]);
}
case CellType.TEXT: {
if (!cellValue) {
return oldCellValue;
}
const userNames = cellValue.split(',');
if (userNames.length === 0) {
return oldCellValue;
}
const collaborators = window.sfMetadata.collaborators || window.sfMetadataContext.getCollaboratorsFromCache();
let nameCollaboratorMap = {};
collaborators.forEach(collaborator => nameCollaboratorMap[collaborator.name] = collaborator);
const emails = userNames.map(name => {
const collaborator = nameCollaboratorMap[name];
return collaborator ? collaborator.email : null;
}).filter(Boolean);
if (emails.length === 0) {
return oldCellValue;
}
return emails;
}
case CellType.CREATOR:
case CellType.LAST_MODIFIER: {
const collaborators = window.sfMetadata.collaborators || window.sfMetadataContext.getCollaboratorsFromCache();
let validEmailMap = {};
collaborators.forEach(collaborator => validEmailMap[collaborator.email] = true);
if (!cellValue || !validEmailMap[cellValue]) {
return null;
}
return [cellValue];
}
default: {
return oldCellValue;
}
}
}
export { convertCellValue };

View File

@@ -0,0 +1,19 @@
import { DateUtils } from '../_basic';
import { getFloatNumber } from '../_basic/utils/cell/column/number';
const formatTextToDate = (text, format = 'YYYY-MM-DD') => {
if (typeof text !== 'string' || !text.trim()) return null;
let isAllNumber = /^[0-9]+$/.test(text);
let dateObj = {};
if (isAllNumber) {
dateObj = new Date(getFloatNumber(text));
} else {
dateObj = DateUtils.parseDateWithFormat(text, format);
}
if (format.indexOf('HH:mm') < 0) {
return DateUtils.format(dateObj, 'YYYY-MM-DD') || null;
}
return DateUtils.format(dateObj, 'YYYY-MM-DD HH:MM') || null;
};
export { formatTextToDate };

View File

@@ -1,11 +1,9 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { import { CellType, NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP } from '../_basic';
CellType,
NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP,
} from '../_basic';
import { getColumnByIndex } from './column-utils'; import { getColumnByIndex } from './column-utils';
import { NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, TRANSFER_TYPES } from '../constants'; import { NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, TRANSFER_TYPES } from '../constants';
import { getGroupRecordByIndex } from './group-metrics'; import { getGroupRecordByIndex } from './group-metrics';
import { convertCellValue } from './convert-utils';
const NORMAL_RULE = ({ value }) => { const NORMAL_RULE = ({ value }) => {
return value; return value;
@@ -20,16 +18,15 @@ class GridUtils {
this.api = api; this.api = api;
} }
getCopiedContent({ type, copied, isGroupView }) { getCopiedContent({ type, copied, isGroupView, columns }) {
// copy from internal grid // copy from internal grid
if (type === TRANSFER_TYPES.DTABLE_FRAGMENT) { if (type === TRANSFER_TYPES.DTABLE_FRAGMENT) {
const { shownColumns: columns } = this.tablePage.state;
const { selectedRecordIds, copiedRange } = copied; const { selectedRecordIds, copiedRange } = copied;
// copy from selected rows // copy from selected rows
if (Array.isArray(selectedRecordIds) && selectedRecordIds.length > 0) { if (Array.isArray(selectedRecordIds) && selectedRecordIds.length > 0) {
return { return {
copiedRecords: selectedRecordIds.map(recordId => this.tablePage.recordGetterById(recordId)), copiedRecords: selectedRecordIds.map(recordId => this.api.recordGetterById(recordId)),
copiedColumns: [...columns], copiedColumns: [...columns],
}; };
} }
@@ -42,7 +39,7 @@ class GridUtils {
const { rowIdx: maxRecordIndex, idx: maxColumnIndex } = bottomRight; const { rowIdx: maxRecordIndex, idx: maxColumnIndex } = bottomRight;
let currentGroupIndex = minGroupRecordIndex; let currentGroupIndex = minGroupRecordIndex;
for (let i = minRecordIndex; i <= maxRecordIndex; i++) { for (let i = minRecordIndex; i <= maxRecordIndex; i++) {
copiedRecords.push(this.tablePage.recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupIndex, recordIndex: i })); copiedRecords.push(this.api.recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupIndex, recordIndex: i }));
if (isGroupView) { if (isGroupView) {
currentGroupIndex++; currentGroupIndex++;
} }
@@ -58,8 +55,8 @@ class GridUtils {
return { copiedRecords, copiedColumns }; return { copiedRecords, copiedColumns };
} }
async paste({ copied, multiplePaste, pasteRange, isGroupView }) { async paste({ copied, multiplePaste, pasteRange, isGroupView, columns }) {
const { row_ids: renderRecordIds, columns } = this.metadata; const { row_ids: renderRecordIds } = this.metadata;
const { topLeft, bottomRight = {} } = pasteRange; const { topLeft, bottomRight = {} } = pasteRange;
const { rowIdx: startRecordIndex, idx: startColumnIndex, groupRecordIndex } = topLeft; const { rowIdx: startRecordIndex, idx: startColumnIndex, groupRecordIndex } = topLeft;
const { rowIdx: endRecordIndex, idx: endColumnIndex } = bottomRight; const { rowIdx: endRecordIndex, idx: endColumnIndex } = bottomRight;
@@ -72,6 +69,7 @@ class GridUtils {
// need expand records // need expand records
const startExpandRecordIndex = renderRecordsCount - startRecordIndex; const startExpandRecordIndex = renderRecordsCount - startRecordIndex;
if ((copiedRecordsLen > startExpandRecordIndex)) return; if ((copiedRecordsLen > startExpandRecordIndex)) return;
let updateRecordIds = []; let updateRecordIds = [];
@@ -80,8 +78,9 @@ class GridUtils {
let idOldRecordData = {}; let idOldRecordData = {};
let idOriginalOldRecordData = {}; let idOriginalOldRecordData = {};
let currentGroupRecordIndex = groupRecordIndex; let currentGroupRecordIndex = groupRecordIndex;
for (let i = 0; i < pasteRecordsLen; i++) { for (let i = 0; i < pasteRecordsLen; i++) {
const pasteRecord = this.tablePage.recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupRecordIndex, recordIndex: startRecordIndex + i }); const pasteRecord = this.api.recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupRecordIndex, recordIndex: startRecordIndex + i });
if (isGroupView) { if (isGroupView) {
currentGroupRecordIndex++; currentGroupRecordIndex++;
} }
@@ -93,6 +92,7 @@ class GridUtils {
const copiedRecord = copiedRecords[copiedRecordIndex]; const copiedRecord = copiedRecords[copiedRecordIndex];
let originalUpdate = {}; let originalUpdate = {};
let originalOldRecordData = {}; let originalOldRecordData = {};
for (let j = 0; j < pasteColumnsLen; j++) { for (let j = 0; j < pasteColumnsLen; j++) {
const pasteColumn = getColumnByIndex(j + startColumnIndex, columns); const pasteColumn = getColumnByIndex(j + startColumnIndex, columns);
if (!pasteColumn || NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP[pasteColumn.type]) { if (!pasteColumn || NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP[pasteColumn.type]) {
@@ -100,16 +100,16 @@ class GridUtils {
} }
const copiedColumnIndex = j % copiedColumnsLen; const copiedColumnIndex = j % copiedColumnsLen;
const copiedColumn = getColumnByIndex(copiedColumnIndex, copiedColumns); const copiedColumn = getColumnByIndex(copiedColumnIndex, copiedColumns);
const { key: pasteColumnKey } = pasteColumn; const { name: pasteColumnName } = pasteColumn;
const { key: copiedColumnKey } = copiedColumn; const { name: copiedColumnName } = copiedColumn;
const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnKey) ? pasteRecord[pasteColumnKey] : null; const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnName) ? pasteRecord[pasteColumnName] : null;
const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnKey) ? copiedRecord[copiedColumnKey] : null; const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnName) ? copiedRecord[copiedColumnName] : null;
const update = this.convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn); const update = convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn);
if (update === pasteCellValue) { if (update === pasteCellValue) {
continue; continue;
} }
originalUpdate[pasteColumnKey] = update; originalUpdate[pasteColumnName] = update;
originalOldRecordData[pasteColumnKey] = pasteCellValue; originalOldRecordData[pasteColumnName] = pasteCellValue;
} }
if (Object.keys(originalUpdate).length > 0) { if (Object.keys(originalUpdate).length > 0) {
@@ -124,7 +124,7 @@ class GridUtils {
} }
if (updateRecordIds.length === 0) return; if (updateRecordIds.length === 0) return;
this.modifyRecords(updateRecordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste); this.api.modifyRecords(updateRecordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
} }
getLinkedRowsIdsByNameColumn(linkedTableRows, linkColumnKey, cellValue, linkItem) { getLinkedRowsIdsByNameColumn(linkedTableRows, linkColumnKey, cellValue, linkItem) {