mirror of
https://github.com/haiwen/seahub.git
synced 2025-08-31 14:42:10 +00:00
404 lines
16 KiB
JavaScript
404 lines
16 KiB
JavaScript
import dayjs from 'dayjs';
|
||
import { CellType, getCellValueByColumn } from '../_basic';
|
||
import { getColumnByIndex, getColumnOriginName } 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;
|
||
};
|
||
|
||
const isCopyPaste = true;
|
||
|
||
class GridUtils {
|
||
|
||
constructor(metadata, api) {
|
||
this.metadata = metadata;
|
||
this.api = api;
|
||
}
|
||
|
||
getCopiedContent({ type, copied, isGroupView, columns }) {
|
||
// copy from internal grid
|
||
if (type === TRANSFER_TYPES.DTABLE_FRAGMENT) {
|
||
const { selectedRecordIds, copiedRange } = copied;
|
||
|
||
// copy from selected rows
|
||
if (Array.isArray(selectedRecordIds) && selectedRecordIds.length > 0) {
|
||
return {
|
||
copiedRecords: selectedRecordIds.map(recordId => this.api.recordGetterById(recordId)),
|
||
copiedColumns: [...columns],
|
||
};
|
||
}
|
||
|
||
// copy from selected range
|
||
let copiedRecords = [];
|
||
let copiedColumns = [];
|
||
const { topLeft, bottomRight } = copiedRange;
|
||
const { rowIdx: minRecordIndex, idx: minColumnIndex, groupRecordIndex: minGroupRecordIndex } = topLeft;
|
||
const { rowIdx: maxRecordIndex, idx: maxColumnIndex } = bottomRight;
|
||
let currentGroupIndex = minGroupRecordIndex;
|
||
for (let i = minRecordIndex; i <= maxRecordIndex; i++) {
|
||
copiedRecords.push(this.api.recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupIndex, recordIndex: i }));
|
||
if (isGroupView) {
|
||
currentGroupIndex++;
|
||
}
|
||
}
|
||
for (let i = minColumnIndex; i <= maxColumnIndex; i++) {
|
||
copiedColumns.push(getColumnByIndex(i, columns));
|
||
}
|
||
return { copiedRecords, copiedColumns };
|
||
}
|
||
|
||
// copy from other external apps as default
|
||
const { copiedRecords, copiedColumns } = copied;
|
||
return { copiedRecords, copiedColumns };
|
||
}
|
||
|
||
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;
|
||
const { copiedRecords, copiedColumns } = copied;
|
||
const copiedRecordsLen = copiedRecords.length;
|
||
const copiedColumnsLen = copiedColumns.length;
|
||
const pasteRecordsLen = multiplePaste ? endRecordIndex - startRecordIndex + 1 : copiedRecordsLen;
|
||
const pasteColumnsLen = multiplePaste ? endColumnIndex - startColumnIndex + 1 : copiedColumnsLen;
|
||
const renderRecordsCount = renderRecordIds.length;
|
||
|
||
// need expand records
|
||
const startExpandRecordIndex = renderRecordsCount - startRecordIndex;
|
||
|
||
if ((copiedRecordsLen > startExpandRecordIndex)) return;
|
||
|
||
let updateRecordIds = [];
|
||
let idRecordUpdates = {};
|
||
let idOriginalRecordUpdates = {};
|
||
let idOldRecordData = {};
|
||
let idOriginalOldRecordData = {};
|
||
let currentGroupRecordIndex = groupRecordIndex;
|
||
|
||
for (let i = 0; i < pasteRecordsLen; i++) {
|
||
const pasteRecord = this.api.recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupRecordIndex, recordIndex: startRecordIndex + i });
|
||
if (isGroupView) {
|
||
currentGroupRecordIndex++;
|
||
}
|
||
if (!pasteRecord) {
|
||
continue;
|
||
}
|
||
const updateRecordId = pasteRecord._id;
|
||
const copiedRecordIndex = i % copiedRecordsLen;
|
||
const copiedRecord = copiedRecords[copiedRecordIndex];
|
||
let originalUpdate = {};
|
||
let originalOldRecordData = {};
|
||
const { canModifyRow, canModifyColumn } = window.sfMetadataContext;
|
||
|
||
for (let j = 0; j < pasteColumnsLen; j++) {
|
||
const pasteColumn = getColumnByIndex(j + startColumnIndex, columns);
|
||
if (!pasteColumn || !(canModifyRow(pasteRecord) && canModifyColumn(pasteColumn))) {
|
||
continue;
|
||
}
|
||
const copiedColumnIndex = j % copiedColumnsLen;
|
||
const copiedColumn = getColumnByIndex(copiedColumnIndex, copiedColumns);
|
||
const pasteColumnName = getColumnOriginName(pasteColumn);
|
||
const copiedColumnName = getColumnOriginName(copiedColumn);
|
||
const pasteCellValue = Object.prototype.hasOwnProperty.call(pasteRecord, pasteColumnName) ? getCellValueByColumn(pasteRecord, pasteColumn) : null;
|
||
const copiedCellValue = Object.prototype.hasOwnProperty.call(copiedRecord, copiedColumnName) ? getCellValueByColumn(copiedRecord, copiedColumn) : null;
|
||
const update = convertCellValue(copiedCellValue, pasteCellValue, pasteColumn, copiedColumn);
|
||
if (update === pasteCellValue) {
|
||
continue;
|
||
}
|
||
originalUpdate[pasteColumnName] = update;
|
||
originalOldRecordData[pasteColumnName] = pasteCellValue;
|
||
}
|
||
|
||
if (Object.keys(originalUpdate).length > 0) {
|
||
updateRecordIds.push(updateRecordId);
|
||
const update = originalUpdate;
|
||
const oldRecordData = originalOldRecordData;
|
||
idRecordUpdates[updateRecordId] = update;
|
||
idOriginalRecordUpdates[updateRecordId] = originalUpdate;
|
||
idOldRecordData[updateRecordId] = oldRecordData;
|
||
idOriginalOldRecordData[updateRecordId] = originalOldRecordData;
|
||
}
|
||
}
|
||
|
||
if (updateRecordIds.length === 0) return;
|
||
this.api.modifyRecords(updateRecordIds, idRecordUpdates, idOriginalRecordUpdates, idOldRecordData, idOriginalOldRecordData, isCopyPaste);
|
||
}
|
||
|
||
getLinkedRowsIdsByNameColumn(linkedTableRows, linkColumnKey, cellValue, linkItem) {
|
||
if (!Array.isArray(linkedTableRows) || linkedTableRows.length === 0) {
|
||
return [];
|
||
}
|
||
const cellValueStr = String(cellValue);
|
||
|
||
// 1、If all string match the corresponding row, return this row
|
||
const linkedRow = linkedTableRows.find(row => row['0000']?.trim() === cellValueStr.trim()) || null;
|
||
if (linkedRow) {
|
||
linkItem[linkColumnKey] = [{ display_value: cellValueStr, row_id: linkedRow._id }];
|
||
return [linkedRow._id];
|
||
}
|
||
|
||
// 2、If the string contains a comma, split into multiple substrings to match the corresponding rows
|
||
let linkedRowsIds = [];
|
||
if (cellValueStr.includes(',') || cellValueStr.includes(',')) {
|
||
const copiedNames = cellValueStr.split(/[,,]/).map(item => item.trim()).filter((value, index, self) => self.indexOf(value) === index);
|
||
if (!Array.isArray(copiedNames) || copiedNames.length === 0) {
|
||
return [];
|
||
}
|
||
linkItem[linkColumnKey] = [];
|
||
copiedNames.forEach((copiedName) => {
|
||
const linkedRow = linkedTableRows.find(row => row['0000']?.trim() === copiedName) || null;
|
||
if (linkedRow) {
|
||
linkItem[linkColumnKey].push({ display_value: copiedName, row_id: linkedRow._id });
|
||
linkedRowsIds.push(linkedRow._id);
|
||
}
|
||
});
|
||
}
|
||
return linkedRowsIds;
|
||
}
|
||
|
||
getUpdateDraggedRecords(draggedRange, shownColumns, rows, idRowMap, groupMetrics) {
|
||
let rowIds = [];
|
||
let updatedOriginalRows = {};
|
||
let oldOriginalRows = {};
|
||
const updatedRows = {};
|
||
const oldRows = {};
|
||
const { overRecordIdx, topLeft, bottomRight } = draggedRange;
|
||
const { idx: startColumnIdx } = topLeft;
|
||
const { idx: endColumnIdx, rowIdx: endRecordIdx, groupRecordIndex } = bottomRight;
|
||
const { canModifyRow, canModifyColumn } = window.sfMetadataContext;
|
||
|
||
const draggedRangeMatrix = this.getDraggedRangeMatrix(shownColumns, draggedRange, rows, groupMetrics, idRowMap);
|
||
const rules = this.getDraggedRangeRules(draggedRangeMatrix, shownColumns, startColumnIdx);
|
||
|
||
const selectedRowLength = draggedRangeMatrix[0].length;
|
||
let fillingIndex = draggedRangeMatrix[0].length;
|
||
|
||
// if group view then use index of groupRows which is different from the normal rows(they represent DOMs)
|
||
let currentGroupRowIndex = groupRecordIndex + 1;
|
||
for (let i = endRecordIdx + 1; i <= overRecordIdx; i++) {
|
||
let dragRow;
|
||
// find the row that need to be updated (it's dragged)
|
||
if (currentGroupRowIndex) {
|
||
const groupRow = getGroupRecordByIndex(currentGroupRowIndex, groupMetrics);
|
||
dragRow = idRowMap[groupRow.rowId];
|
||
} else {
|
||
dragRow = rows[i];
|
||
}
|
||
const { _id: dragRowId } = dragRow;
|
||
fillingIndex++;
|
||
if (!canModifyRow(dragRow)) continue;
|
||
rowIds.push(dragRowId);
|
||
|
||
const idx = (i - endRecordIdx - 1) % selectedRowLength;
|
||
for (let j = startColumnIdx; j <= endColumnIdx; j++) {
|
||
let column = shownColumns[j];
|
||
let { key: cellKey, type } = column;
|
||
const columnName = getColumnOriginName(column);
|
||
if (canModifyColumn(column) && !NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES.includes(type)) {
|
||
const value = draggedRangeMatrix[j - startColumnIdx][idx];
|
||
const rule = rules[cellKey];
|
||
const fillingValue = rule({ n: fillingIndex - 1, value });
|
||
|
||
updatedOriginalRows[dragRowId] = Object.assign({}, updatedOriginalRows[dragRowId], { [columnName]: fillingValue });
|
||
oldOriginalRows[dragRowId] = Object.assign({}, oldOriginalRows[dragRowId], { [columnName]: dragRow[columnName] });
|
||
const update = updatedOriginalRows[dragRowId];
|
||
const oldUpdate = oldOriginalRows[dragRowId];
|
||
|
||
updatedRows[dragRowId] = Object.assign({}, updatedRows[dragRowId], update);
|
||
oldRows[dragRowId] = Object.assign({}, oldRows[dragRowId], oldUpdate);
|
||
}
|
||
}
|
||
currentGroupRowIndex++;
|
||
}
|
||
|
||
return { recordIds: rowIds, idOriginalRecordUpdates: updatedOriginalRows, idRecordUpdates: updatedRows, idOriginalOldRecordData: oldOriginalRows, idOldRecordData: oldRows };
|
||
}
|
||
|
||
getDraggedRangeMatrix(columns, draggedRange, rows, groupMetrics, idRowMap) {
|
||
let draggedRangeMatrix = [];
|
||
const { topLeft, bottomRight } = draggedRange;
|
||
const { idx: startColumnIdx, rowIdx: startRowIdx, groupRecordIndex } = topLeft;
|
||
const { idx: endColumnIdx, rowIdx: endRowIdx } = bottomRight;
|
||
for (let i = startColumnIdx; i <= endColumnIdx; i++) {
|
||
let currentGroupRecordIndex = groupRecordIndex;
|
||
draggedRangeMatrix[i - startColumnIdx] = [];
|
||
const column = columns[i];
|
||
for (let j = startRowIdx; j <= endRowIdx; j++) {
|
||
let selectedRecord;
|
||
if (currentGroupRecordIndex) {
|
||
const groupRecord = getGroupRecordByIndex(currentGroupRecordIndex, groupMetrics);
|
||
selectedRecord = idRowMap[groupRecord.rowId];
|
||
} else {
|
||
selectedRecord = rows[j];
|
||
}
|
||
draggedRangeMatrix[i - startColumnIdx][j - startRowIdx] = getCellValueByColumn(selectedRecord, column);
|
||
currentGroupRecordIndex++;
|
||
}
|
||
}
|
||
return draggedRangeMatrix;
|
||
}
|
||
|
||
getDraggedRangeRules(draggedRangeMatrix, columns, startColumnIdx) {
|
||
let draggedRangeRuleMatrix = {};
|
||
draggedRangeMatrix.forEach((valueList, i) => {
|
||
let column = columns[i + startColumnIdx];
|
||
let { type, data, key } = column;
|
||
let ruleMatrixItem = NORMAL_RULE;
|
||
if (valueList.length > 1) {
|
||
switch (type) {
|
||
case CellType.DATE: {
|
||
let format = data && data.format && data.format.indexOf('HH:mm') > -1 ? 'YYYY-MM-DD HH:mm' : 'YYYY-MM-DD';
|
||
let value0 = valueList[0];
|
||
let yearTolerance = this.getYearTolerance(valueList);
|
||
if (yearTolerance) {
|
||
ruleMatrixItem = ({ n }) => {
|
||
return dayjs(value0).add(n * yearTolerance, 'years').format(format);
|
||
};
|
||
break;
|
||
}
|
||
let monthTolerance = this.getMonthTolerance(valueList);
|
||
if (monthTolerance) {
|
||
ruleMatrixItem = ({ n }) => {
|
||
return dayjs(value0).add(n * monthTolerance, 'months').format(format);
|
||
};
|
||
break;
|
||
}
|
||
let dayTolerance = this.getDayTolerance(valueList);
|
||
if (dayTolerance) {
|
||
ruleMatrixItem = ({ n }) => {
|
||
let time = n * dayTolerance + this.getDateStringValue(value0);
|
||
return dayjs(time).format(format);
|
||
};
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
case CellType.NUMBER: {
|
||
ruleMatrixItem = this.getLeastSquares(valueList);
|
||
break;
|
||
}
|
||
case CellType.TEXT: {
|
||
ruleMatrixItem = this._getTextRule(valueList);
|
||
break;
|
||
}
|
||
case CellType.RATE: {
|
||
ruleMatrixItem = this.getRatingLeastSquares(valueList, data);
|
||
break;
|
||
}
|
||
default: {
|
||
ruleMatrixItem = NORMAL_RULE;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
draggedRangeRuleMatrix[key] = ruleMatrixItem;
|
||
});
|
||
return draggedRangeRuleMatrix;
|
||
}
|
||
|
||
getDateStringValue(date) {
|
||
let dateObject = dayjs(date);
|
||
return dateObject.isValid() ? dateObject.valueOf() : 0;
|
||
}
|
||
|
||
getYearTolerance(dateList) {
|
||
let date0 = dayjs(dateList[0]);
|
||
let date1 = dayjs(dateList[1]);
|
||
if (!date0.isValid() || !date1.isValid()) {
|
||
return 0;
|
||
}
|
||
if (date0.month() !== date1.month() || date0.date() !== date1.date()
|
||
|| date0.hour() !== date1.hour() || date0.minute() !== date1.minute()) {
|
||
return 0;
|
||
}
|
||
let date0Year = date0.year();
|
||
let tolerance = date1.year() - date0Year;
|
||
let isYearArithmeticSequence = dateList.every((date, n) => {
|
||
let dateObject = dayjs(date);
|
||
if (!dateObject.isValid()) {
|
||
return false;
|
||
}
|
||
return dateObject.year() === n * tolerance + date0Year;
|
||
});
|
||
return isYearArithmeticSequence ? tolerance : 0;
|
||
}
|
||
|
||
getMonthTolerance(dateList) {
|
||
let date0 = dayjs(dateList[0]);
|
||
let date1 = dayjs(dateList[1]);
|
||
if (!date0.isValid() || !date1.isValid()) {
|
||
return 0;
|
||
}
|
||
if (date0.date() !== date1.date() || date0.hour() !== date1.hour() || date0.minute() !== date1.minute()) {
|
||
return 0;
|
||
}
|
||
let tolerance = (date1.month() - date0.month()) + (date1.year() - date0.year()) * 12;
|
||
let isMonthArithmeticSequence = dateList.every((date, i) => {
|
||
let month = i * tolerance;
|
||
let dateObject = dayjs(date);
|
||
if (!dateObject.isValid()) {
|
||
return false;
|
||
}
|
||
return dateObject.isSame(dayjs(dateList[0]).add(month, 'month'), 'minute');
|
||
});
|
||
return isMonthArithmeticSequence ? tolerance : 0;
|
||
}
|
||
|
||
getDayTolerance(dateList) {
|
||
let date0 = this.getDateStringValue(dateList[0]);
|
||
let tolerance = this.getDateStringValue(dateList[1]) - date0;
|
||
let isDayArithmeticSequence = dateList.every((date, i) => {
|
||
if (!dayjs(date).isValid()) {
|
||
return false;
|
||
}
|
||
return this.getDateStringValue(date) === i * tolerance + date0;
|
||
});
|
||
return isDayArithmeticSequence ? tolerance : 0;
|
||
}
|
||
|
||
getLeastSquares(numberList) {
|
||
let slope;
|
||
let intercept;
|
||
let xAverage;
|
||
let yAverage;
|
||
let xSum = 0;
|
||
let ySum = 0;
|
||
let xSquareSum = 0;
|
||
let xySum = 0;
|
||
let validCellsLen = 0;
|
||
let emptyCellPositions = [];
|
||
numberList.forEach((v, i) => {
|
||
if (v !== undefined && v !== null && v !== '') {
|
||
validCellsLen++;
|
||
xSum += i;
|
||
ySum += v;
|
||
xySum += (v * i);
|
||
xSquareSum += Math.pow(i, 2);
|
||
} else {
|
||
emptyCellPositions.push(i);
|
||
}
|
||
});
|
||
if (validCellsLen < 2) {
|
||
return NORMAL_RULE;
|
||
}
|
||
xAverage = xSum / validCellsLen;
|
||
yAverage = ySum / validCellsLen;
|
||
slope = (xySum - validCellsLen * xAverage * yAverage) / (xSquareSum - validCellsLen * Math.pow(xAverage, 2));
|
||
intercept = yAverage - slope * xAverage;
|
||
return ({ n }) => {
|
||
if (emptyCellPositions.length && emptyCellPositions.includes(n % numberList.length)) {
|
||
return '';
|
||
}
|
||
let y = n * slope + intercept;
|
||
return Number(parseFloat(y).toFixed(8));
|
||
};
|
||
}
|
||
|
||
}
|
||
|
||
export default GridUtils;
|