mirror of
https://github.com/dream-num/univer.git
synced 2026-09-24 15:32:18 +08:00
fix(sheets-data-validation): data-validation ref-range behavior (#4015)
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
|
||||
import type { Nullable } from './types';
|
||||
import { AbsoluteRefType, type IRange, type IRectLTRB, RANGE_TYPE } from '../sheets/typedef';
|
||||
import { mergeRanges, multiSubtractSingleRange } from './range';
|
||||
import { mergeRanges, multiSubtractSingleRange, splitIntoGrid } from './range';
|
||||
|
||||
/**
|
||||
* This class provides a set of methods to calculate `IRange`.
|
||||
@@ -350,6 +350,10 @@ export class Rectangle {
|
||||
return mergeRanges(ranges);
|
||||
}
|
||||
|
||||
static splitIntoGrid(ranges: IRange[]): IRange[] {
|
||||
return splitIntoGrid(ranges);
|
||||
}
|
||||
|
||||
static subtractMulti(ranges1: IRange[], ranges2: IRange[]): IRange[] {
|
||||
if (!ranges2.length) {
|
||||
return ranges1;
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import type { ICommand, IDataValidationRule } from '@univerjs/core';
|
||||
import type { IUpdateRulePayload } from '../../types/interfaces/i-update-rule-payload';
|
||||
import type { DataValidationChangeSource } from '../../models/data-validation-model';
|
||||
import type { IUpdateRulePayload } from '../../types/interfaces/i-update-rule-payload';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { DataValidationModel } from '../../models/data-validation-model';
|
||||
|
||||
export interface IAddDataValidationMutationParams {
|
||||
|
||||
@@ -123,6 +123,11 @@ export class DataValidationModel extends Disposable {
|
||||
Object.assign(rule, getRuleOptions(payload.payload));
|
||||
break;
|
||||
}
|
||||
|
||||
case UpdateRuleType.ALL: {
|
||||
Object.assign(rule, payload.payload);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -18,4 +18,5 @@ export enum UpdateRuleType {
|
||||
SETTING,
|
||||
RANGE,
|
||||
OPTIONS,
|
||||
ALL,
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDataValidationRuleBase, IDataValidationRuleOptions } from '@univerjs/core';
|
||||
import type { IDataValidationRule, IDataValidationRuleBase, IDataValidationRuleOptions } from '@univerjs/core';
|
||||
import type { UpdateRuleType } from '../enum/update-rule-type';
|
||||
|
||||
export interface IUpdateRuleRangePayload {
|
||||
@@ -32,7 +32,13 @@ export interface IUpdateRuleOptionsPayload {
|
||||
payload: Partial<IDataValidationRuleOptions>;
|
||||
}
|
||||
|
||||
export interface IUpdateRuleAllPayload {
|
||||
type: UpdateRuleType.ALL;
|
||||
payload: Omit<IDataValidationRule, 'uid'>;
|
||||
}
|
||||
|
||||
export type IUpdateRulePayload =
|
||||
IUpdateRuleRangePayload
|
||||
| IUpdateRuleSettingPayload
|
||||
| IUpdateRuleOptionsPayload;
|
||||
| IUpdateRuleOptionsPayload
|
||||
| IUpdateRuleAllPayload;
|
||||
|
||||
@@ -126,7 +126,7 @@ export abstract class BaseDataValidator<DataType = CellValue> {
|
||||
return false;
|
||||
}
|
||||
|
||||
abstract parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise<IFormulaResult>;
|
||||
abstract parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string, row: number, column: number): Promise<IFormulaResult>;
|
||||
|
||||
abstract validatorFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): IFormulaValidResult;
|
||||
|
||||
@@ -185,7 +185,7 @@ export abstract class BaseDataValidator<DataType = CellValue> {
|
||||
return allowBlank;
|
||||
}
|
||||
|
||||
const formulaInfo = await this.parseFormula(rule, unitId, subUnitId);
|
||||
const formulaInfo = await this.parseFormula(rule, unitId, subUnitId, cellInfo.row, cellInfo.column);
|
||||
|
||||
if (!formulaInfo.isFormulaValid) {
|
||||
return false;
|
||||
|
||||
+1
-1
@@ -78,6 +78,7 @@ const ColorSelect = (props: IColorSelectProps) => {
|
||||
open={open}
|
||||
onDropdownVisibleChange={setOpen}
|
||||
dropdownStyle={{ width: 112 }}
|
||||
style={{ width: 96, cursor: 'pointer' }}
|
||||
className={styles.dataValidationFormulaColorSelect}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
@@ -128,7 +129,6 @@ const Template = (props: { item: IDropdownItem; commonProps: any; style?: React.
|
||||
onChange={(color) => {
|
||||
onItemChange(item.id, item.label, color);
|
||||
}}
|
||||
|
||||
/>
|
||||
<Input
|
||||
disabled={item.isRef}
|
||||
|
||||
@@ -75,16 +75,7 @@ export const DataValidationItem = (props: IDataValidationDetailProps) => {
|
||||
stroke: '#49B811',
|
||||
widgets: {},
|
||||
},
|
||||
primary: {
|
||||
startColumn: range.startColumn,
|
||||
endColumn: range.endColumn,
|
||||
startRow: range.startRow,
|
||||
endRow: range.endRow,
|
||||
actualRow: range.startRow,
|
||||
actualColumn: range.startColumn,
|
||||
isMerged: false,
|
||||
isMergedMainCell: false,
|
||||
},
|
||||
primary: null,
|
||||
}));
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
|
||||
@@ -18,11 +18,12 @@ import type { CellValue, IAccessor, ICellData, ICommand, IDataValidationRuleBase
|
||||
import type { DataValidationChangeSource, IAddDataValidationMutationParams, IRemoveDataValidationMutationParams, IUpdateDataValidationMutationParams } from '@univerjs/data-validation';
|
||||
import type { ISetRangeValuesMutationParams, ISheetCommandSharedParams } from '@univerjs/sheets';
|
||||
import type { RangeMutation } from '../../models/rule-matrix';
|
||||
import { CommandType, DataValidationType, ICommandService, IUndoRedoService, IUniverInstanceService, ObjectMatrix, Range, sequenceExecute, Tools } from '@univerjs/core';
|
||||
import { CommandType, DataValidationType, ICommandService, isFormulaString, IUndoRedoService, IUniverInstanceService, ObjectMatrix, Range, sequenceExecute, Tools } from '@univerjs/core';
|
||||
import { AddDataValidationMutation, DataValidatorRegistryService, getRuleOptions, getRuleSetting, RemoveDataValidationMutation, UpdateDataValidationMutation, UpdateRuleType } from '@univerjs/data-validation';
|
||||
import { LexerTreeBuilder } from '@univerjs/engine-formula';
|
||||
import { getSheetCommandTarget, SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets';
|
||||
import { SheetDataValidationModel } from '../../models/sheet-data-validation-model';
|
||||
import { isCustomFormulaType } from '../../utils/formula';
|
||||
import { getStringCellValue } from '../../utils/get-cell-data-origin';
|
||||
import { CHECKBOX_FORMULA_1, CHECKBOX_FORMULA_2, type CheckboxValidator } from '../../validators';
|
||||
|
||||
@@ -113,44 +114,17 @@ export function getDataValidationDiffMutations(
|
||||
});
|
||||
break;
|
||||
case 'update': {
|
||||
redoMutations.push({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: diff.ruleId,
|
||||
payload: {
|
||||
type: UpdateRuleType.RANGE,
|
||||
payload: diff.newRanges,
|
||||
},
|
||||
source,
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
});
|
||||
|
||||
undoMutations.unshift({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: diff.ruleId,
|
||||
payload: {
|
||||
type: UpdateRuleType.RANGE,
|
||||
payload: diff.oldRanges,
|
||||
},
|
||||
source,
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
});
|
||||
|
||||
if (diff.rule.type === DataValidationType.CUSTOM) {
|
||||
if (isCustomFormulaType(diff.rule.type)) {
|
||||
const originRow = diff.oldRanges[0].startRow;
|
||||
const originColumn = diff.oldRanges[0].startColumn;
|
||||
const newRow = diff.newRanges[0].startRow;
|
||||
const newColumn = diff.newRanges[0].startColumn;
|
||||
const rowDiff = newRow - originRow;
|
||||
const columnDiff = newColumn - originColumn;
|
||||
const newFormula = lexerTreeBuilder.moveFormulaRefOffset(diff.rule.formula1!, columnDiff, rowDiff);
|
||||
const newFormula = isFormulaString(diff.rule.formula1!) ? lexerTreeBuilder.moveFormulaRefOffset(diff.rule.formula1!, columnDiff, rowDiff) : diff.rule.formula1;
|
||||
const newFormula2 = isFormulaString(diff.rule.formula2!) ? lexerTreeBuilder.moveFormulaRefOffset(diff.rule.formula2!, columnDiff, rowDiff) : diff.rule.formula2;
|
||||
|
||||
if (newFormula !== diff.rule.formula1) {
|
||||
if (newFormula !== diff.rule.formula1 || newFormula2 !== diff.rule.formula2) {
|
||||
redoMutations.push({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
@@ -158,10 +132,11 @@ export function getDataValidationDiffMutations(
|
||||
subUnitId,
|
||||
ruleId: diff.ruleId,
|
||||
payload: {
|
||||
type: UpdateRuleType.SETTING,
|
||||
type: UpdateRuleType.ALL,
|
||||
payload: {
|
||||
...getRuleSetting(diff.rule),
|
||||
formula1: newFormula,
|
||||
formula2: newFormula2,
|
||||
ranges: diff.newRanges,
|
||||
},
|
||||
},
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
@@ -174,12 +149,44 @@ export function getDataValidationDiffMutations(
|
||||
subUnitId,
|
||||
ruleId: diff.ruleId,
|
||||
payload: {
|
||||
type: UpdateRuleType.SETTING,
|
||||
payload: getRuleSetting(diff.rule),
|
||||
type: UpdateRuleType.ALL,
|
||||
payload: {
|
||||
formula1: diff.rule.formula1,
|
||||
formula2: diff.rule.formula2,
|
||||
ranges: diff.oldRanges,
|
||||
},
|
||||
},
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
redoMutations.push({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: diff.ruleId,
|
||||
payload: {
|
||||
type: UpdateRuleType.RANGE,
|
||||
payload: diff.newRanges,
|
||||
},
|
||||
source,
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
});
|
||||
|
||||
undoMutations.unshift({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: diff.ruleId,
|
||||
payload: {
|
||||
type: UpdateRuleType.RANGE,
|
||||
payload: diff.oldRanges,
|
||||
},
|
||||
source,
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
});
|
||||
}
|
||||
|
||||
const rule = sheetDataValidationModel.getRuleById(unitId, subUnitId, diff.ruleId);
|
||||
@@ -296,11 +303,15 @@ export const AddSheetDataValidationCommand: ICommand<IAddSheetDataValidationComm
|
||||
const matrix = sheetDataValidationModel.getRuleObjectMatrix(unitId, subUnitId).clone();
|
||||
matrix.addRule(rule);
|
||||
const diffs = matrix.diff(sheetDataValidationModel.getRules(unitId, subUnitId));
|
||||
const validator = sheetDataValidationModel.getValidator(rule.type);
|
||||
|
||||
const mutationParams: IAddDataValidationMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
rule,
|
||||
rule: {
|
||||
...rule,
|
||||
...validator?.normalizeFormula(rule, unitId, subUnitId),
|
||||
},
|
||||
};
|
||||
|
||||
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs, accessor);
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDisposable, IMutationInfo, ISheetDataValidationRule } from '@univerjs/core';
|
||||
import type { IUpdateDataValidationMutationParams } from '@univerjs/data-validation';
|
||||
import { Disposable, generateRandomId, Inject, toDisposable } from '@univerjs/core';
|
||||
import { AddDataValidationMutation, RemoveDataValidationMutation, UpdateDataValidationMutation, UpdateRuleType } from '@univerjs/data-validation';
|
||||
import { LexerTreeBuilder } from '@univerjs/engine-formula';
|
||||
import { FormulaRefRangeService } from '@univerjs/sheets-formula';
|
||||
import { SheetDataValidationModel } from '../models/sheet-data-validation-model';
|
||||
import { isCustomFormulaType } from '../utils/formula';
|
||||
|
||||
export class DataValidationFormulaRefRangeController extends Disposable {
|
||||
private _disposableMap: Map<string, IDisposable> = new Map();
|
||||
|
||||
constructor(
|
||||
@Inject(SheetDataValidationModel) private _dataValidationModel: SheetDataValidationModel,
|
||||
@Inject(FormulaRefRangeService) private _formulaRefRangeService: FormulaRefRangeService,
|
||||
@Inject(LexerTreeBuilder) private _lexerTreeBuilder: LexerTreeBuilder
|
||||
) {
|
||||
super();
|
||||
this._initRefRange();
|
||||
}
|
||||
|
||||
private _getIdWithUnitId(unitID: string, subUnitId: string, ruleId: string) {
|
||||
return `${unitID}_${subUnitId}_${ruleId}`;
|
||||
}
|
||||
|
||||
registerRule = (unitId: string, subUnitId: string, rule: ISheetDataValidationRule) => {
|
||||
if (!isCustomFormulaType(rule.type)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.register(unitId, subUnitId, rule);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
register(unitId: string, subUnitId: string, rule: ISheetDataValidationRule) {
|
||||
const oldRanges = rule.ranges;
|
||||
const oldFormula1 = rule.formula1;
|
||||
const oldFormula2 = rule.formula2;
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
const disposable = this._formulaRefRangeService.registerRangeFormula(unitId, subUnitId, oldRanges, [oldFormula1 ?? '', oldFormula2 ?? ''], (res) => {
|
||||
if (res.length === 0) {
|
||||
return {
|
||||
undos: [{
|
||||
id: AddDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
rule,
|
||||
},
|
||||
}],
|
||||
redos: [{
|
||||
id: RemoveDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: rule.uid,
|
||||
},
|
||||
}],
|
||||
};
|
||||
}
|
||||
const redos: IMutationInfo[] = [];
|
||||
const undos: IMutationInfo[] = [];
|
||||
const first = res[0];
|
||||
redos.push({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: rule.uid,
|
||||
payload: {
|
||||
type: UpdateRuleType.ALL,
|
||||
payload: {
|
||||
ranges: first.ranges,
|
||||
formula1: first.formulas[0],
|
||||
formula2: first.formulas[1],
|
||||
},
|
||||
},
|
||||
} as IUpdateDataValidationMutationParams,
|
||||
});
|
||||
undos.push({
|
||||
id: UpdateDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: rule.uid,
|
||||
payload: {
|
||||
type: UpdateRuleType.ALL,
|
||||
payload: {
|
||||
ranges: oldRanges,
|
||||
formula1: oldFormula1,
|
||||
formula2: oldFormula2,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
for (let i = 1; i < res.length; i++) {
|
||||
const item = res[i];
|
||||
const id = generateRandomId();
|
||||
redos.push({
|
||||
id: AddDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
rule: {
|
||||
...rule,
|
||||
uid: id,
|
||||
formula1: item.formulas[0],
|
||||
formula2: item.formulas[1],
|
||||
ranges: item.ranges,
|
||||
},
|
||||
},
|
||||
});
|
||||
undos.push({
|
||||
id: RemoveDataValidationMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ruleId: id,
|
||||
},
|
||||
});
|
||||
}
|
||||
return {
|
||||
undos,
|
||||
redos,
|
||||
};
|
||||
});
|
||||
const id = this._getIdWithUnitId(unitId, subUnitId, rule.uid);
|
||||
this._disposableMap.set(id, disposable);
|
||||
};
|
||||
|
||||
private _initRefRange() {
|
||||
const allRules = this._dataValidationModel.getAll();
|
||||
for (const [unitId, subUnitMap] of allRules) {
|
||||
for (const [subUnitId, rules] of subUnitMap) {
|
||||
for (const rule of rules) {
|
||||
this.registerRule(unitId, subUnitId, rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.disposeWithMe(
|
||||
this._dataValidationModel.ruleChange$.subscribe((option) => {
|
||||
const { unitId, subUnitId, rule } = option;
|
||||
switch (option.type) {
|
||||
case 'add': {
|
||||
const rule = option.rule!;
|
||||
this.registerRule(option.unitId, option.subUnitId, rule);
|
||||
break;
|
||||
}
|
||||
case 'remove': {
|
||||
const disposeSet = this._disposableMap.get(this._getIdWithUnitId(unitId, subUnitId, rule!.uid));
|
||||
if (disposeSet) {
|
||||
disposeSet.dispose();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'update': {
|
||||
const rule = option.rule!;
|
||||
const disposeSet = this._disposableMap.get(this._getIdWithUnitId(unitId, subUnitId, rule!.uid));
|
||||
if (disposeSet) {
|
||||
disposeSet.dispose();
|
||||
}
|
||||
this.registerRule(option.unitId, option.subUnitId, rule);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.disposeWithMe(toDisposable(() => {
|
||||
this._disposableMap.forEach((item) => {
|
||||
item.dispose();
|
||||
});
|
||||
this._disposableMap.clear();
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { ISheetDataValidationRule } from '@univerjs/core';
|
||||
import type { IRemoveDataValidationMutationParams, IUpdateDataValidationMutationParams } from '@univerjs/data-validation';
|
||||
import type { EffectRefRangeParams } from '@univerjs/sheets';
|
||||
import { DataValidationType, Disposable, Inject, Injector, isRangesEqual, toDisposable } from '@univerjs/core';
|
||||
import { Disposable, Inject, Injector, isRangesEqual, toDisposable } from '@univerjs/core';
|
||||
import { RemoveDataValidationMutation, UpdateDataValidationMutation, UpdateRuleType } from '@univerjs/data-validation';
|
||||
import { handleCommonDefaultRangeChangeWithEffectRefCommands, RefRangeService } from '@univerjs/sheets';
|
||||
import { FormulaRefRangeService } from '@univerjs/sheets-formula';
|
||||
@@ -25,6 +25,7 @@ import { removeDataValidationUndoFactory } from '../commands/commands/data-valid
|
||||
import { SheetDataValidationModel } from '../models/sheet-data-validation-model';
|
||||
import { DataValidationCustomFormulaService } from '../services/dv-custom-formula.service';
|
||||
import { DataValidationFormulaService } from '../services/dv-formula.service';
|
||||
import { isCustomFormulaType } from '../utils/formula';
|
||||
|
||||
export class DataValidationRefRangeController extends Disposable {
|
||||
private _disposableMap: Map<string, Set<() => void>> = new Map();
|
||||
@@ -46,11 +47,13 @@ export class DataValidationRefRangeController extends Disposable {
|
||||
}
|
||||
|
||||
registerRule = (unitId: string, subUnitId: string, rule: ISheetDataValidationRule) => {
|
||||
if (isCustomFormulaType(rule.type)) {
|
||||
return;
|
||||
}
|
||||
this.register(unitId, subUnitId, rule);
|
||||
this.registerFormula(unitId, subUnitId, rule);
|
||||
};
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
registerFormula(unitId: string, subUnitId: string, rule: ISheetDataValidationRule) {
|
||||
const ruleId = rule.uid;
|
||||
const id = this._getIdWithUnitId(unitId, subUnitId, ruleId);
|
||||
@@ -106,41 +109,27 @@ export class DataValidationRefRangeController extends Disposable {
|
||||
return { redos, undos };
|
||||
};
|
||||
|
||||
if (rule.type === DataValidationType.CUSTOM) {
|
||||
const currentFormula = this._dataValidationCustomFormulaService.getRuleFormulaInfo(unitId, subUnitId, ruleId);
|
||||
if (currentFormula) {
|
||||
const currentFormula = this._dataValidationFormulaService.getRuleFormulaInfo(unitId, subUnitId, ruleId);
|
||||
if (currentFormula) {
|
||||
const [formula1, formula2] = currentFormula;
|
||||
if (formula1) {
|
||||
const disposable = this._formulaRefRangeService.registerFormula(
|
||||
unitId,
|
||||
subUnitId,
|
||||
currentFormula.formula,
|
||||
formula1.text,
|
||||
(newFormulaString) => handleFormulaChange('formula1', newFormulaString)
|
||||
);
|
||||
disposeSet.add(() => disposable.dispose());
|
||||
}
|
||||
} else {
|
||||
const currentFormula = this._dataValidationFormulaService.getRuleFormulaInfo(unitId, subUnitId, ruleId);
|
||||
|
||||
if (currentFormula) {
|
||||
const [formula1, formula2] = currentFormula;
|
||||
if (formula1) {
|
||||
const disposable = this._formulaRefRangeService.registerFormula(
|
||||
unitId,
|
||||
subUnitId,
|
||||
formula1.text,
|
||||
(newFormulaString) => handleFormulaChange('formula1', newFormulaString)
|
||||
);
|
||||
disposeSet.add(() => disposable.dispose());
|
||||
}
|
||||
|
||||
if (formula2) {
|
||||
const disposable = this._formulaRefRangeService.registerFormula(
|
||||
unitId,
|
||||
subUnitId,
|
||||
formula2.text,
|
||||
(newFormulaString) => handleFormulaChange('formula2', newFormulaString)
|
||||
);
|
||||
disposeSet.add(() => disposable.dispose());
|
||||
}
|
||||
if (formula2) {
|
||||
const disposable = this._formulaRefRangeService.registerFormula(
|
||||
unitId,
|
||||
subUnitId,
|
||||
formula2.text,
|
||||
(newFormulaString) => handleFormulaChange('formula2', newFormulaString)
|
||||
);
|
||||
disposeSet.add(() => disposable.dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ISheetDataValidationRule } from '@univerjs/core';
|
||||
import type { DataValidationType, ISheetDataValidationRule } from '@univerjs/core';
|
||||
import type { IRuleChange, IUpdateRulePayload } from '@univerjs/data-validation';
|
||||
import type { IRemoveSheetMutationParams, ISheetLocation } from '@univerjs/sheets';
|
||||
import { DataValidationStatus, DataValidationType, Disposable, ICommandService, Inject, IUniverInstanceService } from '@univerjs/core';
|
||||
import { DataValidationStatus, Disposable, ICommandService, Inject, IUniverInstanceService } from '@univerjs/core';
|
||||
import { DataValidationModel, DataValidatorRegistryService, UpdateRuleType } from '@univerjs/data-validation';
|
||||
import { RemoveSheetMutation } from '@univerjs/sheets';
|
||||
import { Subject } from 'rxjs';
|
||||
@@ -142,10 +142,8 @@ export class SheetDataValidationModel extends Disposable {
|
||||
const ruleMatrix = this._ensureRuleMatrix(unitId, subUnitId);
|
||||
ruleMatrix.addRule(rule);
|
||||
this._dataValidationCacheService.addRule(unitId, subUnitId, rule);
|
||||
this._dataValidationFormulaService.addRule(unitId, subUnitId, rule.uid, rule.formula1, rule.formula2, rule.ranges);
|
||||
if (rule.type === DataValidationType.CUSTOM) {
|
||||
this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, rule);
|
||||
}
|
||||
this._dataValidationFormulaService.addRule(unitId, subUnitId, rule);
|
||||
this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, rule);
|
||||
}
|
||||
|
||||
private _addRule(unitId: string, subUnitId: string, rule: ISheetDataValidationRule | ISheetDataValidationRule[]): void {
|
||||
@@ -158,41 +156,33 @@ export class SheetDataValidationModel extends Disposable {
|
||||
|
||||
private _updateRule(unitId: string, subUnitId: string, ruleId: string, oldRule: ISheetDataValidationRule, payload: IUpdateRulePayload) {
|
||||
const ruleMatrix = this._ensureRuleMatrix(unitId, subUnitId);
|
||||
const newRule = {
|
||||
...oldRule,
|
||||
...payload.payload,
|
||||
};
|
||||
|
||||
if (payload.type === UpdateRuleType.RANGE) {
|
||||
ruleMatrix.updateRange(ruleId, payload.payload);
|
||||
this._dataValidationCacheService.updateRuleRanges(unitId, subUnitId, ruleId, payload.payload, oldRule.ranges);
|
||||
if (oldRule.type === DataValidationType.CUSTOM) {
|
||||
this._dataValidationCustomFormulaService.deleteByRuleId(unitId, subUnitId, ruleId);
|
||||
this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, {
|
||||
...oldRule,
|
||||
ranges: payload.payload,
|
||||
});
|
||||
}
|
||||
} else if (payload.type === UpdateRuleType.SETTING) {
|
||||
this._dataValidationCacheService.markRangeDirty(unitId, subUnitId, oldRule.ranges);
|
||||
this._dataValidationFormulaService.updateRuleFormulaText(unitId, subUnitId, ruleId, payload.payload.formula1, payload.payload.formula2, oldRule.ranges);
|
||||
if (oldRule.type === DataValidationType.CUSTOM) {
|
||||
this._dataValidationCustomFormulaService.deleteByRuleId(unitId, subUnitId, ruleId);
|
||||
this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, {
|
||||
...oldRule,
|
||||
...payload.payload,
|
||||
});
|
||||
} else if (payload.payload.type === DataValidationType.CUSTOM) {
|
||||
this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, {
|
||||
...oldRule,
|
||||
...payload.payload,
|
||||
});
|
||||
}
|
||||
} else if (payload.type === UpdateRuleType.ALL) {
|
||||
ruleMatrix.updateRange(ruleId, payload.payload.ranges);
|
||||
this._dataValidationCacheService.updateRuleRanges(unitId, subUnitId, ruleId, payload.payload.ranges, oldRule.ranges);
|
||||
this._dataValidationCacheService.markRangeDirty(unitId, subUnitId, oldRule.ranges);
|
||||
}
|
||||
|
||||
this._dataValidationFormulaService.removeRule(unitId, subUnitId, oldRule.uid);
|
||||
this._dataValidationFormulaService.addRule(unitId, subUnitId, newRule);
|
||||
this._dataValidationCustomFormulaService.deleteByRuleId(unitId, subUnitId, ruleId);
|
||||
this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, newRule);
|
||||
}
|
||||
|
||||
private _removeRule(unitId: string, subUnitId: string, oldRule: ISheetDataValidationRule): void {
|
||||
const ruleMatrix = this._ensureRuleMatrix(unitId, subUnitId);
|
||||
ruleMatrix.removeRule(oldRule);
|
||||
this._dataValidationCacheService.removeRule(unitId, subUnitId, oldRule);
|
||||
if (oldRule.type === DataValidationType.CUSTOM) {
|
||||
this._dataValidationCustomFormulaService.deleteByRuleId(unitId, subUnitId, oldRule.uid);
|
||||
}
|
||||
this._dataValidationCustomFormulaService.deleteByRuleId(unitId, subUnitId, oldRule.uid);
|
||||
}
|
||||
|
||||
getValidator(type: DataValidationType) {
|
||||
|
||||
@@ -37,6 +37,7 @@ import { DATA_VALIDATION_PLUGIN_NAME } from './common/const';
|
||||
import { defaultPluginConfig, PLUGIN_CONFIG_KEY } from './controllers/config.schema';
|
||||
import { DataValidationController } from './controllers/dv.controller';
|
||||
import { DataValidationFormulaController } from './controllers/dv-formula.controller';
|
||||
import { DataValidationFormulaRefRangeController } from './controllers/dv-formula-ref-range.controller';
|
||||
import { DataValidationRefRangeController } from './controllers/dv-ref-range.controller';
|
||||
import { SheetDataValidationSheetController } from './controllers/dv-sheet.controller';
|
||||
import { SheetDataValidationModel } from './models/sheet-data-validation-model';
|
||||
@@ -71,9 +72,10 @@ export class UniverSheetsDataValidationPlugin extends Plugin {
|
||||
[SheetsDataValidationValidatorService],
|
||||
[SheetDataValidationModel],
|
||||
[DataValidationController],
|
||||
[DataValidationRefRangeController],
|
||||
[DataValidationFormulaController],
|
||||
[SheetDataValidationSheetController],
|
||||
[DataValidationRefRangeController],
|
||||
[DataValidationFormulaRefRangeController],
|
||||
] as Dependency[]).forEach((dep) => {
|
||||
this._injector.add(dep);
|
||||
});
|
||||
@@ -92,6 +94,7 @@ export class UniverSheetsDataValidationPlugin extends Plugin {
|
||||
|
||||
this._injector.get(DataValidationCacheService);
|
||||
this._injector.get(SheetsDataValidationValidatorService);
|
||||
this._injector.get(DataValidationFormulaRefRangeController);
|
||||
this._injector.get(DataValidationRefRangeController);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,17 +15,16 @@
|
||||
*/
|
||||
|
||||
import type { IRange, ISheetDataValidationRule } from '@univerjs/core';
|
||||
import { DataValidationType, Disposable, Inject, isFormulaString, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { Disposable, Inject, isFormulaString, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { DataValidationModel } from '@univerjs/data-validation';
|
||||
import { RegisterOtherFormulaService } from '@univerjs/sheets-formula';
|
||||
import { getFormulaCellData } from '../utils/formula';
|
||||
import { getFormulaCellData, isCustomFormulaType } from '../utils/formula';
|
||||
import { DataValidationCacheService } from './dv-cache.service';
|
||||
|
||||
interface IFormulaData {
|
||||
formula: string;
|
||||
originRow: number;
|
||||
originCol: number;
|
||||
isTransformable: boolean;
|
||||
formulaId: string;
|
||||
}
|
||||
|
||||
@@ -38,6 +37,7 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
* Map of origin formula of rule
|
||||
*/
|
||||
private _ruleFormulaMap: Map<UnitId, Map<SubUnitId, Map<RuleId, IFormulaData>>> = new Map();
|
||||
private _ruleFormulaMap2: Map<UnitId, Map<SubUnitId, Map<RuleId, IFormulaData>>> = new Map();
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService,
|
||||
@@ -65,7 +65,7 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
const ruleInfo = ruleFormulaMap.get(result.extra?.ruleId);
|
||||
const rule = this._dataValidationModel.getRuleById(unitId, subUnitId, result.extra?.ruleId);
|
||||
|
||||
if (rule && ruleInfo && !ruleInfo.isTransformable) {
|
||||
if (rule && ruleInfo) {
|
||||
this._dataValidationCacheService.markRangeDirty(unitId, subUnitId, rule.ranges);
|
||||
}
|
||||
});
|
||||
@@ -76,11 +76,15 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
|
||||
private _ensureMaps(unitId: string, subUnitId: string) {
|
||||
let ruleFormulaUnitMap = this._ruleFormulaMap.get(unitId);
|
||||
|
||||
let ruleFormulaUnitMap2 = this._ruleFormulaMap2.get(unitId);
|
||||
if (!ruleFormulaUnitMap) {
|
||||
ruleFormulaUnitMap = new Map();
|
||||
this._ruleFormulaMap.set(unitId, ruleFormulaUnitMap);
|
||||
}
|
||||
if (!ruleFormulaUnitMap2) {
|
||||
ruleFormulaUnitMap2 = new Map();
|
||||
this._ruleFormulaMap2.set(unitId, ruleFormulaUnitMap2);
|
||||
}
|
||||
|
||||
let ruleFormulaMap = ruleFormulaUnitMap.get(subUnitId);
|
||||
|
||||
@@ -88,7 +92,14 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
ruleFormulaMap = new Map();
|
||||
ruleFormulaUnitMap.set(subUnitId, ruleFormulaMap);
|
||||
}
|
||||
return { ruleFormulaMap };
|
||||
|
||||
let ruleFormulaMap2 = ruleFormulaUnitMap2.get(subUnitId);
|
||||
if (!ruleFormulaMap2) {
|
||||
ruleFormulaMap2 = new Map();
|
||||
ruleFormulaUnitMap2.set(subUnitId, ruleFormulaMap2);
|
||||
}
|
||||
|
||||
return { ruleFormulaMap, ruleFormulaMap2 };
|
||||
};
|
||||
|
||||
private _registerFormula(unitId: string, subUnitId: string, ruleId: string, formulaString: string, ranges: IRange[]) {
|
||||
@@ -96,7 +107,7 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
};
|
||||
|
||||
deleteByRuleId(unitId: string, subUnitId: string, ruleId: string) {
|
||||
const { ruleFormulaMap } = this._ensureMaps(unitId, subUnitId);
|
||||
const { ruleFormulaMap, ruleFormulaMap2 } = this._ensureMaps(unitId, subUnitId);
|
||||
const rule = this._dataValidationModel.getRuleById(unitId, subUnitId, ruleId) as ISheetDataValidationRule;
|
||||
const formulaInfo = ruleFormulaMap.get(ruleId);
|
||||
|
||||
@@ -109,35 +120,46 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
ruleFormulaMap.delete(ruleId);
|
||||
this._registerOtherFormulaService.deleteFormula(unitId, subUnitId, [current.formulaId]);
|
||||
}
|
||||
|
||||
const current2 = ruleFormulaMap2.get(ruleId);
|
||||
if (current2) {
|
||||
ruleFormulaMap2.delete(ruleId);
|
||||
this._registerOtherFormulaService.deleteFormula(unitId, subUnitId, [current2.formulaId]);
|
||||
}
|
||||
}
|
||||
|
||||
private _addFormulaByRange(unitId: string, subUnitId: string, ruleId: string, formula: string, ranges: IRange[]) {
|
||||
const { ruleFormulaMap } = this._ensureMaps(unitId, subUnitId);
|
||||
|
||||
if (!formula) {
|
||||
return;
|
||||
}
|
||||
private _addFormulaByRange(unitId: string, subUnitId: string, ruleId: string, formula: string | undefined, formula2: string | undefined, ranges: IRange[]) {
|
||||
const { ruleFormulaMap, ruleFormulaMap2 } = this._ensureMaps(unitId, subUnitId);
|
||||
|
||||
const originRow = ranges[0].startRow;
|
||||
const originCol = ranges[0].startColumn;
|
||||
|
||||
const formulaId = this._registerFormula(unitId, subUnitId, ruleId, formula, ranges);
|
||||
if (formula && isFormulaString(formula)) {
|
||||
const formulaId = this._registerFormula(unitId, subUnitId, ruleId, formula, ranges);
|
||||
ruleFormulaMap.set(ruleId, {
|
||||
formula,
|
||||
originCol,
|
||||
originRow,
|
||||
formulaId,
|
||||
});
|
||||
}
|
||||
|
||||
ruleFormulaMap.set(ruleId, {
|
||||
formula,
|
||||
originCol,
|
||||
originRow,
|
||||
formulaId,
|
||||
isTransformable: true,
|
||||
});
|
||||
if (formula2 && isFormulaString(formula2)) {
|
||||
const formulaId2 = this._registerFormula(unitId, subUnitId, ruleId, formula2, ranges);
|
||||
ruleFormulaMap2.set(ruleId, {
|
||||
formula: formula2,
|
||||
originCol,
|
||||
originRow,
|
||||
formulaId: formulaId2,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addRule(unitId: string, subUnitId: string, rule: ISheetDataValidationRule) {
|
||||
const { ranges, formula1, uid: ruleId, type } = rule;
|
||||
if (type !== DataValidationType.CUSTOM || !formula1 || !isFormulaString(formula1)) {
|
||||
return;
|
||||
if (isCustomFormulaType(rule.type)) {
|
||||
const { ranges, formula1, formula2, uid: ruleId } = rule;
|
||||
this._addFormulaByRange(unitId, subUnitId, ruleId, formula1, formula2, ranges);
|
||||
}
|
||||
this._addFormulaByRange(unitId, subUnitId, ruleId, formula1, ranges);
|
||||
}
|
||||
|
||||
async getCellFormulaValue(unitId: string, subUnitId: string, ruleId: string, row: number, column: number) {
|
||||
@@ -154,6 +176,48 @@ export class DataValidationCustomFormulaService extends Disposable {
|
||||
return getFormulaCellData(result?.result?.[offsetRow]?.[offsetCol]);
|
||||
}
|
||||
|
||||
async getCellFormula2Value(unitId: string, subUnitId: string, ruleId: string, row: number, column: number) {
|
||||
const { ruleFormulaMap2 } = this._ensureMaps(unitId, subUnitId);
|
||||
const current = ruleFormulaMap2.get(ruleId);
|
||||
if (!current) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const result = await this._registerOtherFormulaService.getFormulaValue(unitId, subUnitId, current.formulaId);
|
||||
const { originRow, originCol } = current;
|
||||
const offsetRow = row - originRow;
|
||||
const offsetCol = column - originCol;
|
||||
return getFormulaCellData(result?.result?.[offsetRow]?.[offsetCol]);
|
||||
}
|
||||
|
||||
getCellFormulaValueSync(unitId: string, subUnitId: string, ruleId: string, row: number, column: number) {
|
||||
const { ruleFormulaMap } = this._ensureMaps(unitId, subUnitId);
|
||||
const current = ruleFormulaMap.get(ruleId);
|
||||
if (!current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = this._registerOtherFormulaService.getFormulaValueSync(unitId, subUnitId, current.formulaId);
|
||||
const { originRow, originCol } = current;
|
||||
const offsetRow = row - originRow;
|
||||
const offsetCol = column - originCol;
|
||||
return getFormulaCellData(result?.result?.[offsetRow]?.[offsetCol]);
|
||||
}
|
||||
|
||||
getCellFormula2ValueSync(unitId: string, subUnitId: string, ruleId: string, row: number, column: number) {
|
||||
const { ruleFormulaMap2 } = this._ensureMaps(unitId, subUnitId);
|
||||
const current = ruleFormulaMap2.get(ruleId);
|
||||
if (!current) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const result = this._registerOtherFormulaService.getFormulaValueSync(unitId, subUnitId, current.formulaId);
|
||||
const { originRow, originCol } = current;
|
||||
const offsetRow = row - originRow;
|
||||
const offsetCol = column - originCol;
|
||||
return getFormulaCellData(result?.result?.[offsetRow]?.[offsetCol]);
|
||||
}
|
||||
|
||||
getRuleFormulaInfo(unitId: string, subUnitId: string, ruleId: string) {
|
||||
const { ruleFormulaMap } = this._ensureMaps(unitId, subUnitId);
|
||||
|
||||
|
||||
@@ -14,11 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRange, Nullable } from '@univerjs/core';
|
||||
import type { ISheetDataValidationRule, Nullable } from '@univerjs/core';
|
||||
import type { IFormulaInfo, IOtherFormulaResult } from '@univerjs/sheets-formula';
|
||||
import { Disposable, Inject, isFormulaString, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { DataValidationType, Disposable, Inject, isFormulaString, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { DataValidationModel } from '@univerjs/data-validation';
|
||||
import { RegisterOtherFormulaService } from '@univerjs/sheets-formula';
|
||||
import { isCustomFormulaType } from '../utils/formula';
|
||||
import { DataValidationCacheService } from './dv-cache.service';
|
||||
|
||||
type RuleId = string;
|
||||
@@ -85,23 +86,26 @@ export class DataValidationFormulaService extends Disposable {
|
||||
return this._registerOtherFormulaService.registerFormulaWithRange(unitId, subUnitId, formula, ranges, { ruleId });
|
||||
}
|
||||
|
||||
addRule(unitId: string, subUnitId: string, ruleId: string, formula1: string | undefined, formula2: string | undefined, ranges: IRange[]) {
|
||||
const isFormula1Legal = isFormulaString(formula1);
|
||||
const isFormula2Legal = isFormulaString(formula2);
|
||||
if (!isFormula1Legal && !isFormula2Legal) {
|
||||
return;
|
||||
addRule(unitId: string, subUnitId: string, rule: ISheetDataValidationRule) {
|
||||
if (!isCustomFormulaType(rule.type) && rule.type !== DataValidationType.CHECKBOX) {
|
||||
const { formula1, formula2, uid: ruleId } = rule;
|
||||
const isFormula1Legal = isFormulaString(formula1);
|
||||
const isFormula2Legal = isFormulaString(formula2);
|
||||
if (!isFormula1Legal && !isFormula2Legal) {
|
||||
return;
|
||||
}
|
||||
const formulaRuleMap = this._ensureRuleFormulaMap(unitId, subUnitId);
|
||||
const item: [IFormulaInfo | undefined, IFormulaInfo | undefined] = [undefined, undefined];
|
||||
if (isFormula1Legal) {
|
||||
const id = this._registerSingleFormula(unitId, subUnitId, formula1!, ruleId);
|
||||
item[0] = { id, text: formula1! };
|
||||
}
|
||||
if (isFormula2Legal) {
|
||||
const id = this._registerSingleFormula(unitId, subUnitId, formula2!, ruleId);
|
||||
item[1] = { id, text: formula2! };
|
||||
}
|
||||
formulaRuleMap.set(ruleId, item);
|
||||
}
|
||||
const formulaRuleMap = this._ensureRuleFormulaMap(unitId, subUnitId);
|
||||
const item: [IFormulaInfo | undefined, IFormulaInfo | undefined] = [undefined, undefined];
|
||||
if (isFormula1Legal) {
|
||||
const id = this._registerSingleFormula(unitId, subUnitId, formula1!, ruleId);
|
||||
item[0] = { id, text: formula1! };
|
||||
}
|
||||
if (isFormula2Legal) {
|
||||
const id = this._registerSingleFormula(unitId, subUnitId, formula2!, ruleId);
|
||||
item[1] = { id, text: formula2! };
|
||||
}
|
||||
formulaRuleMap.set(ruleId, item);
|
||||
}
|
||||
|
||||
removeRule(unitId: string, subUnitId: string, ruleId: string) {
|
||||
@@ -115,42 +119,6 @@ export class DataValidationFormulaService extends Disposable {
|
||||
idList.length && this._registerOtherFormulaService.deleteFormula(unitId, subUnitId, idList);
|
||||
}
|
||||
|
||||
updateRuleFormulaText(unitId: string, subUnitId: string, ruleId: string, formula1: string | undefined, formula2: string | undefined, ranges: IRange[]) {
|
||||
const formulaRuleMap = this._ensureRuleFormulaMap(unitId, subUnitId);
|
||||
const item = formulaRuleMap.get(ruleId);
|
||||
if (!item) {
|
||||
this.addRule(unitId, subUnitId, ruleId, formula1, formula2, ranges);
|
||||
return;
|
||||
}
|
||||
|
||||
const [oldFormula1, oldFormula2] = item;
|
||||
if (oldFormula1?.text !== formula1) {
|
||||
oldFormula1 && this._registerOtherFormulaService.deleteFormula(unitId, subUnitId, [oldFormula1.id]);
|
||||
if (isFormulaString(formula1)) {
|
||||
const formulaId = this._registerSingleFormula(unitId, subUnitId, formula1!, ruleId);
|
||||
item[0] = {
|
||||
text: formula1!,
|
||||
id: formulaId,
|
||||
};
|
||||
} else {
|
||||
item[0] = undefined;
|
||||
};
|
||||
}
|
||||
|
||||
if (oldFormula2?.text !== formula2) {
|
||||
oldFormula2 && this._registerOtherFormulaService.deleteFormula(unitId, subUnitId, [oldFormula2.id]);
|
||||
if (isFormulaString(formula2)) {
|
||||
const formulaId = this._registerSingleFormula(unitId, subUnitId, formula2!, ruleId);
|
||||
item[1] = {
|
||||
text: formula2!,
|
||||
id: formulaId,
|
||||
};
|
||||
} else {
|
||||
item[1] = undefined;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
getRuleFormulaResult(unitId: string, subUnitId: string, ruleId: string): Promise<Nullable<[Nullable<IOtherFormulaResult>, Nullable<IOtherFormulaResult>]>> {
|
||||
const ruleFormulaMap = this._ensureRuleFormulaMap(unitId, subUnitId);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ICellData, Nullable } from '@univerjs/core';
|
||||
import { DataValidationType, type ICellData, type Nullable } from '@univerjs/core';
|
||||
import { ERROR_TYPE_SET } from '@univerjs/engine-formula';
|
||||
|
||||
export function getFormulaResult(result: Nullable<Nullable<ICellData>[][]>) {
|
||||
@@ -28,3 +28,10 @@ export function getFormulaCellData(result: Nullable<Nullable<ICellData>[][]>) {
|
||||
export function isLegalFormulaResult(res: string) {
|
||||
return !(ERROR_TYPE_SET as Set<string>).has(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* Judge if the data-validation's formula need to be offseted by ranges
|
||||
*/
|
||||
export function isCustomFormulaType(type: DataValidationType) {
|
||||
return type !== DataValidationType.LIST && type !== DataValidationType.LIST_MULTIPLE && type !== DataValidationType.CHECKBOX && type !== DataValidationType.ANY;
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@ import { DataValidationOperator, DataValidationType, isFormulaString, numfmt, To
|
||||
import { BaseDataValidator } from '@univerjs/data-validation';
|
||||
import dayjs from 'dayjs';
|
||||
import { DateOperatorErrorTitleMap, DateOperatorNameMap, DateOperatorTitleMap } from '../common/date-text-map';
|
||||
import { DataValidationFormulaService } from '../services/dv-formula.service';
|
||||
import { DataValidationCustomFormulaService } from '../services/dv-custom-formula.service';
|
||||
import { TWO_FORMULA_OPERATOR_COUNT } from '../types/const/two-formula-operators';
|
||||
import { getFormulaResult, isLegalFormulaResult } from '../utils/formula';
|
||||
import { isLegalFormulaResult } from '../utils/formula';
|
||||
|
||||
const FORMULA1 = '{FORMULA1}';
|
||||
const FORMULA2 = '{FORMULA2}';
|
||||
@@ -61,32 +61,22 @@ export class DateValidator extends BaseDataValidator<number> {
|
||||
];
|
||||
|
||||
scopes: string | string[] = ['sheet'];
|
||||
private _formulaService = this.injector.get(DataValidationFormulaService);
|
||||
private _customFormulaService = this.injector.get(DataValidationCustomFormulaService);
|
||||
|
||||
override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string, row: number, column: number): Promise<IFormulaResult<number | undefined>> {
|
||||
const formulaResult1 = await this._customFormulaService.getCellFormulaValue(unitId, subUnitId, rule.uid, row, column);
|
||||
const formulaResult2 = await this._customFormulaService.getCellFormula2Value(unitId, subUnitId, rule.uid, row, column);
|
||||
|
||||
override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise<IFormulaResult<number | undefined>> {
|
||||
const results = await this._formulaService.getRuleFormulaResult(unitId, subUnitId, rule.uid);
|
||||
const { formula1, formula2 } = rule;
|
||||
const formulaResult1 = getFormulaResult(results?.[0]?.result?.[0][0]);
|
||||
const formulaResult2 = getFormulaResult(results?.[1]?.result?.[0][0]);
|
||||
const isFormulaValid = isLegalFormulaResult(String(formulaResult1)) && isLegalFormulaResult(String(formulaResult2));
|
||||
|
||||
return {
|
||||
formula1: transformDate2SerialNumber(isFormulaString(formula1) ? formulaResult1 : formula1),
|
||||
formula2: transformDate2SerialNumber(isFormulaString(formula2) ? formulaResult2 : formula2),
|
||||
formula1: transformDate2SerialNumber(isFormulaString(formula1) ? formulaResult1?.v : formula1),
|
||||
formula2: transformDate2SerialNumber(isFormulaString(formula2) ? formulaResult2?.v : formula2),
|
||||
isFormulaValid,
|
||||
};
|
||||
}
|
||||
|
||||
parseFormulaSync(rule: IDataValidationRule, unitId: string, subUnitId: string) {
|
||||
const results = this._formulaService.getRuleFormulaResultSync(unitId, subUnitId, rule.uid);
|
||||
const { formula1, formula2 } = rule;
|
||||
|
||||
return {
|
||||
formula1: transformDate2SerialNumber(isFormulaString(formula1) ? getFormulaResult(results?.[0]?.result?.[0][0]) : formula1),
|
||||
formula2: transformDate2SerialNumber(isFormulaString(formula2) ? getFormulaResult(results?.[1]?.result?.[0][0]) : formula2),
|
||||
};
|
||||
}
|
||||
|
||||
override async isValidType(info: IValidatorCellInfo): Promise<boolean> {
|
||||
const { interceptValue, value } = info;
|
||||
if (typeof value === 'number' && typeof interceptValue === 'string') {
|
||||
|
||||
@@ -18,9 +18,9 @@ import type { CellValue, IDataValidationRule, Nullable } from '@univerjs/core';
|
||||
import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation';
|
||||
import { DataValidationOperator, DataValidationType, isFormulaString, Tools } from '@univerjs/core';
|
||||
import { BaseDataValidator } from '@univerjs/data-validation';
|
||||
import { DataValidationFormulaService } from '../services/dv-formula.service';
|
||||
import { DataValidationCustomFormulaService } from '../services/dv-custom-formula.service';
|
||||
import { TWO_FORMULA_OPERATOR_COUNT } from '../types/const/two-formula-operators';
|
||||
import { getFormulaResult, isLegalFormulaResult } from '../utils/formula';
|
||||
import { isLegalFormulaResult } from '../utils/formula';
|
||||
|
||||
export function getCellValueNumber(cellValue: CellValue) {
|
||||
let str = cellValue;
|
||||
@@ -36,8 +36,7 @@ export function getCellValueNumber(cellValue: CellValue) {
|
||||
}
|
||||
|
||||
export class DecimalValidator extends BaseDataValidator<number> {
|
||||
private _formulaService = this.injector.get(DataValidationFormulaService);
|
||||
|
||||
private _customFormulaService = this.injector.get(DataValidationCustomFormulaService);
|
||||
id: string = DataValidationType.DECIMAL;
|
||||
title: string = 'dataValidation.decimal.title';
|
||||
|
||||
@@ -81,17 +80,16 @@ export class DecimalValidator extends BaseDataValidator<number> {
|
||||
return +formula;
|
||||
}
|
||||
|
||||
async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise<IFormulaResult> {
|
||||
const formulaInfo = await this._formulaService.getRuleFormulaResult(unitId, subUnitId, rule.uid);
|
||||
async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string, row: number, column: number): Promise<IFormulaResult> {
|
||||
const formulaResult1 = await this._customFormulaService.getCellFormulaValue(unitId, subUnitId, rule.uid, row, column);
|
||||
const formulaResult2 = await this._customFormulaService.getCellFormula2Value(unitId, subUnitId, rule.uid, row, column);
|
||||
const { formula1, formula2 } = rule;
|
||||
|
||||
const formulaResult1 = getFormulaResult(formulaInfo?.[0]?.result?.[0][0]);
|
||||
const formulaResult2 = getFormulaResult(formulaInfo?.[1]?.result?.[0][0]);
|
||||
const isFormulaValid = isLegalFormulaResult(String(formulaResult1)) && isLegalFormulaResult(String(formulaResult2));
|
||||
|
||||
const info = {
|
||||
formula1: this._parseNumber(isFormulaString(formula1) ? formulaResult1 : formula1),
|
||||
formula2: this._parseNumber(isFormulaString(formula2) ? formulaResult2 : formula2),
|
||||
formula1: this._parseNumber(isFormulaString(formula1) ? formulaResult1?.v : formula1),
|
||||
formula2: this._parseNumber(isFormulaString(formula2) ? formulaResult2?.v : formula2),
|
||||
isFormulaValid,
|
||||
};
|
||||
|
||||
|
||||
@@ -18,9 +18,9 @@ import type { CellValue, IDataValidationRule, IDataValidationRuleBase, Nullable
|
||||
import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation';
|
||||
import { DataValidationOperator, DataValidationType, isFormulaString, Tools } from '@univerjs/core';
|
||||
import { BaseDataValidator, TextLengthErrorTitleMap } from '@univerjs/data-validation';
|
||||
import { DataValidationFormulaService } from '../services/dv-formula.service';
|
||||
import { DataValidationCustomFormulaService } from '../services/dv-custom-formula.service';
|
||||
import { TWO_FORMULA_OPERATOR_COUNT } from '../types/const/two-formula-operators';
|
||||
import { getFormulaResult, isLegalFormulaResult } from '../utils/formula';
|
||||
import { isLegalFormulaResult } from '../utils/formula';
|
||||
|
||||
const FORMULA1 = '{FORMULA1}';
|
||||
const FORMULA2 = '{FORMULA2}';
|
||||
@@ -42,7 +42,7 @@ export class TextLengthValidator extends BaseDataValidator<number> {
|
||||
|
||||
scopes: string | string[] = ['sheet'];
|
||||
|
||||
private _formulaService = this.injector.get(DataValidationFormulaService);
|
||||
private _customFormulaService = this.injector.get(DataValidationCustomFormulaService);
|
||||
|
||||
private _isFormulaOrInt(formula: string) {
|
||||
return !Tools.isBlank(formula) && (isFormulaString(formula) || (!Number.isNaN(+formula) && Number.isInteger(+formula)));
|
||||
@@ -85,17 +85,16 @@ export class TextLengthValidator extends BaseDataValidator<number> {
|
||||
return !Number.isNaN(formula);
|
||||
}
|
||||
|
||||
override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise<IFormulaResult<any>> {
|
||||
const formulaInfo = await this._formulaService.getRuleFormulaResult(unitId, subUnitId, rule.uid);
|
||||
override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string, row: number, column: number): Promise<IFormulaResult<any>> {
|
||||
const formulaResult1 = await this._customFormulaService.getCellFormulaValue(unitId, subUnitId, rule.uid, row, column);
|
||||
const formulaResult2 = await this._customFormulaService.getCellFormula2Value(unitId, subUnitId, rule.uid, row, column);
|
||||
const { formula1, formula2 } = rule;
|
||||
|
||||
const formulaResult1 = getFormulaResult(formulaInfo?.[0]?.result?.[0][0]);
|
||||
const formulaResult2 = getFormulaResult(formulaInfo?.[1]?.result?.[0][0]);
|
||||
const isFormulaValid = isLegalFormulaResult(String(formulaResult1)) && isLegalFormulaResult(String(formulaResult2));
|
||||
|
||||
return {
|
||||
formula1: this._parseNumber(isFormulaString(formula1) ? formulaResult1 : formula1),
|
||||
formula2: this._parseNumber(isFormulaString(formula2) ? formulaResult2 : formula2),
|
||||
formula1: this._parseNumber(isFormulaString(formula1) ? formulaResult1?.v : formula1),
|
||||
formula2: this._parseNumber(isFormulaString(formula2) ? formulaResult2?.v : formula2),
|
||||
isFormulaValid,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -18,13 +18,13 @@ import type { CellValue, IDataValidationRule, IDataValidationRuleBase, Nullable
|
||||
import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation';
|
||||
import { DataValidationOperator, DataValidationType, isFormulaString, Tools } from '@univerjs/core';
|
||||
import { BaseDataValidator } from '@univerjs/data-validation';
|
||||
import { DataValidationFormulaService } from '../services/dv-formula.service';
|
||||
import { DataValidationCustomFormulaService } from '../services/dv-custom-formula.service';
|
||||
import { TWO_FORMULA_OPERATOR_COUNT } from '../types/const/two-formula-operators';
|
||||
import { isLegalFormulaResult } from '../utils/formula';
|
||||
import { getCellValueNumber } from './decimal-validator';
|
||||
|
||||
export class WholeValidator extends BaseDataValidator<number> {
|
||||
private _formulaService = this.injector.get(DataValidationFormulaService);
|
||||
private _customFormulaService = this.injector.get(DataValidationCustomFormulaService);
|
||||
|
||||
id: string = DataValidationType.WHOLE;
|
||||
title: string = 'dataValidation.whole.title';
|
||||
@@ -68,12 +68,13 @@ export class WholeValidator extends BaseDataValidator<number> {
|
||||
return +formula;
|
||||
}
|
||||
|
||||
override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise<IFormulaResult> {
|
||||
const formulaInfo = await this._formulaService.getRuleFormulaResult(unitId, subUnitId, rule.uid);
|
||||
override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string, row: number, column: number): Promise<IFormulaResult> {
|
||||
const res1 = await this._customFormulaService.getCellFormulaValue(unitId, subUnitId, rule.uid, row, column);
|
||||
const res2 = await this._customFormulaService.getCellFormula2Value(unitId, subUnitId, rule.uid, row, column);
|
||||
const { formula1, formula2 } = rule;
|
||||
|
||||
const formula1Result = isFormulaString(formula1) ? formulaInfo?.[0]?.result?.[0]?.[0]?.[0][0]?.v : formula1;
|
||||
const formula2Result = isFormulaString(formula2) ? formulaInfo?.[1]?.result?.[0]?.[0]?.[0][0]?.v : formula2;
|
||||
const formula1Result = isFormulaString(formula1) ? res1?.v : formula1;
|
||||
const formula2Result = isFormulaString(formula2) ? res2?.v : formula2;
|
||||
const isFormulaValid = isLegalFormulaResult(`${formula1Result}`) && isLegalFormulaResult(`${formula2Result}`);
|
||||
|
||||
const info = {
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ITestBed } from './util';
|
||||
import { ICommandService, type IRange } from '@univerjs/core';
|
||||
import { InsertColCommand, MoveRangeCommand, RemoveColCommand } from '@univerjs/sheets';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { FormulaRefRangeService } from '../formula-ref-range.service';
|
||||
import { createCommandTestBed } from './util';
|
||||
|
||||
const MoveRangeCommandId = 'sheet.command.move-range';
|
||||
|
||||
describe('FormulaRefRangeService', () => {
|
||||
let testBed: ITestBed;
|
||||
|
||||
beforeEach(() => {
|
||||
testBed = createCommandTestBed();
|
||||
});
|
||||
|
||||
it('transform formula string with move range', () => {
|
||||
const formulaRefRangeService = testBed.get(FormulaRefRangeService);
|
||||
const resFormula = formulaRefRangeService.transformFormulaByEffectCommand(
|
||||
'test',
|
||||
'sheet1',
|
||||
'=SUM(A1)',
|
||||
{
|
||||
id: MoveRangeCommandId,
|
||||
params: {
|
||||
fromRange: {
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
},
|
||||
toRange: {
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(resFormula).toBe('=SUM(B3)');
|
||||
});
|
||||
|
||||
it('not transform formula string with move range', () => {
|
||||
const formulaRefRangeService = testBed.get(FormulaRefRangeService);
|
||||
const resFormula = formulaRefRangeService.transformFormulaByEffectCommand(
|
||||
'test',
|
||||
'sheet1',
|
||||
'=SUM(A1:A10)',
|
||||
{
|
||||
id: MoveRangeCommandId,
|
||||
params: {
|
||||
fromRange: {
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
},
|
||||
toRange: {
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(resFormula).toBe('=SUM(A1:A10)');
|
||||
});
|
||||
|
||||
it('transform range formula string with move range', async () => {
|
||||
const formulaRefRangeService = testBed.get(FormulaRefRangeService);
|
||||
|
||||
let newFormulas: {
|
||||
formulas: string[];
|
||||
ranges: IRange[];
|
||||
}[] = [];
|
||||
|
||||
formulaRefRangeService.registerRangeFormula(
|
||||
'test',
|
||||
'sheet1',
|
||||
[{
|
||||
startRow: 5,
|
||||
endRow: 10,
|
||||
startColumn: 5,
|
||||
endColumn: 5,
|
||||
}],
|
||||
['=SUM(A1)'],
|
||||
(res) => {
|
||||
newFormulas = res;
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
await testBed.get(ICommandService).executeCommand(
|
||||
MoveRangeCommand.id,
|
||||
{
|
||||
fromRange: {
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
},
|
||||
toRange: {
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(newFormulas).toEqual(
|
||||
[
|
||||
{ formulas: ['=SUM(A3)'], ranges: [{ startColumn: 5, endColumn: 5, startRow: 7, endRow: 10 }] },
|
||||
{ formulas: ['=SUM(B3)'], ranges: [{ startColumn: 5, endColumn: 5, startRow: 5, endRow: 6 }] },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('not transform range formula string with move range', async () => {
|
||||
const formulaRefRangeService = testBed.get(FormulaRefRangeService);
|
||||
|
||||
let newFormulas: {
|
||||
formulas: string[];
|
||||
ranges: IRange[];
|
||||
}[] = [];
|
||||
|
||||
formulaRefRangeService.registerRangeFormula(
|
||||
'test',
|
||||
'sheet1',
|
||||
[{
|
||||
startRow: 5,
|
||||
endRow: 10,
|
||||
startColumn: 5,
|
||||
endColumn: 5,
|
||||
}],
|
||||
['=SUM(A1:A10)'],
|
||||
(res) => {
|
||||
newFormulas = res;
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
await testBed.get(ICommandService).executeCommand(
|
||||
MoveRangeCommand.id,
|
||||
{
|
||||
fromRange: {
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
},
|
||||
toRange: {
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(newFormulas).toEqual(
|
||||
[
|
||||
{
|
||||
formulas: ['=SUM(A1:A10)'],
|
||||
ranges: [{
|
||||
startRow: 5,
|
||||
endRow: 10,
|
||||
startColumn: 5,
|
||||
endColumn: 5,
|
||||
}],
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('transform range formula string with delete col', async () => {
|
||||
const formulaRefRangeService = testBed.get(FormulaRefRangeService);
|
||||
|
||||
let newFormulas: {
|
||||
formulas: string[];
|
||||
ranges: IRange[];
|
||||
}[] = [];
|
||||
|
||||
formulaRefRangeService.registerRangeFormula(
|
||||
'test',
|
||||
'sheet1',
|
||||
[{
|
||||
startRow: 5,
|
||||
endRow: 10,
|
||||
startColumn: 5,
|
||||
endColumn: 10,
|
||||
}],
|
||||
['=A1'],
|
||||
(res) => {
|
||||
newFormulas = res;
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
await testBed.get(ICommandService).executeCommand(
|
||||
RemoveColCommand.id,
|
||||
{
|
||||
range: {
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
startRow: 0,
|
||||
endRow: 9999,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(newFormulas).toEqual(
|
||||
[
|
||||
{ formulas: ['=A1'], ranges: [{ startColumn: 5, endColumn: 8, startRow: 5, endRow: 10 }] },
|
||||
{ formulas: ['=#REF!'], ranges: [{ startColumn: 3, endColumn: 4, startRow: 5, endRow: 10 }] },
|
||||
]
|
||||
);
|
||||
});
|
||||
|
||||
it('transform range formula string with insert col', async () => {
|
||||
const formulaRefRangeService = testBed.get(FormulaRefRangeService);
|
||||
|
||||
let newFormulas: {
|
||||
formulas: string[];
|
||||
ranges: IRange[];
|
||||
}[] = [];
|
||||
|
||||
formulaRefRangeService.registerRangeFormula(
|
||||
'test',
|
||||
'sheet1',
|
||||
[{
|
||||
startRow: 5,
|
||||
endRow: 10,
|
||||
startColumn: 5,
|
||||
endColumn: 10,
|
||||
}],
|
||||
['=A1'],
|
||||
(res) => {
|
||||
newFormulas = res;
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
await testBed.get(ICommandService).executeCommand(
|
||||
InsertColCommand.id,
|
||||
{
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
range: {
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
startRow: 0,
|
||||
endRow: 9999,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
expect(newFormulas).toEqual(
|
||||
[
|
||||
{
|
||||
formulas: ['=C1'],
|
||||
ranges: [{ startColumn: 7, endColumn: 12, startRow: 5, endRow: 10 }],
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import { ICommandService, ILogService, Inject, Injector, IUniverInstanceService, LocaleType, LogLevel, Plugin, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { DocSelectionManagerService } from '@univerjs/docs';
|
||||
import { CalculateFormulaService, DefinedNamesService, FormulaCurrentConfigService, FormulaDataModel, FormulaRuntimeService, ICalculateFormulaService, IDefinedNamesService, IFormulaCurrentConfigService, IFormulaRuntimeService, LexerTreeBuilder } from '@univerjs/engine-formula';
|
||||
import { DefinedNameDataController, InsertColCommand, IRefSelectionsService, MoveColsCommand, MoveRangeCommand, RangeProtectionRuleModel, RefRangeService, RemoveColCommand, SheetInterceptorService, SheetsSelectionsService, WorkbookPermissionService, WorksheetPermissionService, WorksheetProtectionPointModel, WorksheetProtectionRuleModel } from '@univerjs/sheets';
|
||||
import { FormulaRefRangeService } from '../formula-ref-range.service';
|
||||
|
||||
const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
cellData: {
|
||||
0: {
|
||||
0: {
|
||||
v: 1,
|
||||
},
|
||||
1: {
|
||||
f: '=SUM(A1)',
|
||||
},
|
||||
},
|
||||
1: {
|
||||
1: {
|
||||
v: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
locale: LocaleType.ZH_CN,
|
||||
name: '',
|
||||
sheetOrder: [],
|
||||
styles: {},
|
||||
};
|
||||
|
||||
export interface ITestBed {
|
||||
univer: Univer;
|
||||
get: Injector['get'];
|
||||
sheet: Workbook;
|
||||
}
|
||||
|
||||
export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
const get = injector.get.bind(injector);
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
private _formulaDataModel: FormulaDataModel | null = null;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
const injector = this._injector;
|
||||
// injector.add([ICommandService, { useClass: CommandService }]);
|
||||
injector.add([WorksheetPermissionService]);
|
||||
injector.add([WorksheetProtectionPointModel]);
|
||||
injector.add([RangeProtectionRuleModel]);
|
||||
injector.add([WorkbookPermissionService]);
|
||||
injector.add([WorksheetProtectionRuleModel]);
|
||||
injector.add([SheetsSelectionsService]);
|
||||
injector.add([SheetInterceptorService]);
|
||||
injector.add([ICalculateFormulaService, { useClass: CalculateFormulaService }]);
|
||||
injector.add([FormulaDataModel]);
|
||||
injector.add([LexerTreeBuilder]);
|
||||
injector.add([DocSelectionManagerService]);
|
||||
injector.add([IDefinedNamesService, { useClass: DefinedNamesService }]);
|
||||
injector.add([DefinedNameDataController]);
|
||||
injector.add([IFormulaRuntimeService, { useClass: FormulaRuntimeService }]);
|
||||
injector.add([IFormulaCurrentConfigService, { useClass: FormulaCurrentConfigService }]);
|
||||
injector.add([IRefSelectionsService, { useClass: SheetsSelectionsService }]);
|
||||
injector.add([RefRangeService]);
|
||||
injector.add([FormulaRefRangeService]);
|
||||
|
||||
dependencies?.forEach((d) => injector.add(d));
|
||||
|
||||
this._injector.get(SheetInterceptorService);
|
||||
this._injector.get(WorkbookPermissionService);
|
||||
this._injector.get(WorksheetPermissionService);
|
||||
this._injector.get(DefinedNameDataController);
|
||||
|
||||
[
|
||||
MoveRangeCommand,
|
||||
RemoveColCommand,
|
||||
InsertColCommand,
|
||||
MoveColsCommand,
|
||||
].forEach((command) => {
|
||||
this._injector.get(ICommandService).registerCommand(command);
|
||||
});
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this._formulaDataModel = get(FormulaDataModel);
|
||||
this._formulaDataModel.initFormulaData();
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
const sheet = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, workbookData || TEST_WORKBOOK_DATA_DEMO);
|
||||
|
||||
const univerInstanceService = injector.get(IUniverInstanceService);
|
||||
univerInstanceService.focusUnit('test');
|
||||
|
||||
const logService = injector.get(ILogService);
|
||||
logService.setLogLevel(LogLevel.SILENT); // change this to `LogLevel.VERBOSE` to debug tests via logs
|
||||
|
||||
return {
|
||||
univer,
|
||||
get,
|
||||
sheet,
|
||||
};
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
|
||||
import type { IDisposable, IMutationInfo, IRange, Workbook } from '@univerjs/core';
|
||||
import type { EffectRefRangeParams } from '@univerjs/sheets';
|
||||
import { Disposable, DisposableCollection, Inject, IUniverInstanceService, moveRangeByOffset, UniverInstanceType } from '@univerjs/core';
|
||||
import { AbsoluteRefType, Disposable, DisposableCollection, getIntersectRange, Inject, Injector, isFormulaString, IUniverInstanceService, moveRangeByOffset, Rectangle, UniverInstanceType } from '@univerjs/core';
|
||||
import { deserializeRangeWithSheetWithCache, ErrorType, generateStringWithSequence, LexerTreeBuilder, sequenceNodeType, serializeRange, serializeRangeWithSheet, serializeRangeWithSpreadsheet } from '@univerjs/engine-formula';
|
||||
import { handleDefaultRangeChangeWithEffectRefCommands, RefRangeService } from '@univerjs/sheets';
|
||||
import { getSeparateEffectedRangesOnCommand, handleCommonDefaultRangeChangeWithEffectRefCommands, handleDefaultRangeChangeWithEffectRefCommands, RefRangeService } from '@univerjs/sheets';
|
||||
|
||||
export type FormulaChangeMap = Record<string, Record<string, Record<string, string>>>;
|
||||
|
||||
@@ -27,16 +27,55 @@ export type FormulaChangeCallback = (formulaString: string) => {
|
||||
undos: IMutationInfo[];
|
||||
};
|
||||
|
||||
export type RangeFormulaChangeCallback = (formulas: { formulaString: string; ranges: IRange[] }[]) => {
|
||||
export type RangeFormulaChangeCallback = (infos: { formulas: string[]; ranges: IRange[] }[]) => {
|
||||
redos: IMutationInfo[];
|
||||
undos: IMutationInfo[];
|
||||
};
|
||||
|
||||
function getFormulaKeyOffset(lexerTreeBuilder: LexerTreeBuilder, formulaString: string, refOffsetX: number, refOffsetY: number) {
|
||||
const sequenceNodes = lexerTreeBuilder.sequenceNodesBuilder(formulaString);
|
||||
|
||||
if (sequenceNodes == null) {
|
||||
return formulaString;
|
||||
}
|
||||
|
||||
const newSequenceNodes: Array<{ unitId: string; sheetName: string; range: IRange }> = [];
|
||||
|
||||
for (let i = 0, len = sequenceNodes.length; i < len; i++) {
|
||||
const node = sequenceNodes[i];
|
||||
if (typeof node === 'string' || node.nodeType !== sequenceNodeType.REFERENCE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const { token } = node;
|
||||
|
||||
const sequenceGrid = deserializeRangeWithSheetWithCache(token);
|
||||
|
||||
const { sheetName, unitId: sequenceUnitId } = sequenceGrid;
|
||||
|
||||
let newRange: IRange = sequenceGrid.range;
|
||||
if (newRange.startAbsoluteRefType === AbsoluteRefType.ALL && newRange.endAbsoluteRefType === AbsoluteRefType.ALL) {
|
||||
continue;
|
||||
} else {
|
||||
newRange = moveRangeByOffset(newRange, refOffsetX, refOffsetY);
|
||||
}
|
||||
|
||||
newSequenceNodes.push({
|
||||
unitId: sequenceUnitId,
|
||||
sheetName,
|
||||
range: newRange,
|
||||
});
|
||||
}
|
||||
|
||||
return newSequenceNodes.map((item) => `${item.unitId}!${item.sheetName}!${item.range.startRow}!${item.range.endRow}!${item.range.startColumn}!${item.range.endColumn}`).join('|');
|
||||
}
|
||||
|
||||
export class FormulaRefRangeService extends Disposable {
|
||||
constructor(
|
||||
@Inject(RefRangeService) private readonly _refRangeService: RefRangeService,
|
||||
@Inject(LexerTreeBuilder) private readonly _lexerTreeBuilder: LexerTreeBuilder,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(Injector) private readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -78,7 +117,7 @@ export class FormulaRefRangeService extends Disposable {
|
||||
newToken = serializeRange(finalRange);
|
||||
}
|
||||
} else {
|
||||
newToken = ErrorType.ERROR;
|
||||
newToken = ErrorType.REF;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -126,7 +165,7 @@ export class FormulaRefRangeService extends Disposable {
|
||||
newToken = serializeRange(finalRange);
|
||||
}
|
||||
} else {
|
||||
newToken = ErrorType.ERROR;
|
||||
newToken = ErrorType.REF;
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -165,4 +204,210 @@ export class FormulaRefRangeService extends Disposable {
|
||||
|
||||
return disposableCollection;
|
||||
}
|
||||
|
||||
private _getFormulaDependcy(unitId: string, subUnitId: string, formula: string | undefined, ranges: IRange[]) {
|
||||
const nodes = isFormulaString(formula) ? this._lexerTreeBuilder.sequenceNodesBuilder(formula!) : null;
|
||||
|
||||
const dependencyRanges: { unitId: string; subUnitId: string; ranges: IRange[] }[] = [];
|
||||
nodes?.forEach((node) => {
|
||||
if (typeof node === 'object' && node.nodeType === sequenceNodeType.REFERENCE) {
|
||||
const gridRangeName = deserializeRangeWithSheetWithCache(node.token);
|
||||
const { range, unitId: rangeUnitId, sheetName: rangeSheetName } = gridRangeName;
|
||||
// ignore all absolute reference
|
||||
if (range.startAbsoluteRefType === AbsoluteRefType.ALL && range.endAbsoluteRefType === AbsoluteRefType.ALL) {
|
||||
return;
|
||||
}
|
||||
const workbook = this._univerInstanceService.getUnit<Workbook>(rangeUnitId || unitId);
|
||||
const worksheet = rangeSheetName ? workbook?.getSheetBySheetName(rangeSheetName) : workbook?.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
const realUnitId = workbook!.getUnitId();
|
||||
const realSheetId = worksheet.getSheetId();
|
||||
const orginStartRow = ranges[0].startRow;
|
||||
const orginStartColumn = ranges[0].startColumn;
|
||||
const currentStartRow = range.startRow;
|
||||
const currentStartColumn = range.startColumn;
|
||||
|
||||
const offsetRanges = ranges.map((range) => ({
|
||||
startRow: range.startRow - orginStartRow + currentStartRow,
|
||||
endRow: range.endRow - orginStartRow + currentStartRow,
|
||||
startColumn: range.startColumn - orginStartColumn + currentStartColumn,
|
||||
endColumn: range.endColumn - orginStartColumn + currentStartColumn,
|
||||
}));
|
||||
|
||||
dependencyRanges.push({
|
||||
unitId: realUnitId,
|
||||
subUnitId: realSheetId,
|
||||
ranges: offsetRanges,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return dependencyRanges;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
registerRangeFormula(unitId: string, subUnitId: string, oldRanges: IRange[], formulas: string[], callback: RangeFormulaChangeCallback): IDisposable {
|
||||
const disposableCollection = new DisposableCollection();
|
||||
const formulaDeps = formulas.map((formula) => this._getFormulaDependcy(unitId, subUnitId, formula, oldRanges));
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
const handleRangeChange = (commandInfo: EffectRefRangeParams) => {
|
||||
const orginStartRow = oldRanges[0].startRow;
|
||||
const orginStartColumn = oldRanges[0].startColumn;
|
||||
const deps = [{ unitId, subUnitId, ranges: oldRanges }, ...formulaDeps.flat()];
|
||||
const matchedEffectedRanges: IRange[][] = [];
|
||||
const effectedRanges = getSeparateEffectedRangesOnCommand(this._injector, commandInfo);
|
||||
// 1. calculate effected ranges
|
||||
for (const { unitId: depUnitId, subUnitId: depSubUnitId, ranges } of deps) {
|
||||
if (depUnitId === effectedRanges.unitId && depSubUnitId === effectedRanges.subUnitId) {
|
||||
const intersectedRanges: IRange[] = [];
|
||||
const currentStartRow = ranges[0].startRow;
|
||||
const currentStartColumn = ranges[0].startColumn;
|
||||
const offsetRow = currentStartRow - orginStartRow;
|
||||
const offsetColumn = currentStartColumn - orginStartColumn;
|
||||
|
||||
for (const range of effectedRanges.ranges) {
|
||||
const intersectedRange = ranges.map((r) => getIntersectRange(range, r)).filter(Boolean) as IRange[];
|
||||
if (intersectedRange.length > 0) {
|
||||
intersectedRanges.push(...intersectedRange);
|
||||
}
|
||||
}
|
||||
|
||||
if (intersectedRanges.length > 0) {
|
||||
matchedEffectedRanges.push(
|
||||
intersectedRanges.map((range) => ({
|
||||
startRow: range.startRow - offsetRow,
|
||||
endRow: range.endRow - offsetRow,
|
||||
startColumn: range.startColumn - offsetColumn,
|
||||
endColumn: range.endColumn - offsetColumn,
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matchedEffectedRanges.length > 0) {
|
||||
// 2. split effected ranges to enusre there is no overlap
|
||||
const ranges = Rectangle.splitIntoGrid([...matchedEffectedRanges.flat()]);
|
||||
const noEffectRanges = Rectangle.subtractMulti(oldRanges, ranges);
|
||||
noEffectRanges.sort((a, b) => a.startRow - b.startRow || a.startColumn - b.startColumn);
|
||||
|
||||
// 3. calculate every effected range to get new range and new formula
|
||||
const keyMap = new Map<string, { formulas: { newFormula: string }[]; ranges: IRange[] }[]>();
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const range = ranges[i];
|
||||
const currentRow = range.startRow;
|
||||
const currentColumn = range.startColumn;
|
||||
const offsetRow = currentRow - orginStartRow;
|
||||
const offsetColumn = currentColumn - orginStartColumn;
|
||||
const transformedRange = handleCommonDefaultRangeChangeWithEffectRefCommands(range, commandInfo).sort((a, b) => a.startRow - b.startRow || a.startColumn - b.startColumn);
|
||||
if (!transformedRange.length) {
|
||||
continue;
|
||||
}
|
||||
const transformedRow = transformedRange[0].startRow;
|
||||
const transformedColumn = transformedRange[0].startColumn;
|
||||
const transformedOffsetRow = transformedRow - orginStartRow;
|
||||
const transformedOffsetColumn = transformedColumn - orginStartColumn;
|
||||
|
||||
const transformedFormulas = [];
|
||||
for (let j = 0; j < formulas.length; j++) {
|
||||
const formula = formulas[j];
|
||||
const isFormulaFormulaString = isFormulaString(formula);
|
||||
// 3.1 move formula ref offset to get formula string for this range before this command
|
||||
const formulaString = isFormulaFormulaString ? this._lexerTreeBuilder.moveFormulaRefOffset(formula!, offsetColumn, offsetRow) : formula!;
|
||||
// 3.2 transform formula by effect command
|
||||
const newFormula = isFormulaFormulaString ? this.transformFormulaByEffectCommand(unitId, subUnitId, formulaString, commandInfo) : formulaString;
|
||||
// 3.3 get formula key offset for this range after this command
|
||||
const orginFormula = getFormulaKeyOffset(this._lexerTreeBuilder, newFormula, -transformedOffsetColumn, -transformedOffsetRow);
|
||||
transformedFormulas.push({
|
||||
newFormula,
|
||||
orginFormula,
|
||||
});
|
||||
}
|
||||
|
||||
const item = {
|
||||
formulas: transformedFormulas,
|
||||
ranges: transformedRange,
|
||||
key: transformedFormulas.map((item) => item.orginFormula).join('_'),
|
||||
};
|
||||
|
||||
if (keyMap.has(item.key)) {
|
||||
keyMap.get(item.key)!.push(item);
|
||||
} else {
|
||||
keyMap.set(item.key, [item]);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. handle no effected ranges
|
||||
const originKey = formulas.map((item) => getFormulaKeyOffset(this._lexerTreeBuilder, item, 0, 0)).join('_');
|
||||
if (noEffectRanges.length > 0) {
|
||||
const currentRow = noEffectRanges[0].startRow;
|
||||
const currentColumn = noEffectRanges[0].startColumn;
|
||||
const noEffectFormulas = [];
|
||||
for (let i = 0; i < formulas.length; i++) {
|
||||
const formula = formulas[i];
|
||||
noEffectFormulas.push({
|
||||
newFormula: isFormulaString(formula) ? this._lexerTreeBuilder.moveFormulaRefOffset(formula, currentColumn - orginStartColumn, currentRow - orginStartRow) : formula,
|
||||
orginFormula: formula,
|
||||
});
|
||||
}
|
||||
|
||||
const item = {
|
||||
formulas: noEffectFormulas,
|
||||
ranges: noEffectRanges,
|
||||
key: originKey,
|
||||
};
|
||||
|
||||
if (keyMap.has(item.key)) {
|
||||
keyMap.get(item.key)!.push(item);
|
||||
} else {
|
||||
keyMap.set(item.key, [item]);
|
||||
}
|
||||
}
|
||||
|
||||
// 5. merge effected ranges and get new formula,
|
||||
// if origin formula was same, means these ranges can use same formula
|
||||
const res = [];
|
||||
const keys = Array.from(keyMap.keys());
|
||||
for (let i = keys.length - 1; i >= 0; i--) {
|
||||
const key = keys[i];
|
||||
const ranges = keyMap.get(key)!.sort((a, b) => a.ranges[0].startRow - b.ranges[0].startRow || a.ranges[0].startColumn - b.ranges[0].startColumn);
|
||||
const formulas = [];
|
||||
for (let j = 0; j < ranges[0].formulas.length; j++) {
|
||||
formulas.push(ranges[0].formulas[j].newFormula);
|
||||
}
|
||||
const newRanges = Rectangle.mergeRanges(ranges.map((item) => item.ranges).flat());
|
||||
newRanges.sort((a, b) => a.startRow - b.startRow || a.startColumn - b.startColumn);
|
||||
|
||||
res.push({
|
||||
formulas,
|
||||
ranges: newRanges,
|
||||
});
|
||||
}
|
||||
|
||||
return callback(res);
|
||||
}
|
||||
|
||||
return {
|
||||
undos: [],
|
||||
redos: [],
|
||||
};
|
||||
};
|
||||
|
||||
oldRanges.forEach((range) => {
|
||||
const disposable = this._refRangeService.registerRefRange(range, handleRangeChange, unitId, subUnitId);
|
||||
disposableCollection.add(disposable);
|
||||
});
|
||||
|
||||
[...formulaDeps.flat()].forEach(({ unitId, subUnitId, ranges }) => {
|
||||
ranges.forEach((range) => {
|
||||
const disposable = this._refRangeService.registerRefRange(range, handleRangeChange, unitId, subUnitId);
|
||||
disposableCollection.add(disposable);
|
||||
});
|
||||
});
|
||||
|
||||
return disposableCollection;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,11 +84,13 @@ export class SheetsHyperLinkRichTextRefRangeController extends Disposable {
|
||||
|
||||
if (p.body?.customRanges?.some((customRange) => customRange.rangeType === CustomRangeType.HYPERLINK && this._isLegalRangeUrl(unitId, customRange.properties?.url))) {
|
||||
const disposableCollection = new DisposableCollection();
|
||||
let hasWatch = false;
|
||||
p.body?.customRanges?.forEach((customRange) => {
|
||||
if (customRange.rangeType === CustomRangeType.HYPERLINK) {
|
||||
const payload = customRange.properties?.url;
|
||||
const range = this._isLegalRangeUrl(unitId, payload);
|
||||
if (range) {
|
||||
hasWatch = true;
|
||||
disposableCollection.add(this._refRangeService.watchRange(unitId, subUnitId, range, (before, after) => {
|
||||
customRange.properties!.url = `#gid=${subUnitId}&range=${after ? serializeRange(after) : ERROR_RANGE}`;
|
||||
}));
|
||||
@@ -96,7 +98,9 @@ export class SheetsHyperLinkRichTextRefRangeController extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
map.setValue(row, col, disposableCollection);
|
||||
if (hasWatch) {
|
||||
map.setValue(row, col, disposableCollection);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IAccessor, ICommand, IRange, Worksheet } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '../../basics';
|
||||
|
||||
import type { IMoveColumnsMutationParams, IMoveRowsMutationParams } from '../mutations/move-rows-cols.mutation';
|
||||
import type { ISetSelectionsOperationParams } from '../operations/selection.operation';
|
||||
import {
|
||||
CommandType,
|
||||
ErrorService,
|
||||
@@ -25,8 +30,6 @@ import {
|
||||
Rectangle,
|
||||
sequenceExecute,
|
||||
} from '@univerjs/core';
|
||||
import type { IAccessor, ICommand, IRange, Worksheet } from '@univerjs/core';
|
||||
|
||||
import { SheetsSelectionsService } from '../../services/selections/selection-manager.service';
|
||||
import { SheetInterceptorService } from '../../services/sheet-interceptor/sheet-interceptor.service';
|
||||
import {
|
||||
@@ -38,9 +41,6 @@ import {
|
||||
import { SetSelectionsOperation } from '../operations/selection.operation';
|
||||
import { alignToMergedCellsBorders, getPrimaryForRange } from './utils/selection-utils';
|
||||
import { getSheetCommandTarget } from './utils/target-util';
|
||||
import type { ISelectionWithStyle } from '../../basics';
|
||||
import type { IMoveColumnsMutationParams, IMoveRowsMutationParams } from '../mutations/move-rows-cols.mutation';
|
||||
import type { ISetSelectionsOperationParams } from '../operations/selection.operation';
|
||||
|
||||
export interface IMoveRowsCommandParams {
|
||||
unitId?: string;
|
||||
|
||||
@@ -67,6 +67,7 @@ export type { EffectRefRangeParams, IOperator } from './services/ref-range/type'
|
||||
export { EffectRefRangId, OperatorType } from './services/ref-range/type';
|
||||
export { DefinedNameDataController } from './controllers/defined-name-data.controller';
|
||||
export {
|
||||
getSeparateEffectedRangesOnCommand,
|
||||
handleBaseInsertRange,
|
||||
handleBaseMoveRowsCols,
|
||||
handleBaseRemoveRange,
|
||||
|
||||
@@ -14,23 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ObjectMatrix, queryObjectMatrix, Range, RANGE_TYPE, Rectangle } from '@univerjs/core';
|
||||
import type { ICommandInfo, IMutationInfo, IRange, Nullable } from '@univerjs/core';
|
||||
|
||||
import { DeleteRangeMoveLeftCommand } from '../../commands/commands/delete-range-move-left.command';
|
||||
import { DeleteRangeMoveUpCommand } from '../../commands/commands/delete-range-move-up.command';
|
||||
import { InsertRangeMoveDownCommand } from '../../commands/commands/insert-range-move-down.command';
|
||||
import { InsertRangeMoveRightCommandId } from '../../commands/commands/insert-range-move-right.command';
|
||||
import { InsertColMutation, InsertRowMutation } from '../../commands/mutations/insert-row-col.mutation';
|
||||
import { type IMoveRangeMutationParams, MoveRangeMutation } from '../../commands/mutations/move-range.mutation';
|
||||
import { type IMoveColumnsMutationParams, type IMoveRowsMutationParams, MoveColsMutation, MoveRowsMutation } from '../../commands/mutations/move-rows-cols.mutation';
|
||||
import { RemoveColMutation, RemoveRowMutation } from '../../commands/mutations/remove-row-col.mutation';
|
||||
import { RemoveSheetMutation } from '../../commands/mutations/remove-sheet.mutation';
|
||||
import { EffectRefRangId, OperatorType } from './type';
|
||||
import type { IAccessor, ICommandInfo, IMutationInfo, IRange, Nullable } from '@univerjs/core';
|
||||
import type { IInsertColMutationParams, IInsertRowMutationParams, IRemoveColMutationParams, IRemoveRowsMutationParams, IRemoveSheetMutationParams } from '../../basics';
|
||||
|
||||
import type { IInsertColCommandParams, IInsertRowCommandParams } from '../../commands/commands/insert-row-col.command';
|
||||
import type { IRemoveRowColCommandInterceptParams } from '../../commands/commands/remove-row-col.command';
|
||||
import type { ISheetCommandSharedParams } from '../../commands/utils/interface';
|
||||
import type { SheetsSelectionsService } from '../selections/selection-manager.service';
|
||||
import type {
|
||||
EffectRefRangeParams,
|
||||
IDeleteRangeMoveLeftCommand,
|
||||
@@ -46,6 +35,19 @@ import type {
|
||||
IRemoveRowColCommand,
|
||||
IReorderRangeCommand,
|
||||
} from './type';
|
||||
import { Direction, IUniverInstanceService, ObjectMatrix, queryObjectMatrix, Range, RANGE_TYPE, Rectangle } from '@univerjs/core';
|
||||
import { DeleteRangeMoveLeftCommand } from '../../commands/commands/delete-range-move-left.command';
|
||||
import { DeleteRangeMoveUpCommand } from '../../commands/commands/delete-range-move-up.command';
|
||||
import { InsertRangeMoveDownCommand } from '../../commands/commands/insert-range-move-down.command';
|
||||
import { InsertRangeMoveRightCommandId } from '../../commands/commands/insert-range-move-right.command';
|
||||
import { getSheetCommandTarget } from '../../commands/commands/utils/target-util';
|
||||
import { InsertColMutation, InsertRowMutation } from '../../commands/mutations/insert-row-col.mutation';
|
||||
import { type IMoveRangeMutationParams, MoveRangeMutation } from '../../commands/mutations/move-range.mutation';
|
||||
import { type IMoveColumnsMutationParams, type IMoveRowsMutationParams, MoveColsMutation, MoveRowsMutation } from '../../commands/mutations/move-rows-cols.mutation';
|
||||
import { RemoveColMutation, RemoveRowMutation } from '../../commands/mutations/remove-row-col.mutation';
|
||||
import { RemoveSheetMutation } from '../../commands/mutations/remove-sheet.mutation';
|
||||
import { SheetsSelectionsService } from '../selections/selection-manager.service';
|
||||
import { EffectRefRangId, OperatorType } from './type';
|
||||
|
||||
const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
|
||||
export const handleRangeTypeInput = (range: IRange) => {
|
||||
@@ -901,6 +903,84 @@ export const handleRemoveRowCommon = (param: IRemoveRowColCommandInterceptParams
|
||||
return queryObjectMatrix(matrix, (value) => value === 1);
|
||||
};
|
||||
|
||||
export const handleInsertRowCommon = (info: ICommandInfo<IInsertRowCommandParams>, targetRange: IRange) => {
|
||||
const param = info.params!;
|
||||
const insertRow = param.range.startRow;
|
||||
const insertCount = param.range.endRow - param.range.startRow + 1;
|
||||
const direction = param.direction;
|
||||
// expand
|
||||
if (
|
||||
(direction === Direction.UP && insertRow === targetRange.startRow) ||
|
||||
(direction === Direction.DOWN && insertRow - 1 === targetRange.endRow)
|
||||
) {
|
||||
return [
|
||||
{
|
||||
startRow: targetRange.startRow,
|
||||
endRow: targetRange.endRow + insertCount,
|
||||
startColumn: targetRange.startColumn,
|
||||
endColumn: targetRange.endColumn,
|
||||
},
|
||||
];
|
||||
}
|
||||
if (targetRange.startRow >= insertRow) {
|
||||
return [{
|
||||
startRow: targetRange.startRow + insertCount,
|
||||
endRow: targetRange.endRow + insertCount,
|
||||
startColumn: targetRange.startColumn,
|
||||
endColumn: targetRange.endColumn,
|
||||
}];
|
||||
} else if (targetRange.endRow <= insertRow) {
|
||||
return [targetRange];
|
||||
} else {
|
||||
return [{
|
||||
startRow: targetRange.startRow,
|
||||
endRow: targetRange.endRow + insertCount,
|
||||
startColumn: targetRange.startColumn,
|
||||
endColumn: targetRange.endColumn,
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
export const handleInsertColCommon = (info: ICommandInfo<IInsertColCommandParams>, targetRange: IRange) => {
|
||||
const param = info.params!;
|
||||
const insertColumn = param.range.startColumn;
|
||||
const insertCount = param.range.endColumn - param.range.startColumn + 1;
|
||||
const direction = param.direction;
|
||||
|
||||
// expand
|
||||
if (
|
||||
(direction === Direction.LEFT && insertColumn === targetRange.startColumn) ||
|
||||
(direction === Direction.RIGHT && insertColumn - 1 === targetRange.endColumn)
|
||||
) {
|
||||
return [
|
||||
{
|
||||
startRow: targetRange.startRow,
|
||||
endRow: targetRange.endRow,
|
||||
startColumn: targetRange.startColumn,
|
||||
endColumn: targetRange.endColumn + insertCount,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
if (targetRange.startColumn >= insertColumn) {
|
||||
return [{
|
||||
startRow: targetRange.startRow,
|
||||
endRow: targetRange.endRow,
|
||||
startColumn: targetRange.startColumn + insertCount,
|
||||
endColumn: targetRange.endColumn + insertCount,
|
||||
}];
|
||||
} else if (targetRange.endColumn <= insertColumn) {
|
||||
return [targetRange];
|
||||
} else {
|
||||
return [{
|
||||
startRow: targetRange.startRow,
|
||||
endRow: targetRange.endRow,
|
||||
startColumn: targetRange.startColumn,
|
||||
endColumn: targetRange.endColumn + insertCount,
|
||||
}];
|
||||
}
|
||||
};
|
||||
|
||||
export const runRefRangeMutations = (operators: IOperator[], range: IRange) => {
|
||||
let result: Nullable<IRange> = { ...range };
|
||||
operators.forEach((operator) => {
|
||||
@@ -1037,12 +1117,10 @@ export const handleCommonDefaultRangeChangeWithEffectRefCommands = (range: IRang
|
||||
return handleInsertRangeMoveRightCommon(commandInfo as IInsertRangeMoveRightCommand, range);
|
||||
}
|
||||
case EffectRefRangId.InsertColCommandId: {
|
||||
operator = handleInsertCol(commandInfo as IInsertColCommand, range);
|
||||
break;
|
||||
return handleInsertColCommon(commandInfo as IInsertColCommand, range);
|
||||
}
|
||||
case EffectRefRangId.InsertRowCommandId: {
|
||||
operator = handleInsertRow(commandInfo as IInsertRowCommand, range);
|
||||
break;
|
||||
return handleInsertRowCommon(commandInfo as IInsertRowCommand, range);
|
||||
}
|
||||
case EffectRefRangId.MoveColsCommandId: {
|
||||
return handleMoveColsCommon(commandInfo as IMoveColsCommand, range);
|
||||
@@ -1332,3 +1410,217 @@ export function getEffectedRangesOnMutation(mutation: IMutationInfo<MutationsAff
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function, complexity
|
||||
export function getSeparateEffectedRangesOnCommand(accessor: IAccessor, command: EffectRefRangeParams): {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
ranges: IRange[];
|
||||
} {
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const selectionManagerService = accessor.get(SheetsSelectionsService);
|
||||
|
||||
switch (command.id) {
|
||||
case EffectRefRangId.MoveColsCommandId: {
|
||||
const params = command.params!;
|
||||
const target = getSheetCommandTarget(univerInstanceService, {
|
||||
unitId: params.unitId,
|
||||
subUnitId: params.subUnitId,
|
||||
})!;
|
||||
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [
|
||||
params.fromRange,
|
||||
|
||||
{
|
||||
...params.toRange,
|
||||
startColumn: params.fromRange.startColumn < params.toRange.startColumn ? params.fromRange.endColumn + 1 : params.toRange.startColumn,
|
||||
endColumn: params.fromRange.startColumn < params.toRange.startColumn ? params.toRange.endColumn - 1 : params.fromRange.startColumn - 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.MoveRowsCommandId: {
|
||||
const params = command.params!;
|
||||
const target = getSheetCommandTarget(univerInstanceService, {
|
||||
unitId: params.unitId,
|
||||
subUnitId: params.subUnitId,
|
||||
})!;
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [
|
||||
params.fromRange,
|
||||
{
|
||||
...params.toRange,
|
||||
startRow: params.fromRange.startRow < params.toRange.startRow ? params.fromRange.endRow + 1 : params.toRange.startRow,
|
||||
endRow: params.fromRange.startRow < params.toRange.startRow ? params.toRange.endRow - 1 : params.fromRange.startRow - 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.MoveRangeCommandId: {
|
||||
const params = command.params!;
|
||||
const target = getSheetCommandTarget(univerInstanceService)!;
|
||||
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [params.fromRange, params.toRange],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.InsertRowCommandId: {
|
||||
const params = command.params!;
|
||||
const range: IRange = params.range;
|
||||
return {
|
||||
unitId: params.unitId,
|
||||
subUnitId: params.subUnitId,
|
||||
ranges: [
|
||||
...range.startRow > 0
|
||||
? [{
|
||||
...range,
|
||||
startRow: range.startRow - 1,
|
||||
endRow: range.endRow - 1,
|
||||
}]
|
||||
: [],
|
||||
{
|
||||
...range,
|
||||
startRow: range.startRow,
|
||||
endRow: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.InsertColCommandId: {
|
||||
const params = command.params!;
|
||||
const range: IRange = params.range;
|
||||
return {
|
||||
unitId: params.unitId,
|
||||
subUnitId: params.subUnitId,
|
||||
ranges: [
|
||||
...range.startColumn > 0
|
||||
? [{
|
||||
...range,
|
||||
startColumn: range.startColumn - 1,
|
||||
endColumn: range.endColumn - 1,
|
||||
}]
|
||||
: [],
|
||||
{
|
||||
...range,
|
||||
startColumn: range.startColumn,
|
||||
endColumn: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.RemoveRowCommandId: {
|
||||
const params = command.params!;
|
||||
const range: IRange = params.range;
|
||||
const target = getSheetCommandTarget(univerInstanceService)!;
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [
|
||||
range,
|
||||
{
|
||||
...range,
|
||||
startRow: range.endRow + 1,
|
||||
endRow: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.RemoveColCommandId: {
|
||||
const params = command.params!;
|
||||
const range: IRange = params.range;
|
||||
const target = getSheetCommandTarget(univerInstanceService)!;
|
||||
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [
|
||||
range,
|
||||
{
|
||||
...range,
|
||||
startColumn: range.endColumn + 1,
|
||||
endColumn: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.DeleteRangeMoveUpCommandId:
|
||||
case EffectRefRangId.InsertRangeMoveDownCommandId: {
|
||||
const params = command;
|
||||
const target = getSheetCommandTarget(univerInstanceService)!;
|
||||
const range = params.params?.range || selectionManagerService.getCurrentSelections()?.map((s) => s.range)?.[0];
|
||||
if (!range) {
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [],
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [
|
||||
range,
|
||||
{
|
||||
...range,
|
||||
startRow: range.endRow + 1,
|
||||
endRow: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.DeleteRangeMoveLeftCommandId:
|
||||
case EffectRefRangId.InsertRangeMoveRightCommandId: {
|
||||
const params = command;
|
||||
const range = params.params?.range || selectionManagerService.getCurrentSelections()?.map((s) => s.range)?.[0];
|
||||
const target = getSheetCommandTarget(univerInstanceService)!;
|
||||
if (!range) {
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [],
|
||||
};
|
||||
}
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: [
|
||||
range,
|
||||
{
|
||||
...range,
|
||||
startColumn: range.endColumn + 1,
|
||||
endColumn: Number.MAX_SAFE_INTEGER,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case EffectRefRangId.ReorderRangeCommandId: {
|
||||
const params = command;
|
||||
const { range, order } = params.params!;
|
||||
const effectRanges = [];
|
||||
for (let row = range.startRow; row <= range.endRow; row++) {
|
||||
if (row in order) {
|
||||
effectRanges.push({
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
startColumn: range.startColumn,
|
||||
endColumn: range.endColumn,
|
||||
});
|
||||
}
|
||||
}
|
||||
const target = getSheetCommandTarget(univerInstanceService)!;
|
||||
return {
|
||||
unitId: target.unitId,
|
||||
subUnitId: target.subUnitId,
|
||||
ranges: effectRanges,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user