1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-20 02:48:51 +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;
};
const isNumber = (number) => (number || number === 0) && Object.prototype.toString.call(number) === '[object Number]';
export {
getPrecisionNumber,
getNumberDisplayString,
replaceNumberNotAllowInput,
formatStringToNumber,
formatTextToNumber,
getFloatNumber,
isNumber,
};

View File

@@ -97,11 +97,10 @@ const createOption = (options, optionName, optionColor = '') => {
const generatorCellOption = (options, optionName) => {
const existOption = options.find((option) => option.name === optionName);
if (existOption) {
return { selectedOptionId: existOption.id };
return existOption;
}
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';
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, ...params }) => {
const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, searchResult, recordGetterByIndex, recordGetterById, ...params }) => {
const gridUtils = useMemo(() => {
return new GridUtils(metadata, { modifyRecord, modifyRecords });
}, [metadata, modifyRecord, modifyRecords]);
return new GridUtils(metadata, { modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById });
}, [metadata, modifyRecord, modifyRecords, recordGetterByIndex, recordGetterById]);
const { openRecordDetails } = useRecordDetails();
const groupbysCount = useMemo(() => {
@@ -28,6 +30,10 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
return columns.filter(column => !hidden_columns.includes(column.key));
}, [metadata]);
const getCopiedRecordsAndColumnsFromRange = useCallback(({ type, copied, isGroupView }) => {
return gridUtils.getCopiedContent({ type, copied, isGroupView, columns });
}, [gridUtils, columns]);
const updateRecord = useCallback(({ rowId, updates, originalUpdates, oldRowData, originalOldRowData }) => {
modifyRecord && modifyRecord(rowId, updates, oldRowData, originalUpdates, originalOldRowData);
}, [modifyRecord]);
@@ -36,6 +42,10 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
modifyRecords && modifyRecords(recordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
}, [modifyRecords]);
const paste = useCallback(({ type, copied, multiplePaste, pasteRange, isGroupView }) => {
gridUtils.paste({ type, copied, multiplePaste, pasteRange, isGroupView, columns });
}, [gridUtils, columns]);
return (
<div className={classnames('table-main-container container-fluid p-0', { [`group-level-${groupbysCount + 1}`]: groupbysCount > 0 })}>
<Records
@@ -49,10 +59,14 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
gridUtils={gridUtils}
scrollToLoadMore={loadMore}
loadAll={loadAll}
paste={paste}
groupOffsetLeft={groupOffset}
modifyRecord={updateRecord}
updateRecords={updateRecords}
onRowExpand={openRecordDetails}
getCopiedRecordsAndColumnsFromRange={getCopiedRecordsAndColumnsFromRange}
recordGetterById={recordGetterById}
recordGetterByIndex={recordGetterByIndex}
{...params}
/>
</div>

View File

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

View File

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

View File

@@ -72,17 +72,18 @@ class InteractionMasks extends React.Component {
}
componentDidMount() {
this.unsubscribeSelectColumn = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_COLUMN, this.onColumnSelect);
this.unsubscribeDragEnter = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.DRAG_ENTER, this.handleDragEnter);
this.unsubscribeSelectCell = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, this.onSelectCell);
this.unsubscribeSelectNone = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone);
this.unsubscribeSelectStart = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_START, this.onSelectCellRangeStarted);
this.unsubscribeSelectUpdate = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_UPDATE, this.onSelectCellRangeUpdated);
this.unsubscribeSelectEnd = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SELECT_END, this.onSelectCellRangeEnded);
this.unsubscribeOpenEditorEvent = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.OPEN_EDITOR, this.onOpenEditorEvent);
this.unsubscribeCloseEditorEvent = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.CLOSE_EDITOR, this.onCloseEditorEvent);
this.unsubscribeCopy = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.COPY_CELLS, this.onCopy);
this.unsubscribePaste = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.PASTE_CELLS, this.onPaste);
const eventBus = window.sfMetadataContext.eventBus;
this.unsubscribeSelectColumn = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_COLUMN, this.onColumnSelect);
this.unsubscribeDragEnter = eventBus.subscribe(EVENT_BUS_TYPE.DRAG_ENTER, this.handleDragEnter);
this.unsubscribeSelectCell = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_CELL, this.onSelectCell);
this.unsubscribeSelectNone = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_NONE, this.selectNone);
this.unsubscribeSelectStart = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_START, this.onSelectCellRangeStarted);
this.unsubscribeSelectUpdate = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_UPDATE, this.onSelectCellRangeUpdated);
this.unsubscribeSelectEnd = eventBus.subscribe(EVENT_BUS_TYPE.SELECT_END, this.onSelectCellRangeEnded);
this.unsubscribeOpenEditorEvent = eventBus.subscribe(EVENT_BUS_TYPE.OPEN_EDITOR, this.onOpenEditorEvent);
this.unsubscribeCloseEditorEvent = eventBus.subscribe(EVENT_BUS_TYPE.CLOSE_EDITOR, this.onCloseEditorEvent);
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) {
@@ -111,7 +112,7 @@ class InteractionMasks extends React.Component {
}
onColumnSelect = (column) => {
let { columns, isGroupView } = this.props;
let { columns, isGroupView, recordsCount } = this.props;
if (isGroupView) return;
let selectColumnIndex = 0;
for (let i = 0; i < columns.length; i++) {
@@ -120,13 +121,12 @@ class InteractionMasks extends React.Component {
break;
}
}
const rowsCount = this.props.recordsCount;
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: rowsCount - 1 },
bottomRight: { idx: selectColumnIndex, rowIdx: recordsCount - 1 },
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';
import EventBus from '../../components/common/event-bus';
import { username } from '../../utils/constants';
import User from './model/user';
class Context {
@@ -14,6 +15,7 @@ class Context {
this.eventBus = null;
this.hasInit = false;
this.permission = 'r';
this.collaboratorsCache = {};
}
async init({ otherSettings }) {
@@ -128,9 +130,22 @@ class Context {
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 = () => {
// 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 {
CellType,
NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP,
} from '../_basic';
import { CellType, NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP } from '../_basic';
import { getColumnByIndex } from './column-utils';
import { NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, TRANSFER_TYPES } from '../constants';
import { getGroupRecordByIndex } from './group-metrics';
import { convertCellValue } from './convert-utils';
const NORMAL_RULE = ({ value }) => {
return value;
@@ -20,16 +18,15 @@ class GridUtils {
this.api = api;
}
getCopiedContent({ type, copied, isGroupView }) {
getCopiedContent({ type, copied, isGroupView, columns }) {
// copy from internal grid
if (type === TRANSFER_TYPES.DTABLE_FRAGMENT) {
const { shownColumns: columns } = this.tablePage.state;
const { selectedRecordIds, copiedRange } = copied;
// copy from selected rows
if (Array.isArray(selectedRecordIds) && selectedRecordIds.length > 0) {
return {
copiedRecords: selectedRecordIds.map(recordId => this.tablePage.recordGetterById(recordId)),
copiedRecords: selectedRecordIds.map(recordId => this.api.recordGetterById(recordId)),
copiedColumns: [...columns],
};
}
@@ -42,7 +39,7 @@ class GridUtils {
const { rowIdx: maxRecordIndex, idx: maxColumnIndex } = bottomRight;
let currentGroupIndex = minGroupRecordIndex;
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) {
currentGroupIndex++;
}
@@ -58,8 +55,8 @@ class GridUtils {
return { copiedRecords, copiedColumns };
}
async paste({ copied, multiplePaste, pasteRange, isGroupView }) {
const { row_ids: renderRecordIds, columns } = this.metadata;
async paste({ copied, multiplePaste, pasteRange, isGroupView, columns }) {
const { row_ids: renderRecordIds } = this.metadata;
const { topLeft, bottomRight = {} } = pasteRange;
const { rowIdx: startRecordIndex, idx: startColumnIndex, groupRecordIndex } = topLeft;
const { rowIdx: endRecordIndex, idx: endColumnIndex } = bottomRight;
@@ -72,6 +69,7 @@ class GridUtils {
// need expand records
const startExpandRecordIndex = renderRecordsCount - startRecordIndex;
if ((copiedRecordsLen > startExpandRecordIndex)) return;
let updateRecordIds = [];
@@ -80,8 +78,9 @@ class GridUtils {
let idOldRecordData = {};
let idOriginalOldRecordData = {};
let currentGroupRecordIndex = groupRecordIndex;
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) {
currentGroupRecordIndex++;
}
@@ -93,6 +92,7 @@ class GridUtils {
const copiedRecord = copiedRecords[copiedRecordIndex];
let originalUpdate = {};
let originalOldRecordData = {};
for (let j = 0; j < pasteColumnsLen; j++) {
const pasteColumn = getColumnByIndex(j + startColumnIndex, columns);
if (!pasteColumn || NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP[pasteColumn.type]) {
@@ -100,16 +100,16 @@ class GridUtils {
}
const copiedColumnIndex = j % copiedColumnsLen;
const copiedColumn = getColumnByIndex(copiedColumnIndex, copiedColumns);
const { key: pasteColumnKey } = pasteColumn;
const { key: copiedColumnKey } = copiedColumn;
const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnKey) ? pasteRecord[pasteColumnKey] : null;
const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnKey) ? copiedRecord[copiedColumnKey] : null;
const update = this.convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn);
const { name: pasteColumnName } = pasteColumn;
const { name: copiedColumnName } = copiedColumn;
const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnName) ? pasteRecord[pasteColumnName] : null;
const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnName) ? copiedRecord[copiedColumnName] : null;
const update = convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn);
if (update === pasteCellValue) {
continue;
}
originalUpdate[pasteColumnKey] = update;
originalOldRecordData[pasteColumnKey] = pasteCellValue;
originalUpdate[pasteColumnName] = update;
originalOldRecordData[pasteColumnName] = pasteCellValue;
}
if (Object.keys(originalUpdate).length > 0) {
@@ -124,7 +124,7 @@ class GridUtils {
}
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) {