feat(sheets-conditional-formatting): use other-formula-service on conditional-formatting (#6322)

This commit is contained in:
WEI ZHANG
2025-12-16 20:43:53 +08:00
committed by GitHub
parent ea6de15256
commit a1b7d95b65
14 changed files with 97 additions and 126 deletions
@@ -14018,7 +14018,7 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
v: 1,
},
10: {
v: 12313,
v: 10,
},
11: {
v: 123,
@@ -14035,6 +14035,11 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
v: 235,
},
},
3: {
10: {
v: 0,
},
},
4: {
5: {
v: 2,
@@ -14063,8 +14068,11 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
9: {
s: 'uJSelZ22',
},
10: {
v: 123,
},
11: {
v: 126,
v: 235,
},
},
6: {
@@ -14081,11 +14089,16 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
8: {
s: 'uJSelZ22',
},
11: {
v: 632,
},
},
7: {
10: {
v: 'ewe',
},
11: {
v: 893,
v: 235,
},
},
10: {
@@ -24480,7 +24493,7 @@ export const DEFAULT_WORKBOOK_DATA_DEMO: IWorkbookData = {
},
{
name: 'SHEET_CONDITIONAL_FORMATTING_PLUGIN',
data: '{"sheet-0011":[{"cfId":"mdFMCQle","ranges":[{"startRow":0,"startColumn":10,"endRow":7,"endColumn":11,"startAbsoluteRefType":0,"endAbsoluteRefType":0,"rangeType":0}],"rule":{"type":"highlightCell","subType":"text","operator":"containsText","style":{"cl":{"rgb":"#2f56ef"},"bg":{"rgb":"#e8ecfc"}},"value":"123"},"stopIfTrue":false}]}',
data: '{"sheet-0011":[{"cfId":"N3J3L50D","ranges":[{"startRow":4,"startColumn":11,"endRow":6,"endColumn":11,"startAbsoluteRefType":0,"endAbsoluteRefType":0}],"rule":{"style":{"cl":{"rgb":"#2f56ef"},"bg":{"rgb":"#e8ecfc"}},"value":"=L7=L5","type":"highlightCell","subType":"formula"},"stopIfTrue":false},{"cfId":"mdFMCQle","ranges":[{"startRow":0,"startColumn":10,"endRow":7,"endColumn":11,"startAbsoluteRefType":0,"endAbsoluteRefType":0,"rangeType":0}],"rule":{"type":"highlightCell","subType":"text","operator":"containsText","style":{"cl":{"rgb":"#2f56ef"},"bg":{"rgb":"#e8ecfc"}},"value":"123"},"stopIfTrue":false}]}',
},
{
name: 'SHEET_RANGE_PROTECTION_PLUGIN',
@@ -76,7 +76,8 @@
"@univerjs/core": "workspace:*",
"@univerjs/engine-formula": "workspace:*",
"@univerjs/engine-render": "workspace:*",
"@univerjs/sheets": "workspace:*"
"@univerjs/sheets": "workspace:*",
"@univerjs/sheets-formula": "workspace:*"
},
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
@@ -42,6 +42,7 @@ import {
SheetsSelectionsService,
} from '@univerjs/sheets';
import { ConditionalFormattingFormulaService, ConditionalFormattingRuleModel, ConditionalFormattingService, ConditionalFormattingViewModel } from '@univerjs/sheets-conditional-formatting';
import { RegisterOtherFormulaService } from '@univerjs/sheets-formula';
import enUS from '@univerjs/sheets/locale/en-US';
import zhCN from '@univerjs/sheets/locale/zh-CN';
@@ -150,6 +151,7 @@ export function createFacadeTestBed(workbookData?: IWorkbookData, dependencies?:
[ConditionalFormattingFormulaService],
[ConditionalFormattingRuleModel],
[ConditionalFormattingViewModel],
[RegisterOtherFormulaService],
[IActiveDirtyManagerService, { useClass: ActiveDirtyManagerService }],
[ISheetRowFilteredService, { useClass: SheetRowFilteredService }],
] as Dependency[]).forEach((d) => {
@@ -31,7 +31,6 @@ export {
DeleteConditionalRuleMutationUndoFactory,
type IDeleteConditionalRuleMutationParams,
} from './commands/mutations/delete-conditional-rule.mutation';
export { ConditionalFormattingFormulaMarkDirty } from './commands/mutations/formula-mark-dirty.mutation';
export {
type IMoveConditionalRuleMutationParams,
MoveConditionalRuleMutation,
@@ -18,9 +18,9 @@ import type { Dependency, IWorkbookData } from '@univerjs/core';
import { ICommandService, Inject, Injector, IUniverInstanceService, LocaleType, Plugin, Univer, UniverInstanceType } from '@univerjs/core';
import { IActiveDirtyManagerService } from '@univerjs/engine-formula';
import { SheetInterceptorService } from '@univerjs/sheets';
import { RegisterOtherFormulaService } from '@univerjs/sheets-formula';
import { AddConditionalRuleMutation } from '../../../commands/mutations/add-conditional-rule.mutation';
import { DeleteConditionalRuleMutation } from '../../../commands/mutations/delete-conditional-rule.mutation';
import { ConditionalFormattingFormulaMarkDirty } from '../../../commands/mutations/formula-mark-dirty.mutation';
import { MoveConditionalRuleMutation } from '../../../commands/mutations/move-conditional-rule.mutation';
import { SetConditionalRuleMutation } from '../../../commands/mutations/set-conditional-rule.mutation';
import { ConditionalFormattingRuleModel } from '../../../models/conditional-formatting-rule-model';
@@ -71,6 +71,7 @@ export const createTestBed = (dependencies?: Dependency[]) => {
super();
this._injector.add([SheetInterceptorService]);
this._injector.add([ConditionalFormattingService]);
this._injector.add([RegisterOtherFormulaService]);
this._injector.add([ConditionalFormattingFormulaService]);
this._injector.add([ConditionalFormattingRuleModel]);
this._injector.add([ConditionalFormattingViewModel]);
@@ -95,7 +96,6 @@ export const createTestBed = (dependencies?: Dependency[]) => {
DeleteConditionalRuleMutation,
SetConditionalRuleMutation,
MoveConditionalRuleMutation,
ConditionalFormattingFormulaMarkDirty,
].forEach((commandInfo) => {
commandService.registerCommand(commandInfo);
});
@@ -18,8 +18,8 @@ import type { IColorScale, IConditionFormattingRule } from '../type';
import type { IContext } from './base-calculate-unit';
import { CellValueType, ColorKit, ObjectMatrix, Range } from '@univerjs/core';
import { isObject } from '@univerjs/engine-render';
import { FormulaResultStatus } from '@univerjs/sheets-formula';
import { CFValueType } from '../../base/const';
import { FormulaResultStatus } from '../../services/conditional-formatting-formula.service';
import { BaseCalculateUnit, CalculateEmitStatus } from './base-calculate-unit';
import { filterRange, getColorScaleFromValue, getValueByType, isNullable } from './utils';
@@ -18,9 +18,9 @@ import type { ICellData } from '@univerjs/core';
import type { IConditionFormattingRule, IDataBar } from '../type';
import type { IContext } from './base-calculate-unit';
import { CellValueType, ObjectMatrix, Range } from '@univerjs/core';
import { FormulaResultStatus } from '@univerjs/sheets-formula';
import { CFValueType } from '../../base/const';
import { defaultDataBarNativeColor, defaultDataBarPositiveColor, defaultPlaceholderColor } from '../../render/data-bar.render';
import { FormulaResultStatus } from '../../services/conditional-formatting-formula.service';
import { BaseCalculateUnit, CalculateEmitStatus } from './base-calculate-unit';
import { filterRange, getValueByType, isNullable } from './utils';
@@ -19,8 +19,9 @@ import type { IAverageHighlightCell, IFormulaHighlightCell, IHighlightCell, INum
import type { IContext } from './base-calculate-unit';
import { CellValueType, dayjs, Range, Tools } from '@univerjs/core';
import { ERROR_TYPE_SET } from '@univerjs/engine-formula';
import { FormulaResultStatus } from '@univerjs/sheets-formula';
import { CFNumberOperator, CFSubRuleType, CFTextOperator, CFTimePeriodOperator } from '../../base/const';
import { ConditionalFormattingFormulaService, FormulaResultStatus } from '../../services/conditional-formatting-formula.service';
import { ConditionalFormattingFormulaService } from '../../services/conditional-formatting-formula.service';
import { BaseCalculateUnit, CalculateEmitStatus } from './base-calculate-unit';
import { compareWithNumber, getCellValue, isFloatsEqual, isNullable, serialTimeToTimestamp } from './utils';
@@ -376,7 +377,12 @@ export class HighlightCellCalculateUnit extends BaseCalculateUnit<Nullable<IConf
// const _ruleConfig = ruleConfig as IFormulaHighlightCell;
const cache = preComputingResult?.value;
if (cache) {
const value = cache.getValue(row, col);
// The formula result matrix starts from (0,0), but we need to use relative coordinates
// based on the first range's start position
const firstRange = context.rule.ranges[0];
const relativeRow = row - firstRange.startRow;
const relativeCol = col - firstRange.startColumn;
const value = cache.getValue(relativeRow, relativeCol);
return value === true;
}
return false;
@@ -19,8 +19,8 @@ import type { CFNumberOperator } from '../../base/const';
import type { IIconSet } from '../type';
import type { IContext } from './base-calculate-unit';
import { CellValueType, ObjectMatrix, Range } from '@univerjs/core';
import { FormulaResultStatus } from '@univerjs/sheets-formula';
import { CFValueType } from '../../base/const';
import { FormulaResultStatus } from '../../services/conditional-formatting-formula.service';
import { BaseCalculateUnit, CalculateEmitStatus } from './base-calculate-unit';
import { compareWithNumber, filterRange, getOppositeOperator, getValueByType, isNullable } from './utils';
@@ -19,8 +19,9 @@ import type { IConditionFormattingRule, IValueConfig } from '../../models/type';
import type { IContext } from './base-calculate-unit';
import { BooleanNumber, CellValueType, ColorKit, dayjs, ObjectMatrix, Range } from '@univerjs/core';
import { BooleanValue } from '@univerjs/engine-formula';
import { FormulaResultStatus } from '@univerjs/sheets-formula';
import { CFNumberOperator, CFValueType } from '../../base/const';
import { ConditionalFormattingFormulaService, FormulaResultStatus } from '../../services/conditional-formatting-formula.service';
import { ConditionalFormattingFormulaService } from '../../services/conditional-formatting-formula.service';
import { ConditionalFormattingViewModel } from '../conditional-formatting-view-model';
export function isFloatsEqual(a: number, b: number) {
@@ -26,7 +26,6 @@ import { MoveCfCommand } from './commands/commands/move-cf.command';
import { SetCfCommand } from './commands/commands/set-cf.command';
import { AddConditionalRuleMutation } from './commands/mutations/add-conditional-rule.mutation';
import { DeleteConditionalRuleMutation } from './commands/mutations/delete-conditional-rule.mutation';
import { ConditionalFormattingFormulaMarkDirty } from './commands/mutations/formula-mark-dirty.mutation';
import { MoveConditionalRuleMutation } from './commands/mutations/move-conditional-rule.mutation';
import { SetConditionalRuleMutation } from './commands/mutations/set-conditional-rule.mutation';
import {
@@ -78,7 +77,6 @@ export class UniverSheetsConditionalFormattingPlugin extends Plugin {
DeleteConditionalRuleMutation,
SetConditionalRuleMutation,
MoveConditionalRuleMutation,
ConditionalFormattingFormulaMarkDirty,
].forEach((m) => {
this._commandService.registerCommand(m);
});
@@ -15,21 +15,13 @@
*/
import type { ICellData, IRange, Nullable } from '@univerjs/core';
import type { IRemoveOtherFormulaMutationParams, ISetFormulaCalculationResultMutation, ISetOtherFormulaMutationParams } from '@univerjs/engine-formula';
import type { IConditionalFormattingFormulaMarkDirtyParams } from '../commands/mutations/formula-mark-dirty.mutation';
import type { IOtherFormulaResult } from '@univerjs/sheets-formula';
import type { IConditionalFormattingRuleConfig } from '../models/type';
import { BooleanNumber, CellValueType, Disposable, generateRandomId, ICommandService, Inject, ObjectMatrix, RefAlias } from '@univerjs/core';
import {
IActiveDirtyManagerService,
RemoveOtherFormulaMutation,
SetFormulaCalculationResultMutation,
SetOtherFormulaMutation,
} from '@univerjs/engine-formula';
import { BooleanNumber, CellValueType, Disposable, Inject, ObjectMatrix, RefAlias } from '@univerjs/core';
import { FormulaResultStatus, RegisterOtherFormulaService } from '@univerjs/sheets-formula';
import { Subject } from 'rxjs';
import { CFRuleType, CFValueType } from '../base/const';
import { ConditionalFormattingFormulaMarkDirty } from '../commands/mutations/formula-mark-dirty.mutation';
import { ConditionalFormattingRuleModel } from '../models/conditional-formatting-rule-model';
// eslint-disable-next-line ts/consistent-type-definitions
@@ -39,24 +31,7 @@ type IFormulaItem = {
id: string;
unitId: string;
subUnitId: string;
ranges: IRange[];
status: FormulaResultStatus;
formulaId: string;
result: ObjectMatrix<string | number | boolean | undefined | null | void>;
};
export enum FormulaResultStatus {
NOT_REGISTER = 1,
SUCCESS,
WAIT,
ERROR,
}
const getResultFromFormula = (formulaResult: Nullable<ICellData>[][]) => {
const v = formulaResult && formulaResult[0] && formulaResult[0][0];
if (v?.t === CellValueType.BOOLEAN) {
return v.v === BooleanNumber.TRUE || v.v === true;
}
return v ? v.v : false;
};
// TODO: @Gggpound
// It may be possible later to abstract a service that manages the results of an asynchronous calculation to handle the use of the last calculation before waiting for the result to return.
@@ -68,13 +43,11 @@ export class ConditionalFormattingFormulaService extends Disposable {
public result$ = this._result$.asObservable();
constructor(
@Inject(ICommandService) private _commandService: ICommandService,
@Inject(IActiveDirtyManagerService) private _activeDirtyManagerService: IActiveDirtyManagerService,
@Inject(RegisterOtherFormulaService) private _registerOtherFormulaService: RegisterOtherFormulaService,
@Inject(ConditionalFormattingRuleModel) private _conditionalFormattingRuleModel: ConditionalFormattingRuleModel
) {
super();
this._initFormulaCalculationResultChange();
this._initFormulaResultChange();
this._initRuleChange();
}
@@ -105,54 +78,28 @@ export class ConditionalFormattingFormulaService extends Disposable {
}));
}
private _initFormulaCalculationResultChange() {
// Gets the result of the formula calculation and caches it
this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => {
if (commandInfo.id === SetFormulaCalculationResultMutation.id) {
const params = commandInfo.params as ISetFormulaCalculationResultMutation;
for (const unitId in params.unitOtherData) {
for (const subUnitId in params.unitOtherData[unitId]) {
for (const formulaId in params.unitOtherData[unitId]![subUnitId]) {
const resultMatrix = new ObjectMatrix(params.unitOtherData[unitId][subUnitId][formulaId]);
const formulaMapAlias = this._ensureSubunitFormulaMap(unitId, subUnitId).getValue(formulaId, ['formulaId']);
if (!formulaMapAlias) {
continue;
}
const ranges = formulaMapAlias.ranges;
if (!ranges) {
continue;
}
const resultObject = formulaMapAlias.result;
// The engine's calculation result only has the offset, and the actual position needs to be calculated from the upper left corner.
const startRow = ranges[0].startRow;
const startCol = ranges[0].startColumn;
resultMatrix.forValue((row, col, value) => {
resultObject.setValue(startRow + row, startCol + col, getResultFromFormula(value));
});
formulaMapAlias.status = FormulaResultStatus.SUCCESS;
const allFormulaMapAlias = this._getAllFormulaResultByCfId(unitId, subUnitId, formulaMapAlias.cfId);
const isAllFinished = allFormulaMapAlias.every((item) => item.status === FormulaResultStatus.SUCCESS);
this._result$.next({ ...formulaMapAlias, isAllFinished });
private _initFormulaResultChange() {
// Subscribe to formula calculation results
this.disposeWithMe(this._registerOtherFormulaService.formulaResult$.subscribe((results: Record<string, Record<string, IOtherFormulaResult[]>>) => {
for (const unitId in results) {
for (const subUnitId in results[unitId]) {
const subUnitResults = results[unitId][subUnitId];
for (const formulaResult of subUnitResults) {
const formulaMapAlias = this._ensureSubunitFormulaMap(unitId, subUnitId).getValue(formulaResult.formulaId, ['formulaId']);
if (!formulaMapAlias) {
continue;
}
const allFormulaMapAlias = this._getAllFormulaResultByCfId(unitId, subUnitId, formulaMapAlias.cfId);
const isAllFinished = allFormulaMapAlias.every((item) => {
const result = this._registerOtherFormulaService.getFormulaValueSync(unitId, subUnitId, item.formulaId);
return result?.status === FormulaResultStatus.SUCCESS;
});
this._result$.next({ ...formulaMapAlias, isAllFinished });
}
}
}
}));
// Register formula with Dirty Logic
this._activeDirtyManagerService.register(ConditionalFormattingFormulaMarkDirty.id, {
commandId: ConditionalFormattingFormulaMarkDirty.id,
getDirtyData(commandInfo) {
const params = commandInfo.params as IConditionalFormattingFormulaMarkDirtyParams;
return {
dirtyUnitOtherFormulaMap: params,
};
},
});
}
private _ensureSubunitFormulaMap(unitId: string, subUnitId: string) {
@@ -179,38 +126,21 @@ export class ConditionalFormattingFormulaService extends Disposable {
if (formulaMap.getValue(cfFormulaId, ['id'])) {
return;
}
const formulaId = this._createFormulaId(unitId, subUnitId);
const formulaId = this._registerOtherFormulaService.registerFormulaWithRange(unitId, subUnitId, formulaText, ranges);
formulaMap.addValue({
formulaText,
unitId,
subUnitId,
cfId,
id: cfFormulaId,
ranges,
formulaId,
status: FormulaResultStatus.WAIT,
result: new ObjectMatrix(),
});
const params: ISetOtherFormulaMutationParams = {
unitId,
subUnitId,
formulaMap: {
[formulaId]: {
f: formulaText,
ranges,
},
},
};
this._commandService.executeCommand(SetOtherFormulaMutation.id, params, { onlyLocal: true }).then(() => {
this._commandService.executeCommand(ConditionalFormattingFormulaMarkDirty.id, { [unitId]: { [subUnitId]: { [formulaId]: true } } }, { onlyLocal: true });
});
}
private _removeFormulaByCfId(unitId: string, subUnitId: string, cfId: string) {
const values = this.deleteCache(unitId, subUnitId, cfId);
const formulaIdList = values.map((item) => item.formulaId);
this._commandService.executeCommand(RemoveOtherFormulaMutation.id, { unitId, subUnitId, formulaIdList } as IRemoveOtherFormulaMutationParams, { onlyLocal: true });
this._registerOtherFormulaService.deleteFormula(unitId, subUnitId, formulaIdList);
}
public getFormulaResultWithCoords(unitId: string, subUnitId: string, cfId: string, formulaText: string, row: number = 0, col: number = 0) {
@@ -222,16 +152,18 @@ export class ConditionalFormattingFormulaService extends Disposable {
if (!item) {
return { status: FormulaResultStatus.NOT_REGISTER };
}
if (FormulaResultStatus.SUCCESS === item.status && item.result) {
const result = item.result.getValue(row, col);
const formulaResult = this._registerOtherFormulaService.getFormulaValueSync(unitId, subUnitId, item.formulaId);
if (!formulaResult) {
return { status: FormulaResultStatus.NOT_REGISTER };
}
if (formulaResult.status === FormulaResultStatus.SUCCESS && formulaResult.result) {
const cellData = formulaResult.result[row]?.[col];
const result = this._getCellValue(cellData);
return { result, status: FormulaResultStatus.SUCCESS };
}
if (item.status === FormulaResultStatus.WAIT) {
return { status: FormulaResultStatus.WAIT };
}
return { status: FormulaResultStatus.ERROR };
return { status: formulaResult.status };
}
public getFormulaMatrix(unitId: string, subUnitId: string, cfId: string, formulaText: string) {
@@ -243,10 +175,32 @@ export class ConditionalFormattingFormulaService extends Disposable {
if (!item) {
return { status: FormulaResultStatus.NOT_REGISTER };
}
if (FormulaResultStatus.SUCCESS === item.status && item.result) {
const result = item.result;
const formulaResult = this._registerOtherFormulaService.getFormulaValueSync(unitId, subUnitId, item.formulaId);
if (!formulaResult) {
return { status: FormulaResultStatus.NOT_REGISTER };
}
if (formulaResult.status === FormulaResultStatus.SUCCESS && formulaResult.result) {
const result = new ObjectMatrix<string | number | boolean | undefined | null | void>();
const resultMatrix = new ObjectMatrix(formulaResult.result);
resultMatrix.forValue((row, col, cellData) => {
result.setValue(row, col, this._getCellValue(cellData));
});
return { result, status: FormulaResultStatus.SUCCESS };
}
return { status: formulaResult.status };
}
private _getCellValue(cellData: Nullable<ICellData>[][]): string | number | boolean | undefined | null | void {
if (!cellData || !cellData[0] || !cellData[0][0]) {
return false;
}
const cell = cellData[0][0];
if (cell?.t === CellValueType.BOOLEAN) {
return cell.v === BooleanNumber.TRUE || cell.v === true;
}
return cell?.v;
}
/**
@@ -279,13 +233,6 @@ export class ConditionalFormattingFormulaService extends Disposable {
return values;
}
/**
* The external environment is not aware of`formulaId`;it communicates internally with the formula engine.
*/
private _createFormulaId(unitId: string, subUnitId: string) {
return `sheet.cf_${unitId}_${subUnitId}_${generateRandomId(8)}`;
}
/**
* A conditional formatting may have multiple formulas;if the formulas are identical,then the results will be consistent.
*/
+1
View File
@@ -27,6 +27,7 @@ export { UniverRemoteSheetsFormulaPlugin, UniverSheetsFormulaPlugin } from './pl
export { DescriptionService, IDescriptionService } from './services/description.service';
export type { ISearchItem, ISearchItemWithType } from './services/description.service';
export type { IFormulaInfo, IOtherFormulaResult } from './services/formula-common';
export { FormulaResultStatus } from './services/formula-common';
export { FormulaRefRangeService } from './services/formula-ref-range.service';
export type { IRegisterAsyncFunction, IRegisterFunction, ISingleFunctionRegisterParams } from './services/register-function.service';
export type { IRegisterFunctionParams, IUnregisterFunctionParams } from './services/register-function.service';
+3
View File
@@ -1970,6 +1970,9 @@ importers:
'@univerjs/sheets':
specifier: workspace:*
version: link:../sheets
'@univerjs/sheets-formula':
specifier: workspace:*
version: link:../sheets-formula
devDependencies:
'@univerjs-infra/shared':
specifier: workspace:*