import { IStoreState, IRow, IColumn, ICell, EditorUsage, IRowDimensions, IColDimensions, } from '../index.data'; import { IRowWithCell } from './index.data'; import { dissoc, idMap, nextStringId, treeArray as ta } from 'valor-app-utils'; import * as R from 'rambda'; import createStore, { Store } from 'unistore'; import devtools from 'unistore/devtools'; import { CommandManager } from 'valor-unistore-undo'; import { normalizeCellIdMap, normalizeRows } from '../common/helper'; import { recalculate } from '../store/actions/recalculate'; import { getCellij, getPureTreeContext } from '../store/selectors'; import SetCellStyleCommand from '../commands/SetCellStyleCommand'; import SetCellDataCommand from '../commands/SetCellDataCommand'; import SetRowDataCommand from '../commands/SetRowDataCommand'; import InsertTreeRowCommand, { InsertTreeRowCommandParams } from '../commands/InsertTreeRowCommand'; import MoveRowCommand, { MoveRowCommandParams } from '../commands/MoveRowCommand'; import PushRowCommand, { PushRowCommandParams } from '../commands/PushRowCommand'; import DeleteRowCommand, { DeleteRowCommandParams } from '../commands/DeleteRowCommand'; import RecalculateCommand from '../commands/RecalculateCommand'; import ClearSelectionCommand from '../commands/ClearSelectionCommand'; import SelectCellCommand, { SelectCellCommandParams } from '../commands/SelectCellCommand'; import InsertRowCommand, { InsertRowCommandParams } from '../commands/InsertRowCommand'; import { SpreadSheetRuntime } from '../RuntimeContext'; import { FormulaManagerConfig } from '../formula-manager/index.data'; import MergeCellCommand, { MergeCellCommandParams } from '../commands/MergeCellCommand'; import { labelFormula2IdFormula } from '../formula-manager/PositionedFormulaManager/helper'; import InsertColumnCommand, { InsertColumnCommandParams } from '../commands/InsertColumnCommand'; import DeleteColumnCommand, { DeleteColumnCommandParams } from '../commands/DeleteColumnCommand'; import { getValue } from '../utils'; export const defaultStoreState: IStoreState = { id: 'un_initialised', rows: [], columns: [], cells: {}, treeContext: {}, collapsedRowIds: [], // bodyCellNamePolicy: { // rowNameJ: 1, // colNames: {}, // }, usage: 'editor', mode: 'idle', selectionType: null, selectedRowRange: [] as any, selectedCellRange: [] as any, selectedColumnRange: [] as any, isActiving: false, cellDeps: {}, varDeps: {}, variables: {}, highlightedCells: [], freezeAt: { i: -1, j: -1 }, scrollX: 0, scrollY: 0, rowDimensions: {}, colDimensions: {}, containerBox: { w: 0, h: 0 }, padding: { bottom: 500, right: 0 }, }; export function validate(sheet: { rows: IRowWithCell[]; columns: IColumn[] }) { if (sheet.rows.find(row => row.area == 'tree' && (R.isNil(row.level) || row.level! <= 0))) { throw new Error( 'tree area中, 所有row都需要有level, 并且level从1开始(隐藏的根结点的level是0, id是-1)', ); } const lens = sheet.rows.map(row => row.cells && row.cells.length); if (lens.find(len => len !== lens[0])) { throw new Error('rows里的每个row, 长度应相等'); } if (sheet.columns.length !== lens[0]) { throw new Error('columns长度应当与row长度匹配'); } } /** * 将rows+columns, 改造成state格式 * 用途: 例如在跨表引用时, 为获取 sheet1!A1, 外部需要自己将id转成 A1 * 使用这个方法后, 可再使用其它的内部方法完成这个转换 */ export function makeStoreState(sheet: { rows: IRowWithCell[]; columns: IColumn[] }): IStoreState { const treeContext = getPureTreeContext( ({ rows: sheet.rows } as any) as IStoreState, ta.getTreeContexts(sheet.rows), ); // 填充 treeContext 到 cell(当cell.type === 'row-no') // 填充columns const columns = sheet.columns.map((col, j) => ({ ...col, id: j, j })); // 填充cellIds const cellIdMap = normalizeCellIdMap(sheet); // 填充rows const rows: IRow[] = normalizeRows(sheet); const cells = R.map( cell => ({ ...cell, value: getValue(cell!.value, cell!.dataType) }), cellIdMap, ) as any; // 填充懒加载选项 const rowDimensions = rows.reduce( ({ dims, lastRowBottom }, row, i) => { const height = row.height || 30; const newDims = Object.assign({}, dims, { [row.id]: { height, top: lastRowBottom, offsetHeight: height, offsetTop: lastRowBottom, }, }); return { dims: newDims, lastRowBottom: lastRowBottom + height }; }, { dims: {} as IRowDimensions, lastRowBottom: 0 }, ).dims; const colDimensions = columns.reduce( ({ dims, lastColRight }, col, j) => { const width = col.width || 30; const newDims = Object.assign({}, dims, { [col.id]: { width, left: lastColRight, offsetWidth: width, offsetLeft: lastColRight, }, }); return { dims: newDims, lastColRight: lastColRight + width }; }, { dims: {} as IColDimensions, lastColRight: 0 }, ).dims; return { // id: nextStringId('store'), ...defaultStoreState, rows, columns, treeContext, cells, ...dissoc(sheet, ['rows', 'columns']), rowDimensions, colDimensions, }; } export function initStoreState( sheet: { rows: IRowWithCell[]; columns: IColumn[] }, env?: 'test', usage?: EditorUsage, isLabelFormula?: 'label' | 'id', formulaConfig?: FormulaManagerConfig, freezeAt?: IStoreState['freezeAt'], padding?: IStoreState['padding'], ): IStoreState { // 初步组合 ( 未计算 ) const stateUnCalculated = isLabelFormula === 'label' ? translateLabelFormula(makeStoreState(sheet)) : makeStoreState(sheet); let result = { env, ...stateUnCalculated, ...recalculate(stateUnCalculated, { force: true, config: formulaConfig }), usage: usage || 'editor', }; result = freezeAt ? Object.assign(result, { freezeAt }) : result; result = padding ? Object.assign(result, { padding }) : result; return result; } export function initStore(props: { env?: 'test'; usage: EditorUsage; rows: IRowWithCell[]; columns: IColumn[]; isLabelFormula?: 'label' | 'id'; formulaConfig?: FormulaManagerConfig; freezeAt?: IStoreState['freezeAt']; padding?: IStoreState['padding']; }): Store { const { rows, columns } = props; const storeState: IStoreState = Object.assign( initStoreState( { rows, columns }, props.env, props.usage, props.isLabelFormula, props.formulaConfig, props.freezeAt, props.padding, ), { //临时 bodyCellNamePolicy: props.formulaConfig && (props.formulaConfig as any).bodyCellNamePolicy, }, ); const store = createStore(storeState); return process.env.NODE_ENV === 'production' ? store : devtools(store); } function withDefaultHelper(args: { helpers?: { normalizeRow: (rows: IRow[], row: IRowWithCell) => IRowWithCell }; }) { return args.helpers ? args : { ...args, helpers: { normalizeRow: (rows: IRow[], row: IRowWithCell) => row } }; } export function getQueries(runtime: SpreadSheetRuntime) { const store = runtime.store; return { getSelection() { const state = store.getState(); return { selectionType: state.selectionType, selectionRowRange: state.selectedRowRange, selectedColumnRange: state.selectedColumnRange, selectedCellRange: state.selectedCellRange, }; }, recalculateNeeded() { return runtime.disableAutoCalculate; }, }; } export function getCommands(manager: CommandManager) { return { setCellStyle({ style }: { style: React.CSSProperties }) { manager.execute(SetCellStyleCommand, { style }); }, setCellData(cellPatch: Partial) { manager.execute(SetCellDataCommand, cellPatch); }, setRowData(rowPatch: Partial) { manager.execute(SetRowDataCommand, rowPatch); }, insertTreeRow(params: InsertTreeRowCommandParams) { manager.execute(InsertTreeRowCommand, withDefaultHelper(params)); }, insertRow(params: InsertRowCommandParams) { manager.execute(InsertRowCommand, withDefaultHelper(params)); }, insertColumn(params: InsertColumnCommandParams) { manager.execute(InsertColumnCommand, params); }, deleteColumn(params: DeleteColumnCommandParams) { manager.execute(DeleteColumnCommand, params); }, pushRow(params: PushRowCommandParams) { manager.execute(PushRowCommand, withDefaultHelper(params)); }, moveRow(params: MoveRowCommandParams) { manager.execute(MoveRowCommand, params); }, deleteRow(params: DeleteRowCommandParams) { manager.execute(DeleteRowCommand, withDefaultHelper(params)); }, mergeCell(params: MergeCellCommandParams) { manager.execute(MergeCellCommand, params); }, recalculate() { manager.execute(RecalculateCommand); }, clearSelection() { manager.execute(ClearSelectionCommand); }, selectCell(params: SelectCellCommandParams) { manager.execute(SelectCellCommand, params); }, undo() { manager.undo(); }, redo() { manager.redo(); }, }; } /** * 将 formula="A1+K!A2" => formula="{{id11}}+{{id1!12}}" */ export function translateLabelFormula( state: IStoreState, config?: FormulaManagerConfig, ): IStoreState { var cells = R.map(cell => { if (cell.formula) { cell.formula = labelFormula2IdFormula(state, cell.formula, config); } return cell; }, state.cells); return { ...state, cells }; }