import { IStoreState, ID, ISelection, ICellNN, ICell } from '../index.data'; import { getCell, getRow, getCellij, getRowi, getRangeByIj, isCellSingleSelected, } from '../store/selectors'; import { treeArray as ta, dissoc } from 'valor-app-utils'; import * as R from 'rambda'; import { IRowWithCell } from '../SpreadSheetProvider/index.data'; import { Store } from 'unistore'; import { SpreadSheetRuntime } from '../RuntimeContext'; import { IMergeCellUndoContext } from './index.data'; import { uuid } from '../store/actions/utils'; /** * 当前选择模式, 是否可以操作行(增,删,升降级) */ export function canModifyRow(state: IStoreState): boolean { const { isActiving, selectionType, selectedCellRange, selectedRowRange } = state; // 如果正在编辑, 不允许改内容 if (isActiving) return false; // 如果没有选择, 不允许改内容 if (!selectionType) return false; // 必须为单元格模式, 必须单选, 否则不允许改内容 if (selectionType === 'cell' && selectedCellRange[0] !== selectedCellRange[1]) return false; // 必须为行模式, 必须单选, 否则不允许改内容 if (selectionType === 'row' && selectedRowRange[0] !== selectedRowRange[1]) return false; return true; } /** * 找到当前行的索引 ( 必须确保canModifyRow ) * 如果不允许(增,删,升降级), 则返回-1 */ export function getCurrentRowIndex(state: IStoreState): number { /* const { selectionType, selectedCellRange, selectedRowRange } = state; if (!canModifyRow(state)) return -1; if (selectionType === null) return -1; return selectionType === 'cell' ? getCellij(state, selectedCellRange[0]).i : getRowi(state, selectedRowRange[0]); */ const result = getCurrentRowIndexes(state); if (!result || result.length <= 0) return -1; return result[0]; } export function getCurrentRowIndexes(state: IStoreState): number[] { const { selectionType, selectedCellRange, selectedRowRange } = state; if (!canModifyRow(state)) return []; if (selectionType === null) return []; return selectionType === 'cell' ? [getCellij(state, selectedCellRange[0]).i, getCellij(state, selectedCellRange[1]).i] : [getRowi(state, selectedRowRange[0]), getRowi(state, selectedRowRange[1])]; } /** * 获取子树, 并拼成 IRowWithCell的样式, 主要供 delete 后恢复 j */ export function getRowWithCells_subtree(state: IStoreState, i: number): IRowWithCell[] { const lastDecendantIndex = ta.getLastDecendantIndex(state.rows, i); return R.range(i, lastDecendantIndex + 1).map(rowIndex => getRowWithCells(state, rowIndex)); } export function getRowWithCells(state: IStoreState, i: number): IRowWithCell { const row = state.rows[i]; const cellsInRow = row .cellIds!.filter(Boolean) .map(cellId => state.cells[cellId!]) .sort((it1, it2) => getCellij(state, it1!.id).j - getCellij(state, it1!.id).j); return { ...dissoc(row, 'cellIds'), cells: cellsInRow } as IRowWithCell; } export function restoreSelection(store: Store, id: ID, runtime: SpreadSheetRuntime) { // 如果当前单元格已被选中, 则直接返回 if (isCellSingleSelected(store.getState(), id!)) return; // 否则选中之 runtime.fsmService.send({ type: 'MOUSE.DOWN', selectedCell: id }); } //////////////////////////////////// merge //////////////////////////////////// // 将选中的大合并单元格, 如 {i:5,j:3,cell1}, {i:5,j:4,null} // {i:6,j:3,null}, {i:6,j:4,null} // 合并成一cell: rowspan=2, colspan=2 export function bigCell_ij2cells( state: IStoreState, cell: ICellNN, ): { i: number; j: number; cell: ICell }[] { const rowspan = cell.rowspan || 1; const colspan = cell.colspan || 1; const newCellss = R.range(cell.i!, cell.i! + rowspan).map(i => R.range(cell.j!, cell.j! + colspan).map(j => { const copied = { ...cell, rowspan: 1, colspan: 1, i, j }; if (i === cell.i && j === cell.j) { return copied; } return { ...copied, id: uuid(), rowId: state.rows[i].id, formula: undefined, formulaDisabled: false, error: null, meta: null, value: cell.dataType === 'number' ? 0 : '', }; }), ); return locateCell(newCellss, cell.i!, cell.j!); } /** * cellId对应rowspan, colspan 均 > 1, 将之拆分 * 修改 cell 本身的 rowspan/colspan, 添加新的cells * 在row.cells里相应修改 */ export function splitCell( state: IStoreState, cellId: ID, runtime: SpreadSheetRuntime, ): { newState: Partial; undoContext: IMergeCellUndoContext; } { const cell = getCell(state, cellId) as ICellNN; const rowspan = cell.rowspan || 1; const colspan = cell.colspan || 1; const oldCellss = R.range(0, rowspan).map(i => R.range(0, colspan).map(j => (i === 0 && j === 0 ? cell : null)), ); const undoContext = locateCell(oldCellss, cell.i!, cell.j!); const splitedCells = bigCell_ij2cells(state, cell); const newState = placeCells(state, splitedCells); return { newState, undoContext, }; } export function placeCells( state: IStoreState, ij2cells: { i: number; j: number; cell: ICell }[], ): IStoreState { return ij2cells.reduce( (acc: IStoreState, { i, j, cell }) => { const row = acc.rows[i]; const oldCellId = row.cellIds![j]; // 从cells中, 先删除老的cell const newCells0 = oldCellId && acc.cells[oldCellId] ? dissoc(acc.cells, oldCellId + '') : acc.cells; const newCells = cell ? Object.assign(newCells0, { [cell.id]: cell }) : oldCellId ? dissoc(newCells0, oldCellId + '') : newCells0; const cellIdsInRow = R.update(j, cell ? cell.id : null, row.cellIds!); const newRow = { ...row, cellIds: cellIdsInRow }; const newRows = R.update(i, newRow, acc.rows); return { ...acc, cells: newCells, rows: newRows }; }, { ...state, rows: [...state.rows], cells: { ...state.cells } }, ); } // 将跨行列的cell: rowspan=2, colspan=2 // 拆分成 {i:5,j:3,cell}, {i:5,j:4,null} // {i:6,j:3,null}, {i:6,j:4,null} // ( 可能存在多个跨行列的单元格, 因此考虑范围即可 ) export function spanedCell_ij2cells( state: IStoreState, cellRange: ISelection['selectedCellRange'], ): { i: number; j: number; cell: ICell }[] { const startIj = getCellij(state, cellRange[0]); const endIj = getCellij(state, cellRange[1]); const cellss = getRangeByIj(state, startIj.i, startIj.j, endIj.i, endIj.j); const mergedCell: ICellNN = { ...cellss[0][0]!, rowspan: cellss.length, colspan: cellss[0].length, }; const newCellss = cellss.map((cells, i) => cells.map((cell, j) => (i === 0 && j === 0 ? mergedCell : null)), ); return locateCell(newCellss, startIj.i, startIj.j!); } export function mergeCells( state: IStoreState, cellRange: ISelection['selectedCellRange'], runtime: SpreadSheetRuntime, ): { newState: Partial; undoContext: IMergeCellUndoContext; } { const oldCellss = getRange(state, cellRange); const oldFirstCellIj = getCellij(state, oldCellss[0][0]!.id); const undoContext = locateCell(oldCellss, oldFirstCellIj.i!, oldFirstCellIj.j!); const ij2cells = spanedCell_ij2cells(state, cellRange); const newState = placeCells(state, ij2cells); return { newState, undoContext, }; } function locateCell(cellss: ICell[][], fromI: number, fromJ: number) { const ij2cellss = cellss.map((cells, i) => cells.map((cell, j) => ({ i: fromI + i, j: fromJ + j, cell, })), ); return (R.flatten(ij2cellss) as any) as { i: number; j: number; cell: ICell }[]; } function getRange(state: IStoreState, cellRange: ISelection['selectedCellRange']): ICell[][] { const startIj = getCellij(state, cellRange[0]); const endIj = getCellij(state, cellRange[1]); return getRangeByIj(state, startIj.i, startIj.j, endIj.i, endIj.j); }