diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 860afdc5e7..34bf4d2b7f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -140,7 +140,7 @@ export { isBlackColor, isWhiteColor } from './shared/color/color-kit'; export { cellToRange } from './shared/common'; export { getIntersectRange } from './shared/range'; export { nameCharacterCheck } from './shared/name'; -export { fromCallback, takeAfter } from './shared/rxjs'; +export { bufferDebounceTime, fromCallback, takeAfter } from './shared/rxjs'; export { awaitTime } from './shared/timer'; export { Range } from './sheets/range'; export { diff --git a/packages/core/src/shared/rxjs.ts b/packages/core/src/shared/rxjs.ts index 33e6a5ffb1..6c82a7d25a 100644 --- a/packages/core/src/shared/rxjs.ts +++ b/packages/core/src/shared/rxjs.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { Observable } from 'rxjs'; +import type { OperatorFunction } from 'rxjs'; import type { IDisposable } from '../common/di'; +import { debounceTime, map, Observable, tap } from 'rxjs'; type CallbackFn = (cb: (...args: T) => void) => IDisposable; @@ -57,3 +58,15 @@ export function takeAfter(callback: (value: T) => boolean) { }; } +export function bufferDebounceTime(time: number = 0): OperatorFunction { + return (source: Observable) => { + let bufferedValues: T[] = []; + + return source.pipe( + tap((value) => bufferedValues.push(value)), + debounceTime(time), + map(() => bufferedValues), + tap(() => bufferedValues = []) + ); + }; +} diff --git a/packages/data-validation/src/index.ts b/packages/data-validation/src/index.ts index c3720478f2..d925bd6cbc 100644 --- a/packages/data-validation/src/index.ts +++ b/packages/data-validation/src/index.ts @@ -20,7 +20,7 @@ export { } from './common/util'; export { DataValidationResourceController } from './controllers/dv-resource.controller'; -export { type DataValidationChangeSource, DataValidationModel, type IRuleChange, type IValidStatusChange } from './models/data-validation-model'; +export { type DataValidationChangeSource, DataValidationModel, type IRuleChange } from './models/data-validation-model'; export { UniverDataValidationPlugin } from './plugin'; export { DataValidatorRegistryScope, DataValidatorRegistryService } from './services/data-validator-registry.service'; diff --git a/packages/data-validation/src/models/data-validation-model.ts b/packages/data-validation/src/models/data-validation-model.ts index 1502bf1688..95159c28cb 100644 --- a/packages/data-validation/src/models/data-validation-model.ts +++ b/packages/data-validation/src/models/data-validation-model.ts @@ -14,12 +14,12 @@ * limitations under the License. */ +import type { IDataValidationRule } from '@univerjs/core'; +import type { IUpdateRulePayload } from '../types/interfaces/i-update-rule-payload'; import { Disposable, ILogService, Tools } from '@univerjs/core'; import { debounceTime, Subject } from 'rxjs'; -import type { DataValidationStatus, IDataValidationRule } from '@univerjs/core'; import { getRuleOptions, getRuleSetting } from '../common/util'; import { UpdateRuleType } from '../types/enum/update-rule-type'; -import type { IUpdateRulePayload } from '../types/interfaces/i-update-rule-payload'; type DataValidationChangeType = 'update' | 'add' | 'remove'; export type DataValidationChangeSource = 'command' | 'patched'; @@ -34,13 +34,6 @@ export interface IRuleChange { oldRule?: IDataValidationRule; } -export interface IValidStatusChange { - unitId: string; - subUnitId: string; - ruleId: string; - status: DataValidationStatus; -} - interface ISubUnitDataValidation { map: Map; list: IDataValidationRule[]; } diff --git a/packages/facade/src/apis/sheets/f-workbook.ts b/packages/facade/src/apis/sheets/f-workbook.ts index 7e1a2b7089..f924e9133a 100644 --- a/packages/facade/src/apis/sheets/f-workbook.ts +++ b/packages/facade/src/apis/sheets/f-workbook.ts @@ -15,13 +15,13 @@ */ import type { CommandListener, ICommandInfo, IDisposable, IExecutionOptions, IRange, IWorkbookData, Nullable, ObjectMatrix, Workbook } from '@univerjs/core'; -import type { IRuleChange, IValidStatusChange } from '@univerjs/data-validation'; +import type { IRuleChange } from '@univerjs/data-validation'; import type { IUpdateCommandParams } from '@univerjs/docs-ui'; import type { ISetSelectionsOperationParams, ISheetCommandSharedParams, } from '@univerjs/sheets'; -import type { IAddSheetDataValidationCommandParams, IDataValidationResCache, IRemoveSheetAllDataValidationCommandParams, IRemoveSheetDataValidationCommandParams, IUpdateSheetDataValidationOptionsCommandParams, IUpdateSheetDataValidationRangeCommandParams, IUpdateSheetDataValidationSettingCommandParams } from '@univerjs/sheets-data-validation'; +import type { IAddSheetDataValidationCommandParams, IDataValidationResCache, IRemoveSheetAllDataValidationCommandParams, IRemoveSheetDataValidationCommandParams, IUpdateSheetDataValidationOptionsCommandParams, IUpdateSheetDataValidationRangeCommandParams, IUpdateSheetDataValidationSettingCommandParams, IValidStatusChange } from '@univerjs/sheets-data-validation'; import type { ICanvasFloatDom } from '@univerjs/sheets-drawing-ui'; import type { ISheetHyperLinkInfo } from '@univerjs/sheets-hyper-link-ui'; import type { CommentUpdate, IAddCommentCommandParams, IDeleteCommentCommandParams } from '@univerjs/thread-comment'; diff --git a/packages/facade/src/apis/sheets/f-worksheet.ts b/packages/facade/src/apis/sheets/f-worksheet.ts index d3cb516d19..b188af3b14 100644 --- a/packages/facade/src/apis/sheets/f-worksheet.ts +++ b/packages/facade/src/apis/sheets/f-worksheet.ts @@ -21,9 +21,10 @@ import type { FilterModel } from '@univerjs/sheets-filter'; import type { FWorkbook, IFICanvasFloatDom } from './f-workbook'; import { Direction, ICommandService, Inject, Injector, ObjectMatrix, RANGE_TYPE } from '@univerjs/core'; +import { DataValidationModel } from '@univerjs/data-validation'; import { deserializeRangeWithSheet } from '@univerjs/engine-formula'; import { copyRangeStyles, InsertColCommand, InsertRowCommand, MoveColsCommand, MoveRowsCommand, RemoveColCommand, RemoveRowCommand, SetColHiddenCommand, SetColWidthCommand, SetFrozenCommand, SetRangeValuesMutation, SetRowHeightCommand, SetRowHiddenCommand, SetSpecificColsVisibleCommand, SetSpecificRowsVisibleCommand, SetWorksheetRowIsAutoHeightCommand, SheetsSelectionsService } from '@univerjs/sheets'; -import { DataValidationModel, SheetsDataValidationValidatorService } from '@univerjs/sheets-data-validation'; +import { SheetsDataValidationValidatorService } from '@univerjs/sheets-data-validation'; import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui'; import { SheetsFilterService } from '@univerjs/sheets-filter'; import { SheetsThreadCommentModel } from '@univerjs/sheets-thread-comment'; diff --git a/packages/sheets-data-validation-ui/src/controllers/dv-render.controller.ts b/packages/sheets-data-validation-ui/src/controllers/dv-render.controller.ts index 88f2169901..aa7edf66f5 100644 --- a/packages/sheets-data-validation-ui/src/controllers/dv-render.controller.ts +++ b/packages/sheets-data-validation-ui/src/controllers/dv-render.controller.ts @@ -19,7 +19,7 @@ import { DataValidationStatus, DataValidationType, ICommandService, Inject, Inte import { DataValidatorRegistryService } from '@univerjs/data-validation'; import { IRenderManagerService } from '@univerjs/engine-render'; import { InterceptCellContentPriority, INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets'; -import { getCellValueOrigin, SheetDataValidationModel } from '@univerjs/sheets-data-validation'; +import { DataValidationCacheService, getCellValueOrigin, SheetDataValidationModel } from '@univerjs/sheets-data-validation'; import { AutoHeightController, IEditorBridgeService, SheetSkeletonManagerService } from '@univerjs/sheets-ui'; import { IMenuManagerService } from '@univerjs/ui'; import { bufferTime, filter } from 'rxjs'; @@ -44,6 +44,7 @@ export class SheetsDataValidationRenderController extends RxDisposable { @Inject(SheetDataValidationModel) private readonly _sheetDataValidationModel: SheetDataValidationModel, @Inject(DataValidatorRegistryService) private readonly _dataValidatorRegistryService: DataValidatorRegistryService, @Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService, + @Inject(DataValidationCacheService) private readonly _dataValidationCacheService: DataValidationCacheService, @Optional(IEditorBridgeService) private readonly _editorBridgeService?: IEditorBridgeService ) { super(); @@ -145,7 +146,7 @@ export class SheetsDataValidationRenderController extends RxDisposable { if (!rule) { return next(cell); } - const validStatus = this._sheetDataValidationModel.validator(cell, rule, pos); + const validStatus = this._dataValidationCacheService.getValue(unitId, subUnitId, row, col)?.status ?? DataValidationStatus.VALID; const validator = this._dataValidatorRegistryService.getValidatorItem(rule.type); const cellOrigin = pos.rawData; let cache: Nullable; @@ -264,7 +265,8 @@ export class SheetsDataValidationMobileRenderController extends RxDisposable { @Inject(AutoHeightController) private readonly _autoHeightController: AutoHeightController, @Inject(DataValidatorRegistryService) private readonly _dataValidatorRegistryService: DataValidatorRegistryService, @Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService, - @Inject(SheetDataValidationModel) private readonly _sheetDataValidationModel: SheetDataValidationModel + @Inject(SheetDataValidationModel) private readonly _sheetDataValidationModel: SheetDataValidationModel, + @Inject(DataValidationCacheService) private readonly _dataValidationCacheService: DataValidationCacheService ) { super(); @@ -293,7 +295,7 @@ export class SheetsDataValidationMobileRenderController extends RxDisposable { if (!rule) { return next(cell); } - const validStatus = this._sheetDataValidationModel.validator(cell, rule, pos); + const validStatus = this._dataValidationCacheService.getValue(unitId, subUnitId, row, col)?.status ?? DataValidationStatus.VALID; const validator = this._dataValidatorRegistryService.getValidatorItem(rule.type); const cellOrigin = worksheet.getCellRaw(row, col); const cellValue = getCellValueOrigin(cellOrigin); diff --git a/packages/sheets-data-validation-ui/src/controllers/dv-rerender.controller.ts b/packages/sheets-data-validation-ui/src/controllers/dv-rerender.controller.ts new file mode 100644 index 0000000000..82f10cb8ef --- /dev/null +++ b/packages/sheets-data-validation-ui/src/controllers/dv-rerender.controller.ts @@ -0,0 +1,51 @@ +/** + * 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 { Workbook } from '@univerjs/core'; +import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; +import type { IValidStatusChange } from '@univerjs/sheets-data-validation'; +import { bufferDebounceTime, Disposable, Inject } from '@univerjs/core'; +import { SheetDataValidationModel } from '@univerjs/sheets-data-validation'; +import { SheetSkeletonManagerService } from '@univerjs/sheets-ui'; + +export class SheetsDataValidationReRenderController extends Disposable implements IRenderModule { + constructor( + private readonly _context: IRenderContext, + @Inject(SheetDataValidationModel) private readonly _sheetDataValidationModel: SheetDataValidationModel, + @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService + ) { + super(); + this._initSkeletonChange(); + } + + private _initSkeletonChange() { + const reRender = (values: IValidStatusChange[]) => { + if (!values.length) { + return; + } + const sheetIds = new Set(); + values.forEach((value) => { + sheetIds.add(value.subUnitId); + }); + sheetIds.forEach((sheetId) => { + this._sheetSkeletonManagerService.getWorksheetSkeleton(sheetId)?.skeleton.makeDirty(true); + }); + this._context.mainComponent?.makeForceDirty(); + }; + + this.disposeWithMe(this._sheetDataValidationModel.validStatusChange$.pipe(bufferDebounceTime(16)).subscribe(reRender)); + } +} diff --git a/packages/sheets-data-validation-ui/src/mobile-plugin.ts b/packages/sheets-data-validation-ui/src/mobile-plugin.ts index 1bad08f38a..7c7d979b6e 100644 --- a/packages/sheets-data-validation-ui/src/mobile-plugin.ts +++ b/packages/sheets-data-validation-ui/src/mobile-plugin.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import type { Dependency } from '@univerjs/core'; +import type { Dependency, Workbook } from '@univerjs/core'; import type { IUniverSheetsDataValidationUIConfig } from './controllers/config.schema'; import { ICommandService, IConfigService, Inject, Injector, Plugin, UniverInstanceType } from '@univerjs/core'; +import { IRenderManagerService } from '@univerjs/engine-render'; import { AddSheetDataValidationAndOpenCommand } from './commands/commands/data-validation-ui.command'; import { CloseValidationPanelOperation, @@ -31,6 +32,7 @@ import { DataValidationAutoFillController } from './controllers/dv-auto-fill.con import { DataValidationCopyPasteController } from './controllers/dv-copy-paste.controller'; import { DataValidationPermissionController } from './controllers/dv-permission.controller'; import { SheetsDataValidationRenderController } from './controllers/dv-render.controller'; +import { SheetsDataValidationReRenderController } from './controllers/dv-rerender.controller'; import { SheetsDataValidationUIController } from './controllers/dv-ui.controller'; import { DataValidationPanelService } from './services/data-validation-panel.service'; import { DataValidationDropdownManagerService } from './services/dropdown-manager.service'; @@ -86,6 +88,12 @@ export class UniverSheetsDataValidationMobileUIPlugin extends Plugin { override onReady(): void { this._injector.get(DataValidationCopyPasteController); this._injector.get(DataValidationPermissionController); + + const renderManager = this._injector.get(IRenderManagerService); + renderManager.registerRenderModule( + UniverInstanceType.UNIVER_SHEET, + [SheetsDataValidationReRenderController] as Dependency + ); } override onRendered(): void { diff --git a/packages/sheets-data-validation-ui/src/plugin.ts b/packages/sheets-data-validation-ui/src/plugin.ts index 495bf5b437..ca8e2e5a33 100644 --- a/packages/sheets-data-validation-ui/src/plugin.ts +++ b/packages/sheets-data-validation-ui/src/plugin.ts @@ -14,9 +14,10 @@ * limitations under the License. */ -import type { Dependency } from '@univerjs/core'; +import type { Dependency, Workbook } from '@univerjs/core'; import type { IUniverSheetsDataValidationUIConfig } from './controllers/config.schema'; import { ICommandService, IConfigService, Inject, Injector, Plugin, UniverInstanceType } from '@univerjs/core'; +import { IRenderManagerService } from '@univerjs/engine-render'; import { AddSheetDataValidationAndOpenCommand } from './commands/commands/data-validation-ui.command'; import { CloseValidationPanelOperation, @@ -32,6 +33,7 @@ import { DataValidationCopyPasteController } from './controllers/dv-copy-paste.c import { DataValidationPermissionController } from './controllers/dv-permission.controller'; import { DataValidationRejectInputController } from './controllers/dv-reject-input.controller'; import { SheetsDataValidationRenderController } from './controllers/dv-render.controller'; +import { SheetsDataValidationReRenderController } from './controllers/dv-rerender.controller'; import { SheetsDataValidationUIController } from './controllers/dv-ui.controller'; import { DataValidationPanelService } from './services/data-validation-panel.service'; import { DataValidationDropdownManagerService } from './services/dropdown-manager.service'; @@ -90,6 +92,12 @@ export class UniverSheetsDataValidationUIPlugin extends Plugin { this._injector.get(DataValidationPermissionController); this._injector.get(DataValidationRejectInputController); this._injector.get(DataValidationAlertController); + + const renderManager = this._injector.get(IRenderManagerService); + renderManager.registerRenderModule( + UniverInstanceType.UNIVER_SHEET, + [SheetsDataValidationReRenderController] as Dependency + ); } override onRendered(): void { diff --git a/packages/sheets-data-validation/src/index.ts b/packages/sheets-data-validation/src/index.ts index bbb80325c4..a83e40299d 100644 --- a/packages/sheets-data-validation/src/index.ts +++ b/packages/sheets-data-validation/src/index.ts @@ -15,7 +15,7 @@ */ export { DATA_VALIDATION_PLUGIN_NAME } from './common/const'; -export { SheetDataValidationModel } from './models/sheet-data-validation-model'; +export { type IValidStatusChange, SheetDataValidationModel } from './models/sheet-data-validation-model'; export { UniverSheetsDataValidationPlugin } from './plugin'; export { DataValidationCacheService } from './services/dv-cache.service'; export type { IDataValidationResCache } from './services/dv-cache.service'; @@ -32,9 +32,6 @@ export { CHECKBOX_FORMULA_1, CHECKBOX_FORMULA_2, CheckboxValidator, transformChe export { ListMultipleValidator } from './validators/list-multiple-validator'; export { deserializeListOptions, getDataValidationCellValue, serializeListOptions } from './validators/util'; -// TODO: @jikkai: I think this is a mistake, it should be exported from here. @weird94 -export { DataValidationModel } from '@univerjs/data-validation'; - // #region - all commands export { diff --git a/packages/sheets-data-validation/src/models/sheet-data-validation-model.ts b/packages/sheets-data-validation/src/models/sheet-data-validation-model.ts index da0164f1f8..1991ce0d32 100644 --- a/packages/sheets-data-validation/src/models/sheet-data-validation-model.ts +++ b/packages/sheets-data-validation/src/models/sheet-data-validation-model.ts @@ -14,10 +14,10 @@ * limitations under the License. */ -import type { DataValidationType, ICellDataForSheetInterceptor, ISheetDataValidationRule, Nullable } from '@univerjs/core'; -import type { IRuleChange, IUpdateRulePayload, IValidStatusChange } from '@univerjs/data-validation'; +import type { ISheetDataValidationRule } from '@univerjs/core'; +import type { IRuleChange, IUpdateRulePayload } from '@univerjs/data-validation'; import type { IRemoveSheetMutationParams, ISheetLocation } from '@univerjs/sheets'; -import { DataValidationStatus, Disposable, ICommandService, Inject, IUniverInstanceService } from '@univerjs/core'; +import { DataValidationStatus, DataValidationType, Disposable, ICommandService, Inject, IUniverInstanceService } from '@univerjs/core'; import { DataValidationModel, DataValidatorRegistryService, UpdateRuleType } from '@univerjs/data-validation'; import { RemoveSheetMutation } from '@univerjs/sheets'; import { Subject } from 'rxjs'; @@ -27,6 +27,15 @@ import { DataValidationFormulaService } from '../services/dv-formula.service'; import { getCellValueOrigin } from '../utils/get-cell-data-origin'; import { RuleMatrix } from './rule-matrix'; +export interface IValidStatusChange { + unitId: string; + subUnitId: string; + row: number; + col: number; + ruleId: string; + status: DataValidationStatus; +} + export class SheetDataValidationModel extends Disposable { private readonly _ruleMatrixMap = new Map>(); private readonly _validStatusChange$ = new Subject(); @@ -132,7 +141,9 @@ export class SheetDataValidationModel extends Disposable { ruleMatrix.addRule(rule); this._dataValidationCacheService.addRule(unitId, subUnitId, rule); this._dataValidationFormulaService.addRule(unitId, subUnitId, rule.uid, rule.formula1, rule.formula2); - this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, rule); + if (rule.type === DataValidationType.CUSTOM) { + this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, rule); + } } private _addRule(unitId: string, subUnitId: string, rule: ISheetDataValidationRule | ISheetDataValidationRule[]): void { @@ -148,11 +159,20 @@ export class SheetDataValidationModel extends Disposable { if (payload.type === UpdateRuleType.RANGE) { ruleMatrix.updateRange(ruleId, payload.payload); this._dataValidationCacheService.updateRuleRanges(unitId, subUnitId, ruleId, payload.payload, oldRule.ranges); - this._dataValidationCustomFormulaService.updateRuleRanges(unitId, subUnitId, ruleId, oldRule.ranges, payload.payload); + if (oldRule.type === DataValidationType.CUSTOM) { + this._dataValidationCustomFormulaService.updateRuleRanges(unitId, subUnitId, ruleId, 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); - this._dataValidationCustomFormulaService.updateRuleFormula(unitId, subUnitId, ruleId, oldRule.ranges, payload.payload.formula1!); + if (oldRule.type === DataValidationType.CUSTOM) { + this._dataValidationCustomFormulaService.updateRuleFormula(unitId, subUnitId, ruleId, oldRule.ranges, payload.payload.formula1!); + } else if (payload.payload.type === DataValidationType.CUSTOM) { + this._dataValidationCustomFormulaService.addRule(unitId, subUnitId, { + ...oldRule, + ...payload.payload, + }); + } } } @@ -160,6 +180,9 @@ export class SheetDataValidationModel extends Disposable { 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); + } } getValidator(type: DataValidationType) { @@ -180,9 +203,11 @@ export class SheetDataValidationModel extends Disposable { return this._dataValidationModel.getRuleById(unitId, subUnitId, ruleId); } - validator(cell: Nullable, rule: ISheetDataValidationRule, pos: ISheetLocation, _onCompete?: (status: DataValidationStatus, changed: boolean) => void): DataValidationStatus { + validator(rule: ISheetDataValidationRule, pos: ISheetLocation, _onCompete?: (status: DataValidationStatus, changed: boolean) => void): DataValidationStatus { const { col, row, unitId, subUnitId, worksheet } = pos; const ruleId = rule.uid; + const formula1 = rule.formula1; + const formula2 = rule.formula2; const onCompete = (status: DataValidationStatus, changed: boolean) => { if (_onCompete) { _onCompete(status, changed); @@ -193,9 +218,13 @@ export class SheetDataValidationModel extends Disposable { subUnitId, ruleId: rule.uid, status, + row, + col, }); } }; + + const cell = worksheet.getCellValueOnly(row, col); const validator = this.getValidator(rule.type); const cellRaw = worksheet.getCellRaw(row, col); const cellValue = getCellValueOrigin(cellRaw); @@ -204,12 +233,14 @@ export class SheetDataValidationModel extends Disposable { if (validator) { const cache = this._dataValidationCacheService.ensureCache(unitId, subUnitId); const current = cache.getValue(row, col); - if (!current || current.value !== cellValue || current.interceptValue !== interceptValue || current.ruleId !== ruleId) { + if (!current || current.value !== cellValue || current.interceptValue !== interceptValue || current.ruleId !== ruleId || current.formula1 !== formula1 || current.formula2 !== formula2) { cache.setValue(row, col, { value: cellValue, interceptValue, status: DataValidationStatus.VALIDATING, ruleId, + formula1: formula1 || '', + formula2: formula2 || '', }); validator.validator( { @@ -231,6 +262,8 @@ export class SheetDataValidationModel extends Disposable { status: realStatus, ruleId, interceptValue, + formula1: formula1 || '', + formula2: formula2 || '', }); onCompete(realStatus, true); }); diff --git a/packages/sheets-data-validation/src/plugin.ts b/packages/sheets-data-validation/src/plugin.ts index b94162dd97..c258736cf5 100644 --- a/packages/sheets-data-validation/src/plugin.ts +++ b/packages/sheets-data-validation/src/plugin.ts @@ -90,6 +90,8 @@ export class UniverSheetsDataValidationPlugin extends Plugin { this._commandService.registerCommand(command); }); + this._injector.get(DataValidationCacheService); + this._injector.get(SheetsDataValidationValidatorService); this._injector.get(DataValidationRefRangeController); } diff --git a/packages/sheets-data-validation/src/services/dv-cache.service.ts b/packages/sheets-data-validation/src/services/dv-cache.service.ts index 121d514ca9..fbe4624be2 100644 --- a/packages/sheets-data-validation/src/services/dv-cache.service.ts +++ b/packages/sheets-data-validation/src/services/dv-cache.service.ts @@ -14,8 +14,10 @@ * limitations under the License. */ -import { ObjectMatrix, Range } from '@univerjs/core'; import type { CellValue, DataValidationStatus, IRange, ISheetDataValidationRule, Nullable } from '@univerjs/core'; +import { Disposable, ICommandService, Inject, ObjectMatrix, Range } from '@univerjs/core'; +import { type ISetRangeValuesMutationParams, SetRangeValuesMutation } from '@univerjs/sheets'; +import { Subject } from 'rxjs'; export interface IDataValidationResCache { value: Nullable; @@ -23,10 +25,40 @@ export interface IDataValidationResCache { status: DataValidationStatus; ruleId: string; temp?: boolean; + formula1: string; + formula2: string; } -export class DataValidationCacheService { +export class DataValidationCacheService extends Disposable { private _cacheMatrix: Map>>> = new Map(); + private _dirtyRanges$ = new Subject<{ unitId: string; subUnitId: string; ranges: IRange[] }>(); + + readonly dirtyRanges$ = this._dirtyRanges$.asObservable(); + + constructor( + @Inject(ICommandService) private readonly _commandService: ICommandService + ) { + super(); + this._initDirtyRanges(); + } + + private _initDirtyRanges() { + this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => { + if (commandInfo.id === SetRangeValuesMutation.id) { + const { cellValue, unitId, subUnitId } = commandInfo.params as ISetRangeValuesMutationParams; + if (cellValue) { + const range = new ObjectMatrix(cellValue).getDataRange(); + if (range.endRow === -1) return; + + this._dirtyRanges$.next({ + unitId, + subUnitId, + ranges: [range], + }); + } + } + })); + } private _ensureCache(unitId: string, subUnitId: string) { let unitMap = this._cacheMatrix.get(unitId); @@ -86,6 +118,8 @@ export class DataValidationCacheService { } }); }); + + this._dirtyRanges$.next({ unitId, subUnitId, ranges: [...oldRanges, ...newRanges] }); } markRangeDirty(unitId: string, subUnitId: string, ranges: IRange[]) { @@ -95,11 +129,14 @@ export class DataValidationCacheService { cache.setValue(row, col, undefined); }); }); + + this._dirtyRanges$.next({ unitId, subUnitId, ranges }); } markCellDirty(unitId: string, subUnitId: string, row: number, col: number) { const cache = this._ensureCache(unitId, subUnitId); cache.setValue(row, col, undefined); + this._dirtyRanges$.next({ unitId, subUnitId, ranges: [{ startRow: row, startColumn: col, endRow: row, endColumn: col }] }); } private _deleteRange(unitId: string, subUnitId: string, ranges: IRange[]) { @@ -109,6 +146,7 @@ export class DataValidationCacheService { cache.realDeleteValue(row, col); }); }); + this._dirtyRanges$.next({ unitId, subUnitId, ranges }); } getValue(unitId: string, subUnitId: string, row: number, col: number) { diff --git a/packages/sheets-data-validation/src/services/dv-custom-formula.service.ts b/packages/sheets-data-validation/src/services/dv-custom-formula.service.ts index 057bd86716..c2e755b2ea 100644 --- a/packages/sheets-data-validation/src/services/dv-custom-formula.service.ts +++ b/packages/sheets-data-validation/src/services/dv-custom-formula.service.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { DataValidationType, Disposable, ILogService, Inject, isFormulaString, IUniverInstanceService, ObjectMatrix, Range, UniverInstanceType } from '@univerjs/core'; +import type { IRange, ISheetDataValidationRule } from '@univerjs/core'; +import { DataValidationType, Disposable, Inject, Injector, isFormulaString, IUniverInstanceService, ObjectMatrix, Range, UniverInstanceType } from '@univerjs/core'; import { DataValidationModel } from '@univerjs/data-validation'; import { LexerTreeBuilder } from '@univerjs/engine-formula'; import { RegisterOtherFormulaService } from '@univerjs/sheets-formula'; -import type { IRange, ISheetDataValidationRule } from '@univerjs/core'; import { DataValidationCacheService } from './dv-cache.service'; interface IDataValidationFormula { @@ -68,7 +68,7 @@ export class DataValidationCustomFormulaService extends Disposable { @Inject(LexerTreeBuilder) private _lexerTreeBuilder: LexerTreeBuilder, @Inject(DataValidationModel) private readonly _dataValidationModel: DataValidationModel, @Inject(DataValidationCacheService) private readonly _dataValidationCacheService: DataValidationCacheService, - @ILogService private readonly _logService: ILogService + @Inject(Injector) private readonly _injector: Injector ) { super(); diff --git a/packages/sheets-data-validation/src/services/dv-formula.service.ts b/packages/sheets-data-validation/src/services/dv-formula.service.ts index 9a18a235d1..978feb9f9f 100644 --- a/packages/sheets-data-validation/src/services/dv-formula.service.ts +++ b/packages/sheets-data-validation/src/services/dv-formula.service.ts @@ -14,11 +14,11 @@ * limitations under the License. */ +import type { Nullable } from '@univerjs/core'; +import type { IFormulaInfo, IOtherFormulaResult } from '@univerjs/sheets-formula'; import { Disposable, Inject, isFormulaString, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; import { DataValidationModel } from '@univerjs/data-validation'; import { RegisterOtherFormulaService } from '@univerjs/sheets-formula'; -import type { Nullable } from '@univerjs/core'; -import type { IFormulaInfo, IOtherFormulaResult } from '@univerjs/sheets-formula'; import { DataValidationCacheService } from './dv-cache.service'; type RuleId = string; diff --git a/packages/sheets-data-validation/src/services/dv-validator-service.ts b/packages/sheets-data-validation/src/services/dv-validator-service.ts index 4b65eaebc4..06e18f8d77 100644 --- a/packages/sheets-data-validation/src/services/dv-validator-service.ts +++ b/packages/sheets-data-validation/src/services/dv-validator-service.ts @@ -14,19 +14,65 @@ * limitations under the License. */ -import { DataValidationStatus, Inject, IUniverInstanceService, Range, Tools, UniverInstanceType } from '@univerjs/core'; import type { IDataValidationRule, IRange, Nullable, ObjectMatrix, Workbook } from '@univerjs/core'; +import type { IDataValidationResCache } from './dv-cache.service'; +import { bufferDebounceTime, DataValidationStatus, Disposable, Inject, IUniverInstanceService, LifecycleService, LifecycleStages, Range, Tools, UniverInstanceType } from '@univerjs/core'; +import { bufferWhen, filter } from 'rxjs'; import { SheetDataValidationModel } from '../models/sheet-data-validation-model'; import { DataValidationCacheService } from './dv-cache.service'; -import type { IDataValidationResCache } from './dv-cache.service'; -export class SheetsDataValidationValidatorService { +export class SheetsDataValidationValidatorService extends Disposable { constructor( @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @Inject(SheetDataValidationModel) private readonly _sheetDataValidationModel: SheetDataValidationModel, - @Inject(DataValidationCacheService) private readonly _dataValidationCacheService: DataValidationCacheService + @Inject(DataValidationCacheService) private readonly _dataValidationCacheService: DataValidationCacheService, + @Inject(LifecycleService) private readonly _lifecycleService: LifecycleService ) { + super(); + this._initRecalculate(); + } + private _initRecalculate() { + const handleDirtyRanges = (ranges: { unitId: string; subUnitId: string; ranges: IRange[] }[]) => { + if (ranges.length === 0) { + return; + } + + const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); + const worksheet = workbook?.getActiveSheet(); + + const map: Record> = {}; + + ranges.flat().forEach((range) => { + if (!map[range.unitId]) { + map[range.unitId] = {}; + } + if (!map[range.unitId][range.subUnitId]) { + map[range.unitId][range.subUnitId] = []; + } + const workbook = this._univerInstanceService.getUnit(range.unitId, UniverInstanceType.UNIVER_SHEET); + const worksheet = workbook?.getSheetBySheetId(range.subUnitId); + if (!worksheet) { + return; + } + map[range.unitId][range.subUnitId].push(...range.ranges.map((range) => Range.transformRange(range, worksheet))); + }); + + Object.entries(map).forEach(([unitId, subUnitMap]) => { + Object.entries(subUnitMap).forEach(([subUnitId, ranges]) => { + if (workbook?.getUnitId() === unitId && worksheet?.getSheetId() === subUnitId) { + this.validatorRanges(unitId, subUnitId, ranges); + } else { + requestIdleCallback(() => { + this.validatorRanges(unitId, subUnitId, ranges); + }); + } + }); + }); + }; + + this.disposeWithMe(this._dataValidationCacheService.dirtyRanges$.pipe(bufferWhen(() => this._lifecycleService.lifecycle$.pipe(filter((stage) => stage === LifecycleStages.Rendered)))).subscribe(handleDirtyRanges)); + this.disposeWithMe(this._dataValidationCacheService.dirtyRanges$.pipe(filter(() => this._lifecycleService.stage >= LifecycleStages.Rendered), bufferDebounceTime(20)).subscribe(handleDirtyRanges)); } async validatorCell(unitId: string, subUnitId: string, row: number, col: number) { @@ -44,14 +90,15 @@ export class SheetsDataValidationValidatorService { throw new Error(`row or col is not defined, row: ${row}, col: ${col}`); } - const cell = worksheet.getCell(row, col); const rule = this._sheetDataValidationModel.getRuleByLocation(unitId, subUnitId, row, col); if (!rule) { return DataValidationStatus.VALID; } return new Promise((resolve) => { - this._sheetDataValidationModel.validator(cell, rule, { unitId, subUnitId, row, col, worksheet, workbook }, resolve); + this._sheetDataValidationModel.validator(rule, { unitId, subUnitId, row, col, worksheet, workbook }, (status) => { + resolve(status); + }); }); }