mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-22 11:57:34 +00:00
feat(tag): display tags with table (#7311)
This commit is contained in:
53
frontend/src/components/sf-table/utils/cell-comparer.js
Normal file
53
frontend/src/components/sf-table/utils/cell-comparer.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import ObjectUtils, { isEmptyObject } from '../../../utils/object';
|
||||
import { getCellValueByColumn } from './cell';
|
||||
|
||||
export const checkCellValueChanged = (oldVal, newVal) => {
|
||||
if (oldVal === newVal) return false;
|
||||
if (oldVal === undefined || oldVal === null || oldVal === '') {
|
||||
if (newVal === undefined || newVal === null || newVal === '') return false;
|
||||
if (typeof newVal === 'object' && isEmptyObject(newVal)) return false;
|
||||
if (Array.isArray(newVal)) return newVal.length !== 0;
|
||||
if (typeof newVal === 'boolean') return newVal !== false;
|
||||
}
|
||||
if (Array.isArray(oldVal) && Array.isArray(newVal)) {
|
||||
// [{}].toString(): [object Object]
|
||||
return JSON.stringify(oldVal) !== JSON.stringify(newVal);
|
||||
}
|
||||
if (typeof oldVal === 'object' && typeof newVal === 'object' && newVal !== null) {
|
||||
return !ObjectUtils.isSameObject(oldVal, newVal);
|
||||
}
|
||||
return oldVal !== newVal;
|
||||
};
|
||||
|
||||
export const cellCompare = (props, nextProps) => {
|
||||
const {
|
||||
record: oldRecord, column, isCellSelected, isLastCell, highlightClassName, height, bgColor,
|
||||
} = props;
|
||||
const {
|
||||
record: newRecord, highlightClassName: newHighlightClassName, height: newHeight, column: newColumn, bgColor: newBgColor,
|
||||
} = nextProps;
|
||||
|
||||
// the modification of column is not currently supported, only the modification of cell data is considered
|
||||
const oldValue = getCellValueByColumn(oldRecord, column);
|
||||
const newValue = getCellValueByColumn(newRecord, column);
|
||||
let isCustomCellValueChanged = false;
|
||||
if (props.checkCellValueChanged) {
|
||||
isCustomCellValueChanged = props.checkCellValueChanged(column, oldRecord, newRecord);
|
||||
}
|
||||
return (
|
||||
isCustomCellValueChanged ||
|
||||
checkCellValueChanged(oldValue, newValue) ||
|
||||
oldRecord._last_modifier !== newRecord._last_modifier ||
|
||||
isCellSelected !== nextProps.isCellSelected ||
|
||||
isLastCell !== nextProps.isLastCell ||
|
||||
highlightClassName !== newHighlightClassName ||
|
||||
height !== newHeight ||
|
||||
column.name !== newColumn.name ||
|
||||
column.left !== newColumn.left ||
|
||||
column.width !== newColumn.width ||
|
||||
!ObjectUtils.isSameObject(column.data, newColumn.data) ||
|
||||
bgColor !== newBgColor ||
|
||||
props.groupRecordIndex !== nextProps.groupRecordIndex ||
|
||||
props.recordIndex !== nextProps.recordIndex
|
||||
);
|
||||
};
|
12
frontend/src/components/sf-table/utils/cell.js
Normal file
12
frontend/src/components/sf-table/utils/cell.js
Normal file
@@ -0,0 +1,12 @@
|
||||
import { checkIsPrivateColumn } from './column';
|
||||
|
||||
/*
|
||||
* @param {object} record eg: { [column_key]: value, [column_name]: value }
|
||||
* @param {object} column
|
||||
* @return {any} value
|
||||
*/
|
||||
export const getCellValueByColumn = (record, column) => {
|
||||
if (!record || !column) return null;
|
||||
const { key, name } = column;
|
||||
return checkIsPrivateColumn(column) ? record[key] : record[name];
|
||||
};
|
123
frontend/src/components/sf-table/utils/column.js
Normal file
123
frontend/src/components/sf-table/utils/column.js
Normal file
@@ -0,0 +1,123 @@
|
||||
import { shallowCloneObject } from '../../../utils/object';
|
||||
|
||||
export const checkIsNameColumn = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.is_name_column;
|
||||
};
|
||||
|
||||
export const checkIsColumnFrozen = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.frozen;
|
||||
};
|
||||
|
||||
export const checkEditableViaClickCell = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.editable_via_click_cell;
|
||||
};
|
||||
|
||||
export const checkIsColumnSupportDirectEdit = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.is_support_direct_edit;
|
||||
};
|
||||
|
||||
export const checkIsColumnSupportPreview = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.is_support_preview;
|
||||
};
|
||||
|
||||
export const checkIsColumnEditable = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.editable;
|
||||
};
|
||||
|
||||
export const checkIsPopupColumnEditor = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.is_popup_editor;
|
||||
};
|
||||
|
||||
export const checkIsPrivateColumn = (column) => {
|
||||
if (!column) return false;
|
||||
return !!column.is_private;
|
||||
};
|
||||
|
||||
export const getColumnOriginName = (column) => {
|
||||
if (checkIsPrivateColumn(column)) return column.key;
|
||||
return column.name;
|
||||
};
|
||||
|
||||
export const getColumnByKey = (columns, columnKey) => {
|
||||
if (!Array.isArray(columns) || !columnKey) return null;
|
||||
return columns.find((column) => column.key === columnKey);
|
||||
};
|
||||
|
||||
export const getColumnIndexByKey = (columnKey, columns) => {
|
||||
let index = 0;
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
if (columnKey === columns[i].key) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return index;
|
||||
};
|
||||
|
||||
export function getColumnByIndex(index, columns) {
|
||||
if (Array.isArray(columns)) {
|
||||
return columns[index];
|
||||
}
|
||||
if (typeof Immutable !== 'undefined') {
|
||||
return columns.get(index);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export const getFrozenColumns = (columns) => {
|
||||
return columns.filter(column => checkIsColumnFrozen(column));
|
||||
};
|
||||
|
||||
export const recalculate = (columns, allColumns, sequenceColumnWidth = 0) => {
|
||||
const displayColumns = columns;
|
||||
const displayAllColumns = allColumns;
|
||||
const totalWidth = displayColumns.reduce((total, column) => {
|
||||
const width = column.width;
|
||||
total += width;
|
||||
return total;
|
||||
}, 0);
|
||||
let left = sequenceColumnWidth;
|
||||
const frozenColumns = displayColumns.filter(c => checkIsColumnFrozen(c));
|
||||
const frozenColumnsWidth = frozenColumns.reduce((w, column) => {
|
||||
const width = column.width;
|
||||
return w + width;
|
||||
}, 0);
|
||||
const lastFrozenColumnKey = frozenColumnsWidth > 0 ? frozenColumns[frozenColumns.length - 1].key : null;
|
||||
const newColumns = displayColumns.map((column, index) => {
|
||||
const width = column.width;
|
||||
column.idx = index; // set column idx
|
||||
column.left = left; // set column offset
|
||||
column.width = width;
|
||||
left += width;
|
||||
return column;
|
||||
});
|
||||
|
||||
return {
|
||||
totalWidth,
|
||||
lastFrozenColumnKey,
|
||||
frozenColumnsWidth,
|
||||
columns: newColumns,
|
||||
allColumns: displayAllColumns,
|
||||
};
|
||||
};
|
||||
|
||||
export const recalculateColumnMetricsByResizeColumn = (columnMetrics, sequenceColumnWidth, columnKey, width) => {
|
||||
let newColumnMetrics = shallowCloneObject(columnMetrics);
|
||||
let updatedColumns = columnMetrics.columns.map((column) => shallowCloneObject(column));
|
||||
const columnIndex = updatedColumns.findIndex((column) => column.key === columnKey);
|
||||
let updatedColumn = updatedColumns[columnIndex];
|
||||
updatedColumn.width = width;
|
||||
newColumnMetrics.columns = updatedColumns;
|
||||
|
||||
const columnAllIndex = columnMetrics.allColumns.findIndex((column) => column.key === columnKey);
|
||||
newColumnMetrics.allColumns[columnAllIndex] = { ...columnMetrics.columns[columnIndex], width };
|
||||
|
||||
return recalculate(newColumnMetrics.columns, newColumnMetrics.allColumns, sequenceColumnWidth);
|
||||
};
|
107
frontend/src/components/sf-table/utils/get-event-transfer.js
Normal file
107
frontend/src/components/sf-table/utils/get-event-transfer.js
Normal file
@@ -0,0 +1,107 @@
|
||||
import TRANSFER_TYPES from '../constants/transfer-types';
|
||||
|
||||
const { FRAGMENT, HTML, TEXT } = TRANSFER_TYPES;
|
||||
|
||||
function getEventTransfer(event) {
|
||||
const transfer = event.dataTransfer || event.clipboardData;
|
||||
let dtableFragment = getType(transfer, FRAGMENT);
|
||||
let html = getType(transfer, HTML);
|
||||
let text = getType(transfer, TEXT);
|
||||
let files = getFiles(transfer);
|
||||
|
||||
// paste sf-metadata
|
||||
if (dtableFragment) {
|
||||
return { [TRANSFER_TYPES.METADATA_FRAGMENT]: JSON.parse(dtableFragment), type: TRANSFER_TYPES.METADATA_FRAGMENT };
|
||||
}
|
||||
|
||||
// paste html
|
||||
if (html) {
|
||||
let copiedTableNode = (new DOMParser()).parseFromString(html, HTML).querySelector('table');
|
||||
if (copiedTableNode) {
|
||||
return { [TRANSFER_TYPES.METADATA_FRAGMENT]: html2TableFragment(copiedTableNode), html, text, type: 'html' };
|
||||
}
|
||||
return { [TRANSFER_TYPES.METADATA_FRAGMENT]: text2TableFragment(text), html, text, type: 'html' };
|
||||
}
|
||||
|
||||
// paste local picture or other files here
|
||||
if (files && files.length) {
|
||||
return { [TRANSFER_TYPES.METADATA_FRAGMENT]: text2TableFragment(text), 'files': files, type: 'files' };
|
||||
}
|
||||
|
||||
// paste text
|
||||
if (text) {
|
||||
return { [TRANSFER_TYPES.METADATA_FRAGMENT]: text2TableFragment(text), text, type: 'text' };
|
||||
}
|
||||
}
|
||||
|
||||
function getType(transfer, type) {
|
||||
if (!transfer.types || !transfer.types.length) {
|
||||
// COMPAT: In IE 11, there is no `types` field but `getData('Text')`
|
||||
// is supported`. (2017/06/23)
|
||||
return type === TEXT ? transfer.getData('Text') || null : null;
|
||||
}
|
||||
|
||||
return transfer.getData(type);
|
||||
}
|
||||
|
||||
function text2TableFragment(data) {
|
||||
let formattedData = data ? data.replace(/\r/g, '') : '';
|
||||
let dataSplitted = formattedData.split('\n');
|
||||
let rowSplitted = dataSplitted[0].split('\t');
|
||||
let copiedColumns = rowSplitted.map((value, j) => ({ key: `col${j}`, type: 'text' }));
|
||||
let copiedRecords = [];
|
||||
dataSplitted.forEach((row) => {
|
||||
let obj = {};
|
||||
if (row) {
|
||||
row = row.split('\t');
|
||||
row.forEach((col, j) => {
|
||||
obj[`col${j}`] = col;
|
||||
});
|
||||
}
|
||||
copiedRecords.push(obj);
|
||||
});
|
||||
|
||||
return { copiedRecords, copiedColumns };
|
||||
}
|
||||
|
||||
function html2TableFragment(tableNode) {
|
||||
let trs = tableNode.querySelectorAll('tr');
|
||||
let tds = trs[0].querySelectorAll('td');
|
||||
let copiedColumns = [];
|
||||
let copiedRecords = [];
|
||||
tds.forEach((td, i) => {
|
||||
copiedColumns.push({ key: `col${i}`, type: 'text' });
|
||||
});
|
||||
trs.forEach((tr) => {
|
||||
let row = {};
|
||||
let cells = tr.querySelectorAll('td');
|
||||
cells.forEach((cell, i) => {
|
||||
row[`col${i}`] = cell.innerText;
|
||||
});
|
||||
copiedRecords.push(row);
|
||||
});
|
||||
return { copiedRecords, copiedColumns };
|
||||
}
|
||||
|
||||
function getFiles(transfer) {
|
||||
let files;
|
||||
try {
|
||||
// Get and normalize files if they exist.
|
||||
if (transfer.items && transfer.items.length) {
|
||||
files = Array.from(transfer.items)
|
||||
.map(item => (item.kind === 'file' ? item.getAsFile() : null))
|
||||
.filter(exists => exists);
|
||||
} else if (transfer.files && transfer.files.length) {
|
||||
files = Array.from(transfer.files);
|
||||
}
|
||||
} catch (err) {
|
||||
if (transfer.files && transfer.files.length) {
|
||||
files = Array.from(transfer.files);
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
export { text2TableFragment };
|
||||
|
||||
export default getEventTransfer;
|
53
frontend/src/components/sf-table/utils/grid-utils.js
Normal file
53
frontend/src/components/sf-table/utils/grid-utils.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import TRANSFER_TYPES from '../constants/transfer-types';
|
||||
import { getColumnByIndex } from './column';
|
||||
|
||||
|
||||
class GridUtils {
|
||||
|
||||
constructor(renderRecordsIds, { recordGetterById, recordGetterByIndex }) {
|
||||
this.renderRecordsIds = renderRecordsIds;
|
||||
this.api = {
|
||||
recordGetterById,
|
||||
recordGetterByIndex,
|
||||
};
|
||||
}
|
||||
|
||||
getCopiedContent({ type, copied, isGroupView, columns }) {
|
||||
// copy from internal grid
|
||||
if (type === TRANSFER_TYPES.METADATA_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 };
|
||||
}
|
||||
}
|
||||
|
||||
export default GridUtils;
|
9
frontend/src/components/sf-table/utils/grid.js
Normal file
9
frontend/src/components/sf-table/utils/grid.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { OVER_SCAN_COLUMNS } from '../constants/grid';
|
||||
|
||||
export const getColOverScanStartIdx = (colVisibleStartIdx) => {
|
||||
return Math.max(0, Math.floor(colVisibleStartIdx / 10) * 10 - OVER_SCAN_COLUMNS);
|
||||
};
|
||||
|
||||
export const getColOverScanEndIdx = (colVisibleEndIdx, totalNumberColumns) => {
|
||||
return Math.min(Math.ceil(colVisibleEndIdx / 10) * 10 + OVER_SCAN_COLUMNS, totalNumberColumns);
|
||||
};
|
173
frontend/src/components/sf-table/utils/group-metrics.js
Normal file
173
frontend/src/components/sf-table/utils/group-metrics.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import { INSERT_ROW_HEIGHT } from '../constants/grid';
|
||||
import { GROUP_HEADER_HEIGHT, GROUP_ROW_TYPE, GROUP_VIEW_OFFSET } from '../constants/group';
|
||||
import { getColumnByKey } from './column';
|
||||
|
||||
export const createGroupMetrics = (groups, groupbys, pathFoldedGroupMap, columns, rowHeight, includeInsertRow) => {
|
||||
let groupbyColumnsMap = {};
|
||||
groupbys.forEach(groupby => {
|
||||
const columnKey = groupby.column_key;
|
||||
const column = getColumnByKey(columns, columnKey);
|
||||
groupbyColumnsMap[columnKey] = column;
|
||||
});
|
||||
const maxLevel = groupbys.length;
|
||||
const groupRows = getGroupsRows(
|
||||
groups, groupbyColumnsMap, pathFoldedGroupMap, includeInsertRow, rowHeight, maxLevel,
|
||||
{ parentGroupPath: [], currentLevel: maxLevel, isParentGroupVisible: true }
|
||||
);
|
||||
const { computedGroupRows, groupRowsHeight, idGroupRowMap } = setupGroupsRows(groupRows, maxLevel);
|
||||
return {
|
||||
groupRows: computedGroupRows,
|
||||
idGroupRowMap,
|
||||
groupRowsHeight,
|
||||
maxLevel,
|
||||
};
|
||||
};
|
||||
|
||||
export const getGroupsRows = (
|
||||
groups, groupbyColumnsMap, pathFoldedGroupMap, includeInsertRow, rowHeight, maxLevel, {
|
||||
parentGroupPath, parentGroupKey, currentLevel, isParentGroupVisible,
|
||||
}
|
||||
) => {
|
||||
let groupRows = [];
|
||||
groups.forEach((group, groupIndex) => {
|
||||
let groupPath = [];
|
||||
if (parentGroupPath.length > 0) {
|
||||
groupPath.push(...parentGroupPath);
|
||||
}
|
||||
groupPath.push(groupIndex);
|
||||
const { cell_value, subgroups, row_ids, column_key, summaries, original_cell_value } = group;
|
||||
const groupPathString = groupPath.join('-');
|
||||
const isExpanded = isExpandedGroup(groupPathString, pathFoldedGroupMap);
|
||||
const left = (maxLevel - currentLevel + 1) * GROUP_VIEW_OFFSET;
|
||||
const groupKey = `${parentGroupKey ? parentGroupKey : column_key}_${cell_value}`;
|
||||
let groupContainer = {
|
||||
type: GROUP_ROW_TYPE.GROUP_CONTAINER,
|
||||
level: currentLevel,
|
||||
left,
|
||||
key: groupKey,
|
||||
cell_value,
|
||||
column_key,
|
||||
isExpanded,
|
||||
summaries,
|
||||
groupPath,
|
||||
groupPathString,
|
||||
column: groupbyColumnsMap[column_key],
|
||||
visible: isParentGroupVisible,
|
||||
original_cell_value
|
||||
};
|
||||
if (Array.isArray(subgroups) && subgroups.length > 0) {
|
||||
const flattenSubgroups = getGroupsRows(
|
||||
subgroups, groupbyColumnsMap, pathFoldedGroupMap, includeInsertRow, rowHeight, maxLevel,
|
||||
{ parentGroupPath: groupPath, parentGroupKey: groupKey, currentLevel: currentLevel - 1, isParentGroupVisible: isParentGroupVisible && isExpanded }
|
||||
);
|
||||
let groupCount = 0;
|
||||
let subgroupsHeight = 0;
|
||||
let first_row_id;
|
||||
flattenSubgroups.forEach((subgroupContainer) => {
|
||||
if (subgroupContainer.type === GROUP_ROW_TYPE.GROUP_CONTAINER && subgroupContainer.level + 1 === currentLevel) {
|
||||
groupCount += subgroupContainer.count || 0;
|
||||
subgroupsHeight += (subgroupContainer.height || 0) + GROUP_VIEW_OFFSET;
|
||||
if (!first_row_id) {
|
||||
first_row_id = subgroupContainer.first_row_id;
|
||||
}
|
||||
}
|
||||
});
|
||||
groupContainer.first_row_id = first_row_id;
|
||||
groupContainer.count = groupCount;
|
||||
groupContainer.height = (isExpanded ? subgroupsHeight : 0) + GROUP_HEADER_HEIGHT;
|
||||
groupRows.push(groupContainer);
|
||||
groupRows.push(...flattenSubgroups);
|
||||
} else if (Array.isArray(row_ids) && row_ids.length > 0) {
|
||||
const rowsLength = row_ids.length;
|
||||
const lastRowIndex = rowsLength - 1;
|
||||
const isRowVisible = isParentGroupVisible && isExpanded;
|
||||
const isBtnInsertRowVisible = isRowVisible && includeInsertRow;
|
||||
const rowsHeight = isRowVisible ? rowsLength * rowHeight + 1 : 0;
|
||||
const btnInsertRowHeight = isBtnInsertRowVisible ? INSERT_ROW_HEIGHT : 0;
|
||||
let rows = row_ids.map((rowId, index) => {
|
||||
return {
|
||||
type: GROUP_ROW_TYPE.ROW,
|
||||
key: `row-${rowId}`,
|
||||
rowIdx: index,
|
||||
isLastRow: index === lastRowIndex,
|
||||
visible: isRowVisible,
|
||||
height: index === lastRowIndex ? rowHeight + 1 : rowHeight,
|
||||
level: currentLevel,
|
||||
rowsLength,
|
||||
left,
|
||||
rowId,
|
||||
groupPath,
|
||||
groupPathString,
|
||||
};
|
||||
});
|
||||
groupContainer.first_row_id = rows[0].rowId;
|
||||
groupContainer.count = rowsLength;
|
||||
groupContainer.height = rowsHeight + btnInsertRowHeight + GROUP_HEADER_HEIGHT;
|
||||
groupRows.push(groupContainer);
|
||||
groupRows.push(...rows);
|
||||
}
|
||||
});
|
||||
return groupRows;
|
||||
};
|
||||
|
||||
export const setupGroupsRows = (groupRows, maxLevel) => {
|
||||
let groupRowsHeight = GROUP_VIEW_OFFSET;
|
||||
let top = GROUP_VIEW_OFFSET;
|
||||
let idGroupRowMap = {};
|
||||
let pervVisibleGroupLevel;
|
||||
const computedGroupRows = groupRows.map((flattenGroup, index) => {
|
||||
const { type, level, height, visible } = flattenGroup;
|
||||
let newGroupRow = {
|
||||
...flattenGroup,
|
||||
top,
|
||||
groupRecordIndex: index,
|
||||
};
|
||||
if (type === GROUP_ROW_TYPE.GROUP_CONTAINER) {
|
||||
if (visible) {
|
||||
if (level === maxLevel) {
|
||||
groupRowsHeight += height + GROUP_VIEW_OFFSET;
|
||||
}
|
||||
top += GROUP_HEADER_HEIGHT;
|
||||
pervVisibleGroupLevel = level;
|
||||
}
|
||||
} else if (type === GROUP_ROW_TYPE.ROW) {
|
||||
const { rowId } = flattenGroup;
|
||||
idGroupRowMap[rowId] = newGroupRow;
|
||||
if (visible) {
|
||||
top += height;
|
||||
}
|
||||
} else if (type === GROUP_ROW_TYPE.BTN_INSERT_ROW) {
|
||||
if (visible) {
|
||||
top += height;
|
||||
}
|
||||
}
|
||||
const nextFlattenGroup = groupRows[index + 1];
|
||||
if (nextFlattenGroup && nextFlattenGroup.visible && nextFlattenGroup.type === GROUP_ROW_TYPE.GROUP_CONTAINER) {
|
||||
const { groupPath: nextGroupPath, level: nextGroupLevel } = nextFlattenGroup;
|
||||
if (nextGroupPath[nextGroupPath.length - 1] > 0) {
|
||||
top += GROUP_VIEW_OFFSET;
|
||||
}
|
||||
if (nextGroupLevel > pervVisibleGroupLevel) {
|
||||
top += (nextGroupLevel - pervVisibleGroupLevel) * GROUP_VIEW_OFFSET;
|
||||
}
|
||||
}
|
||||
return newGroupRow;
|
||||
});
|
||||
return { computedGroupRows, groupRowsHeight, idGroupRowMap };
|
||||
};
|
||||
|
||||
export const isExpandedGroup = (groupPathString, pathFoldedGroupMap) => {
|
||||
return !pathFoldedGroupMap || !pathFoldedGroupMap[groupPathString];
|
||||
};
|
||||
|
||||
export const isNestedGroupRow = (currentGroupRow, targetGroupRow) => {
|
||||
const { groupPath: currentGroupPath, groupPathString: currentGroupPathString, level: currentGroupLevel, type: currentGroupRowType } = currentGroupRow;
|
||||
const { groupPath: targetGroupPath, groupPathString: targetGroupPathString, level: targetGroupLevel } = targetGroupRow;
|
||||
return (currentGroupPathString === targetGroupPathString && currentGroupRowType !== GROUP_ROW_TYPE.GROUP_CONTAINER) ||
|
||||
(currentGroupLevel < targetGroupLevel && currentGroupPath[0] === targetGroupPath[0]);
|
||||
};
|
||||
|
||||
export const getGroupRecordByIndex = (index, groupMetrics) => {
|
||||
const groupRows = groupMetrics.groupRows || [];
|
||||
return groupRows[index] || {};
|
||||
};
|
35
frontend/src/components/sf-table/utils/group.js
Normal file
35
frontend/src/components/sf-table/utils/group.js
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Get group by paths
|
||||
* @param {array} paths e.g. [ 0, 1, 2 ]
|
||||
* @param {array} groups grouped rows
|
||||
* @returns group, object
|
||||
*/
|
||||
export const getGroupByPath = (paths, groups) => {
|
||||
if (!Array.isArray(paths) || !Array.isArray(groups)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const level0GroupIndex = paths[0];
|
||||
if (level0GroupIndex < 0 || level0GroupIndex >= groups.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let level = 1;
|
||||
let foundGroup = groups[level0GroupIndex];
|
||||
while (level < paths.length) {
|
||||
if (!foundGroup) {
|
||||
break;
|
||||
}
|
||||
const subGroups = foundGroup.subgroups;
|
||||
const currentLevelGroupIndex = paths[level];
|
||||
if (
|
||||
!Array.isArray(subGroups)
|
||||
|| (currentLevelGroupIndex < 0 || currentLevelGroupIndex >= subGroups.length)
|
||||
) {
|
||||
break;
|
||||
}
|
||||
foundGroup = subGroups[currentLevelGroupIndex];
|
||||
level += 1;
|
||||
}
|
||||
return foundGroup;
|
||||
};
|
38
frontend/src/components/sf-table/utils/index.js
Normal file
38
frontend/src/components/sf-table/utils/index.js
Normal file
@@ -0,0 +1,38 @@
|
||||
export const addClassName = (originClassName, targetClassName) => {
|
||||
const originClassNames = originClassName.split(' ');
|
||||
if (originClassNames.indexOf(targetClassName) > -1) return originClassName;
|
||||
return originClassName + ' ' + targetClassName;
|
||||
};
|
||||
|
||||
export const removeClassName = (originClassName, targetClassName) => {
|
||||
let originClassNames = originClassName.split(' ');
|
||||
const targetClassNameIndex = originClassNames.indexOf(targetClassName);
|
||||
if (targetClassNameIndex < 0) return originClassName;
|
||||
originClassNames.splice(targetClassNameIndex, 1);
|
||||
return originClassNames.join(' ');
|
||||
};
|
||||
|
||||
export const getEventClassName = (e) => {
|
||||
// svg mouseEvent event.target.className is an object
|
||||
if (!e || !e.target) return '';
|
||||
return e.target.getAttribute('class') || '';
|
||||
};
|
||||
|
||||
/* is weiXin built-in browser */
|
||||
export const isWeiXinBuiltInBrowser = () => {
|
||||
const agent = navigator.userAgent.toLowerCase();
|
||||
if (agent.match(/MicroMessenger/i) === 'micromessenger' ||
|
||||
(typeof window.WeixinJSBridge !== 'undefined')) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isWindowsBrowser = () => {
|
||||
return /windows|win32/i.test(navigator.userAgent);
|
||||
};
|
||||
|
||||
export const isWebkitBrowser = () => {
|
||||
let agent = navigator.userAgent.toLowerCase();
|
||||
return agent.includes('webkit');
|
||||
};
|
56
frontend/src/components/sf-table/utils/record-metrics.js
Normal file
56
frontend/src/components/sf-table/utils/record-metrics.js
Normal file
@@ -0,0 +1,56 @@
|
||||
function selectRecord(recordId, recordMetrics) {
|
||||
if (isRecordSelected(recordId, recordMetrics)) {
|
||||
return;
|
||||
}
|
||||
recordMetrics.idSelectedRecordMap[recordId] = true;
|
||||
}
|
||||
|
||||
function selectRecordsById(recordIds, recordMetrics) {
|
||||
recordIds.forEach(recordId => {
|
||||
selectRecord(recordId, recordMetrics);
|
||||
});
|
||||
}
|
||||
|
||||
function deselectRecord(recordId, recordMetrics) {
|
||||
if (!isRecordSelected(recordId, recordMetrics)) {
|
||||
return;
|
||||
}
|
||||
delete recordMetrics.idSelectedRecordMap[recordId];
|
||||
}
|
||||
|
||||
function deselectAllRecords(recordMetrics) {
|
||||
recordMetrics.idSelectedRecordMap = {};
|
||||
}
|
||||
|
||||
function isRecordSelected(recordId, recordMetrics) {
|
||||
return recordMetrics.idSelectedRecordMap[recordId];
|
||||
}
|
||||
|
||||
function getSelectedIds(recordMetrics) {
|
||||
return Object.keys(recordMetrics.idSelectedRecordMap);
|
||||
}
|
||||
|
||||
function hasSelectedRecords(recordMetrics) {
|
||||
return getSelectedIds(recordMetrics).length > 0;
|
||||
}
|
||||
|
||||
function isSelectedAll(recordIds, recordMetrics) {
|
||||
const selectedRecordsLen = getSelectedIds(recordMetrics).length;
|
||||
if (selectedRecordsLen === 0) {
|
||||
return false;
|
||||
}
|
||||
return recordIds.every(recordId => isRecordSelected(recordId, recordMetrics));
|
||||
}
|
||||
|
||||
const recordMetrics = {
|
||||
selectRecord,
|
||||
selectRecordsById,
|
||||
deselectRecord,
|
||||
deselectAllRecords,
|
||||
isRecordSelected,
|
||||
getSelectedIds,
|
||||
hasSelectedRecords,
|
||||
isSelectedAll,
|
||||
};
|
||||
|
||||
export default recordMetrics;
|
53
frontend/src/components/sf-table/utils/records-body-utils.js
Normal file
53
frontend/src/components/sf-table/utils/records-body-utils.js
Normal file
@@ -0,0 +1,53 @@
|
||||
import { isMobile } from '../../../utils/utils';
|
||||
import { checkIsColumnFrozen } from './column';
|
||||
|
||||
export const getColumnScrollPosition = (columns, idx, tableContentWidth) => {
|
||||
let left = 0;
|
||||
let frozen = 0;
|
||||
const selectedColumn = getColumn(columns, idx);
|
||||
if (!selectedColumn) return null;
|
||||
|
||||
for (let i = 0; i < idx; i++) {
|
||||
const column = getColumn(columns, i);
|
||||
if (column) {
|
||||
if (column.width) {
|
||||
left += column.width;
|
||||
}
|
||||
if (checkIsColumnFrozen(column)) {
|
||||
frozen += column.width;
|
||||
}
|
||||
}
|
||||
}
|
||||
return isMobile ? left - (tableContentWidth - selectedColumn.width) / 2 : left - frozen;
|
||||
};
|
||||
|
||||
export const getColumn = (columns, idx) => {
|
||||
if (Array.isArray(columns)) {
|
||||
return columns[idx];
|
||||
} else if (typeof Immutable !== 'undefined') {
|
||||
return columns.get(idx);
|
||||
}
|
||||
};
|
||||
|
||||
export const getColVisibleStartIdx = (columns, scrollLeft) => {
|
||||
let remainingScroll = scrollLeft;
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let { width } = columns[i];
|
||||
remainingScroll -= width;
|
||||
if (remainingScroll < 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getColVisibleEndIdx = (columns, recordBodyWidth, scrollLeft) => {
|
||||
let usefulWidth = recordBodyWidth + scrollLeft;
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let { width } = columns[i];
|
||||
usefulWidth -= width;
|
||||
if (usefulWidth < 0) {
|
||||
return i - 1 - 1;
|
||||
}
|
||||
}
|
||||
return columns.length - 1;
|
||||
};
|
196
frontend/src/components/sf-table/utils/selected-cell-utils.js
Normal file
196
frontend/src/components/sf-table/utils/selected-cell-utils.js
Normal file
@@ -0,0 +1,196 @@
|
||||
// import { Utils } from '../../../utils/utils';
|
||||
import { CELL_MASK as Z_INDEX_CELL_MASK, FROZEN_CELL_MASK as Z_INDEX_FROZEN_CELL_MASK } from '../constants/z-index';
|
||||
import { getCellValueByColumn } from './cell';
|
||||
import { checkIsColumnEditable, checkIsColumnSupportPreview, getColumnByIndex } from './column';
|
||||
import { getGroupByPath } from './group';
|
||||
import { getGroupRecordByIndex } from './group-metrics';
|
||||
|
||||
const SELECT_DIRECTION = {
|
||||
UP: 'upwards',
|
||||
DOWN: 'downwards',
|
||||
};
|
||||
|
||||
export const getRowTop = (rowIdx, rowHeight) => rowIdx * rowHeight;
|
||||
|
||||
export const getSelectedRow = ({ selectedPosition, isGroupView, recordGetterByIndex }) => {
|
||||
const { groupRecordIndex, rowIdx } = selectedPosition;
|
||||
return recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
||||
};
|
||||
|
||||
export const getSelectedColumn = ({ selectedPosition, columns }) => {
|
||||
const { idx } = selectedPosition;
|
||||
return getColumnByIndex(idx, columns);
|
||||
};
|
||||
|
||||
export const getSelectedCellValue = ({ selectedPosition, columns, isGroupView, recordGetterByIndex }) => {
|
||||
const column = getSelectedColumn({ selectedPosition, columns });
|
||||
const record = getSelectedRow({ selectedPosition, isGroupView, recordGetterByIndex });
|
||||
return getCellValueByColumn(record, column);
|
||||
};
|
||||
|
||||
export const checkIsCellSupportOpenEditor = (cell, column, isGroupView, recordGetterByIndex, checkCanModifyRecord) => {
|
||||
const { groupRecordIndex, rowIdx } = cell;
|
||||
if (!column) return false;
|
||||
|
||||
// open the editor to preview cell value
|
||||
if (checkIsColumnSupportPreview(column)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!checkIsColumnEditable(column)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex, recordIndex: rowIdx });
|
||||
if (!record || !checkCanModifyRecord) return false;
|
||||
return !!checkCanModifyRecord(record);
|
||||
};
|
||||
|
||||
export const checkIsSelectedCellEditable = ({ enableCellSelect, selectedPosition, columns, isGroupView, recordGetterByIndex, checkCanModifyRecord }) => {
|
||||
const column = getSelectedColumn({ selectedPosition, columns });
|
||||
if (!checkIsColumnEditable(column)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const row = getSelectedRow({ selectedPosition, isGroupView, recordGetterByIndex });
|
||||
if (!row || !checkCanModifyRecord) {
|
||||
return false;
|
||||
}
|
||||
return checkCanModifyRecord(row);
|
||||
};
|
||||
|
||||
export function selectedRangeIsSingleCell(selectedRange) {
|
||||
const { topLeft, bottomRight } = selectedRange;
|
||||
if (
|
||||
topLeft.idx !== bottomRight.idx ||
|
||||
topLeft.rowIdx !== bottomRight.rowIdx
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export const getSelectedDimensions = ({
|
||||
selectedPosition, columns, rowHeight, scrollLeft, isGroupView, groupOffsetLeft,
|
||||
getRecordTopFromRecordsBody,
|
||||
}) => {
|
||||
const { idx, rowIdx, groupRecordIndex } = selectedPosition;
|
||||
const defaultDimensions = { width: 0, left: 0, top: 0, height: rowHeight, zIndex: 1 };
|
||||
if (idx >= 0) {
|
||||
const column = columns && columns[idx];
|
||||
if (!column) {
|
||||
return defaultDimensions;
|
||||
}
|
||||
const { frozen, width } = column;
|
||||
let left = frozen ? scrollLeft + column.left : column.left;
|
||||
let top;
|
||||
if (isGroupView) {
|
||||
left += groupOffsetLeft;
|
||||
// group view uses border-top, No group view uses border-bottom (for group animation) so selected top should be increased 1
|
||||
top = getRecordTopFromRecordsBody(groupRecordIndex) + 1;
|
||||
} else {
|
||||
top = getRecordTopFromRecordsBody(rowIdx);
|
||||
}
|
||||
const zIndex = frozen ? Z_INDEX_FROZEN_CELL_MASK : Z_INDEX_CELL_MASK;
|
||||
return { width, left, top, height: rowHeight, zIndex };
|
||||
}
|
||||
return defaultDimensions;
|
||||
};
|
||||
|
||||
export function getNewSelectedRange(startCell, nextCellPosition) {
|
||||
const { idx: currentIdx, rowIdx: currentRowIdx, groupRecordIndex: currentGroupRecordIndex } = startCell;
|
||||
const { idx: newIdx, rowIdx: newRowIdx, groupRecordIndex: newGroupRecordIndex } = nextCellPosition;
|
||||
const colIndexes = [currentIdx, newIdx].sort((a, b) => a - b);
|
||||
const rowIndexes = [currentRowIdx, newRowIdx].sort((a, b) => a - b);
|
||||
const groupRecordIndexes = [currentGroupRecordIndex, newGroupRecordIndex].sort((a, b) => a - b);
|
||||
const topLeft = { idx: colIndexes[0], rowIdx: rowIndexes[0], groupRecordIndex: groupRecordIndexes[0] };
|
||||
const bottomRight = { idx: colIndexes[1], rowIdx: rowIndexes[1], groupRecordIndex: groupRecordIndexes[1] };
|
||||
return { topLeft, bottomRight };
|
||||
}
|
||||
|
||||
const getColumnRangeProperties = (from, to, columns) => {
|
||||
let totalWidth = 0;
|
||||
let anyColFrozen = false;
|
||||
for (let i = from; i <= to; i++) {
|
||||
const column = columns[i];
|
||||
if (column) {
|
||||
totalWidth += column.width;
|
||||
anyColFrozen = anyColFrozen || column.frozen;
|
||||
}
|
||||
}
|
||||
return { totalWidth, anyColFrozen, left: columns[from].left };
|
||||
};
|
||||
|
||||
export const getSelectedRangeDimensions = ({
|
||||
selectedRange, columns, rowHeight, isGroupView, groups, groupMetrics,
|
||||
groupOffsetLeft, getRecordTopFromRecordsBody,
|
||||
}) => {
|
||||
const { topLeft, bottomRight, startCell, cursorCell } = selectedRange;
|
||||
if (topLeft.idx < 0) {
|
||||
return { width: 0, left: 0, top: 0, height: rowHeight, zIndex: Z_INDEX_CELL_MASK };
|
||||
}
|
||||
|
||||
let { totalWidth, anyColFrozen, left } = getColumnRangeProperties(topLeft.idx, bottomRight.idx, columns);
|
||||
let height;
|
||||
let top;
|
||||
if (isGroupView) {
|
||||
let { groupRecordIndex: startGroupRecordIndex } = startCell;
|
||||
let { groupRecordIndex: endGroupRecordIndex } = cursorCell;
|
||||
const startGroupRow = getGroupRecordByIndex(startGroupRecordIndex, groupMetrics);
|
||||
const endGroupRow = getGroupRecordByIndex(endGroupRecordIndex, groupMetrics);
|
||||
const startGroupPathString = startGroupRow.groupPathString;
|
||||
const endGroupPathString = endGroupRow.groupPathString;
|
||||
let topGroupRowIndex;
|
||||
let selectDirection;
|
||||
if (startGroupRecordIndex < endGroupRecordIndex) {
|
||||
topGroupRowIndex = startGroupRecordIndex;
|
||||
selectDirection = SELECT_DIRECTION.DOWN;
|
||||
} else {
|
||||
topGroupRowIndex = endGroupRecordIndex;
|
||||
selectDirection = SELECT_DIRECTION.UP;
|
||||
}
|
||||
|
||||
if (startGroupPathString === endGroupPathString) {
|
||||
// within the same group.
|
||||
height = (Math.abs(endGroupRecordIndex - startGroupRecordIndex) + 1) * rowHeight;
|
||||
} else if (selectDirection === SELECT_DIRECTION.DOWN) {
|
||||
// within different group: select cells from top to bottom.
|
||||
const groupPath = startGroupRow.groupPath;
|
||||
const group = getGroupByPath(groupPath, groups);
|
||||
const groupRowIds = group.row_ids || [];
|
||||
height = (groupRowIds.length - startGroupRow.rowIdx || 0) * rowHeight;
|
||||
} else if (selectDirection === SELECT_DIRECTION.UP) {
|
||||
// within different group: select cells from bottom to top.
|
||||
const startGroupRowIdx = startGroupRow.rowIdx || 0;
|
||||
topGroupRowIndex = startGroupRecordIndex - startGroupRowIdx;
|
||||
height = (startGroupRowIdx + 1) * rowHeight;
|
||||
}
|
||||
height += 1; // record height: 32
|
||||
left += groupOffsetLeft;
|
||||
top = getRecordTopFromRecordsBody(topGroupRowIndex);
|
||||
} else {
|
||||
height = (bottomRight.rowIdx - topLeft.rowIdx + 1) * rowHeight;
|
||||
top = getRecordTopFromRecordsBody(topLeft.rowIdx);
|
||||
}
|
||||
|
||||
const zIndex = anyColFrozen ? Z_INDEX_FROZEN_CELL_MASK : Z_INDEX_CELL_MASK;
|
||||
return { width: totalWidth, left, top, height, zIndex };
|
||||
};
|
||||
|
||||
export const getRecordsFromSelectedRange = ({ selectedRange, isGroupView, recordGetterByIndex }) => {
|
||||
const { topLeft, bottomRight } = selectedRange;
|
||||
const { rowIdx: startRecordIdx, groupRecordIndex } = topLeft;
|
||||
const { rowIdx: endRecordIdx } = bottomRight;
|
||||
let currentGroupRowIndex = groupRecordIndex;
|
||||
let records = [];
|
||||
for (let recordIndex = startRecordIdx, endIdx = endRecordIdx + 1; recordIndex < endIdx; recordIndex++) {
|
||||
const record = recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupRowIndex, recordIndex });
|
||||
if (isGroupView) {
|
||||
currentGroupRowIndex++;
|
||||
}
|
||||
if (record) {
|
||||
records.push(record);
|
||||
}
|
||||
}
|
||||
return records;
|
||||
};
|
120
frontend/src/components/sf-table/utils/set-event-transfer.js
Normal file
120
frontend/src/components/sf-table/utils/set-event-transfer.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import TRANSFER_TYPES from '../constants/transfer-types';
|
||||
import { getColumnByIndex } from './column';
|
||||
import { toggleSelection } from './toggle-selection';
|
||||
|
||||
const { TEXT, FRAGMENT } = TRANSFER_TYPES;
|
||||
|
||||
function setEventTransfer({
|
||||
type, selectedRecordIds, copiedRange, copiedColumns, copiedRecords, copiedTableId, tableData, copiedText,
|
||||
recordGetterById, isGroupView, recordGetterByIndex, getClientCellValueDisplayString, event = {},
|
||||
}) {
|
||||
const transfer = event.dataTransfer || event.clipboardData;
|
||||
if (type === TRANSFER_TYPES.METADATA_FRAGMENT) {
|
||||
const copiedText = Array.isArray(selectedRecordIds) && selectedRecordIds.length > 0 ?
|
||||
getCopiedTextFormSelectedRecordIds(selectedRecordIds, tableData, recordGetterById, getClientCellValueDisplayString) :
|
||||
getCopiedTextFromSelectedCells(copiedRange, tableData, isGroupView, recordGetterByIndex, getClientCellValueDisplayString);
|
||||
const copiedGrid = {
|
||||
selectedRecordIds,
|
||||
copiedRange,
|
||||
copiedColumns,
|
||||
copiedRecords,
|
||||
copiedTableId,
|
||||
};
|
||||
const serializeCopiedGrid = JSON.stringify(copiedGrid);
|
||||
if (transfer) {
|
||||
transfer.setData(TEXT, copiedText);
|
||||
transfer.setData(FRAGMENT, serializeCopiedGrid);
|
||||
} else {
|
||||
execCopyWithNoEvents(copiedText, serializeCopiedGrid);
|
||||
}
|
||||
} else {
|
||||
let format = TRANSFER_TYPES[type.toUpperCase()];
|
||||
if (transfer) {
|
||||
transfer.setData(format, copiedText);
|
||||
} else {
|
||||
execCopyWithNoEvents(copiedText, { format });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getCopiedTextFormSelectedRecordIds(selectedRecordIds, tableData, recordGetterById, getClientCellValueDisplayString) {
|
||||
const records = selectedRecordIds.map(recordId => recordGetterById(recordId));
|
||||
return getCopiedText(records, tableData.columns, getClientCellValueDisplayString);
|
||||
}
|
||||
|
||||
function getCopiedTextFromSelectedCells(copiedRange, tableData, isGroupView, recordGetterByIndex, getClientCellValueDisplayString) {
|
||||
const { topLeft, bottomRight } = copiedRange;
|
||||
const { rowIdx: minRecordIndex, idx: minColumnIndex, groupRecordIndex } = topLeft;
|
||||
const { rowIdx: maxRecordIndex, idx: maxColumnIndex } = bottomRight;
|
||||
const { columns } = tableData;
|
||||
let currentGroupRecordIndex = groupRecordIndex;
|
||||
let operateRecords = [];
|
||||
let operateColumns = [];
|
||||
for (let i = minRecordIndex; i <= maxRecordIndex; i++) {
|
||||
operateRecords.push(recordGetterByIndex({ isGroupView, groupRecordIndex: currentGroupRecordIndex, recordIndex: i }));
|
||||
if (isGroupView) {
|
||||
currentGroupRecordIndex++;
|
||||
}
|
||||
}
|
||||
for (let i = minColumnIndex; i <= maxColumnIndex; i++) {
|
||||
operateColumns.push(getColumnByIndex(i, columns));
|
||||
}
|
||||
return getCopiedText(operateRecords, operateColumns, getClientCellValueDisplayString);
|
||||
}
|
||||
|
||||
function getCopiedText(records, columns, getClientCellValueDisplayString) {
|
||||
const lastRecordIndex = records.length - 1;
|
||||
const lastColumnIndex = columns.length - 1;
|
||||
let copiedText = '';
|
||||
records.forEach((record, recordIndex) => {
|
||||
columns.forEach((column, columnIndex) => {
|
||||
copiedText += (record && getClientCellValueDisplayString && getClientCellValueDisplayString(record, column)) || '';
|
||||
if (columnIndex < lastColumnIndex) {
|
||||
copiedText += '\t';
|
||||
}
|
||||
});
|
||||
if (recordIndex < lastRecordIndex) {
|
||||
copiedText += '\n';
|
||||
}
|
||||
});
|
||||
return copiedText;
|
||||
}
|
||||
|
||||
export function execCopyWithNoEvents(text, serializeContent) {
|
||||
let reselectPrevious;
|
||||
let range;
|
||||
let selection;
|
||||
let mark;
|
||||
let success = false;
|
||||
try {
|
||||
reselectPrevious = toggleSelection();
|
||||
range = document.createRange();
|
||||
selection = document.getSelection();
|
||||
mark = document.createElement('span');
|
||||
mark.textContent = text;
|
||||
mark.addEventListener('copy', function (e) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
let transfer = e.dataTransfer || e.clipboardData;
|
||||
transfer.clearData();
|
||||
transfer.setData(TEXT, text);
|
||||
transfer.setData(FRAGMENT, serializeContent);
|
||||
});
|
||||
document.body.appendChild(mark);
|
||||
range.selectNodeContents(mark);
|
||||
selection.addRange(range);
|
||||
success = document.execCommand('copy');
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
if (mark) {
|
||||
document.body.removeChild(mark);
|
||||
}
|
||||
reselectPrevious();
|
||||
}
|
||||
}
|
||||
|
||||
export default setEventTransfer;
|
21
frontend/src/components/sf-table/utils/table.js
Normal file
21
frontend/src/components/sf-table/utils/table.js
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Get table row by id
|
||||
* @param {object} table
|
||||
* @param {string} rowId the id of row
|
||||
* @returns row, object
|
||||
*/
|
||||
export const getRowById = (table, rowId) => {
|
||||
if (!table || !table.id_row_map || !rowId) return null;
|
||||
return table.id_row_map[rowId];
|
||||
};
|
||||
|
||||
/**
|
||||
* Get table rows by ids
|
||||
* @param {object} table { id_row_map, ... }
|
||||
* @param {array} rowsIds [ row._id, ... ]
|
||||
* @returns rows, array
|
||||
*/
|
||||
export const getRowsByIds = (table, rowsIds) => {
|
||||
if (!table || !table.id_row_map || !Array.isArray(rowsIds)) return [];
|
||||
return rowsIds.map((rowId) => table.id_row_map[rowId]).filter(Boolean);
|
||||
};
|
34
frontend/src/components/sf-table/utils/toggle-selection.js
Normal file
34
frontend/src/components/sf-table/utils/toggle-selection.js
Normal file
@@ -0,0 +1,34 @@
|
||||
export function toggleSelection() {
|
||||
let selection = document.getSelection();
|
||||
if (!selection.rangeCount) {
|
||||
return function () {};
|
||||
}
|
||||
let active = document.activeElement;
|
||||
let ranges = [];
|
||||
for (let i = 0; i < selection.rangeCount; i++) {
|
||||
ranges.push(selection.getRangeAt(i));
|
||||
}
|
||||
|
||||
switch (active.tagName.toUpperCase()) { // .toUpperCase handles XHTML
|
||||
case 'INPUT':
|
||||
case 'TEXTAREA':
|
||||
active.blur();
|
||||
break;
|
||||
default:
|
||||
active = null;
|
||||
break;
|
||||
}
|
||||
|
||||
selection.removeAllRanges();
|
||||
return function () {
|
||||
selection.type === 'Caret' &&
|
||||
selection.removeAllRanges();
|
||||
if (!selection.rangeCount) {
|
||||
ranges.forEach(function (range) {
|
||||
selection.addRange(range);
|
||||
});
|
||||
}
|
||||
active &&
|
||||
active.focus();
|
||||
};
|
||||
}
|
29
frontend/src/components/sf-table/utils/viewport.js
Normal file
29
frontend/src/components/sf-table/utils/viewport.js
Normal file
@@ -0,0 +1,29 @@
|
||||
export const getColVisibleStartIdx = (columns, scrollLeft) => {
|
||||
let remainingScroll = scrollLeft;
|
||||
const nonFrozenColumns = columns.slice(0);
|
||||
for (let i = 0; i < nonFrozenColumns.length; i++) {
|
||||
let { width } = columns[i];
|
||||
remainingScroll -= width;
|
||||
if (remainingScroll < 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getColVisibleEndIdx = (columns, gridWidth, scrollLeft) => {
|
||||
let remainingWidth = gridWidth + scrollLeft;
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
let { width } = columns[i];
|
||||
remainingWidth -= width;
|
||||
if (remainingWidth < 0) {
|
||||
return i - 1;
|
||||
}
|
||||
}
|
||||
return columns.length - 1;
|
||||
};
|
||||
|
||||
export const getVisibleBoundaries = (columns, scrollLeft, gridWidth) => {
|
||||
const colVisibleStartIdx = getColVisibleStartIdx(columns, scrollLeft);
|
||||
const colVisibleEndIdx = getColVisibleEndIdx(columns, gridWidth, scrollLeft);
|
||||
return { colVisibleStartIdx, colVisibleEndIdx };
|
||||
};
|
Reference in New Issue
Block a user