From 22ccbb17e4b9037e90e320c2eae17c4c18d42991 Mon Sep 17 00:00:00 2001 From: zhangw Date: Mon, 22 Apr 2024 15:57:57 +0800 Subject: [PATCH] feat(data-validation): transform support for data validation (#1915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add-range & delete-range support spillable ref-range strategy * feat: ref-range * feat: move resource and sheet controller to data-validation pkg * fix: unique editor id * fix: eslint * feat: lock * fix: move-range * fix: css * feat: add ref-range tldr pic * feat: test * feat: ref-range unit testt * fix: redi warning * feat: lint * feat: build * feat: move mark-dirty mutation to dv-pkg * feat: add dayjs default plugin * feat: text-length i18n * feat: rename ref-range util * feat: rename * feat: support data-validation transform * feat: data-validation list support formula * feat: remove useless code * feat: dv-demo * fix: build * feat: delay load data validation resource on web app * feat: build fix * fix: build * feat: lint * feat: add unitId bind * feat: subUnitId * feat: update * feat: update * feat: update * feat: update * feat: update * feat: update * fix: empty range * feat: update * feat: add polyfill of old version * feat: update * feat: update * feat: update * feat: update * feat: fix deps issue * feat: style update * feat: update * feat: update * feat: update * feat: update * feat: update * feat: lock * feat: build * feat: temp * feat: update * feat: update * feat: update * feat: update * feat: update * feat: test * feat: update * fix: fix lockfile * fix: popup * feat: locale * feat: lint --------- Co-authored-by: 白熱 --- .../demo/default-workbook-data-validation.ts | 174 ++++++++++++++++++ packages/data-validation/package.json | 1 + .../mutations/data-validation.mutation.ts | 8 +- packages/data-validation/src/common/util.ts | 6 +- .../src/models/data-validation-manager.ts | 28 ++- .../src/models/data-validation-model.ts | 43 +++-- packages/data-validation/src/plugin.ts | 2 +- .../src/types/interfaces/i-formula-input.ts | 1 + .../draggable-list/DraggableList.tsx | 1 - .../design/src/components/popup/RectPopup.tsx | 42 ++--- .../controllers/dv-ref-range.controller.ts | 144 +-------------- .../src/locale/en-US.ts | 2 + .../src/locale/zh-CN.ts | 11 +- .../models/sheet-data-validation-manager.ts | 30 ++- .../src/services/register-formula.service.ts | 12 +- .../src/validators/list-multiple-validator.ts | 96 +--------- .../src/validators/list-validator.ts | 101 ++++++++-- .../src/views/detail/index.tsx | 35 ++-- .../formula-input/custom-formula-input.tsx | 2 + .../src/views/formula-input/index.module.less | 2 - .../formula-input/list-formula-input.tsx | 121 +++++++----- .../src/widgets/dropdown-multiple-widget.ts | 7 +- .../services/canvas-pop-manager.service.ts | 4 +- .../editor-container/EditorContainer.tsx | 2 +- 24 files changed, 484 insertions(+), 391 deletions(-) create mode 100644 examples/src/data/sheets/demo/default-workbook-data-validation.ts diff --git a/examples/src/data/sheets/demo/default-workbook-data-validation.ts b/examples/src/data/sheets/demo/default-workbook-data-validation.ts new file mode 100644 index 0000000000..58e04ab803 --- /dev/null +++ b/examples/src/data/sheets/demo/default-workbook-data-validation.ts @@ -0,0 +1,174 @@ +/** + * 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. + */ + +export const DEFAULT_WORKBOOK_DATA_VALIDATION = { + id: 'workbook-01', + sheetOrder: [ + '80FIpJ1SdQLVIUEbrnC3J', + ], + name: 'UniverSheet Demo', + appVersion: '3.0.0-alpha', + locale: 'zhCN', + styles: {}, + sheets: { + '80FIpJ1SdQLVIUEbrnC3J': { + name: '工作表1', + id: '80FIpJ1SdQLVIUEbrnC3J', + tabColor: '', + hidden: 0, + rowCount: 1000, + columnCount: 20, + zoomRatio: 1, + freeze: { + xSplit: 0, + ySplit: 0, + startRow: -1, + startColumn: -1, + }, + scrollTop: 0, + scrollLeft: 0, + defaultColumnWidth: 73, + defaultRowHeight: 19, + mergeData: [], + cellData: { + 0: { + 0: { + v: 'YES', + t: 1, + }, + 2: { + v: 'IF', + t: 1, + }, + 5: { + v: '中文', + t: 1, + }, + 6: { + v: '一', + t: 1, + }, + 7: { + v: '二', + t: 1, + }, + 8: { + v: '三', + t: 1, + }, + }, + 1: { + 5: { + v: '数字', + t: 1, + }, + 6: { + v: 1, + t: 2, + }, + 7: { + v: 2, + t: 2, + }, + 8: { + v: 3, + t: 2, + }, + }, + 2: { + 2: { + v: '级联', + t: 1, + }, + 3: { + v: '中文', + t: 1, + }, + 4: { + v: '一', + t: 1, + }, + 5: null, + 6: null, + 7: null, + 8: null, + }, + 3: { + 3: null, + 4: null, + 5: null, + 6: null, + 7: null, + 8: null, + }, + }, + rowData: { + 0: { + hd: 0, + h: 19, + ah: 20, + }, + 2: { + hd: 0, + h: 19, + ah: 20, + }, + 3: { + hd: 0, + h: 19, + ah: 19, + }, + }, + columnData: {}, + showGridlines: 1, + rowHeader: { + width: 46, + hidden: 0, + }, + columnHeader: { + height: 20, + hidden: 0, + }, + selections: [ + 'A1', + ], + rightToLeft: 0, + }, + }, + resources: [ + { + name: 'SHEET_NUMFMT_PLUGIN', + data: '', + }, + { + name: 'SHEET_DEFINED_NAME_PLUGIN', + data: '', + }, + { + name: 'SHEET_DATA_VALIDATION', + data: '{"sheet-0011":[{"uid":"xxx-1","type":"decimal","ranges":[{"startRow":0,"endRow":5,"startColumn":0,"endColumn":2}],"operator":"greaterThan","formula1":"111","errorStyle":1},{"uid":"xxx-0","type":"date","ranges":[{"startRow":0,"endRow":5,"startColumn":3,"endColumn":5}],"operator":"greaterThan","formula1":"100","errorStyle":1},{"uid":"xxx-2","type":"checkbox","ranges":[{"startRow":6,"endRow":10,"startColumn":0,"endColumn":5}]},{"uid":"xxx-3","type":"list","ranges":[{"startRow":11,"endRow":15,"startColumn":0,"endColumn":5}],"formula1":"1,2,3,hahaha"},{"uid":"xxx-4","type":"custom","ranges":[{"startRow":16,"endRow":20,"startColumn":0,"endColumn":5}],"formula1":"=A1"},{"uid":"xxx-5","type":"listMultiple","ranges":[{"startRow":21,"endRow":21,"startColumn":0,"endColumn":0}],"formula1":"1,2,3,4,5,哈哈哈哈"}],"80FIpJ1SdQLVIUEbrnC3J":[{"uid":"pQCe2q","type":"list","formula1":"=IF(A1=\\"YES\\",G1:I1,G2:I2)","ranges":[{"startRow":0,"endRow":0,"startColumn":3,"endColumn":3}],"formula2":""},{"uid":"JL6foF","type":"list","formula1":"=F1:F2","ranges":[{"startRow":2,"startColumn":3,"endRow":2,"endColumn":3,"rangeType":0}],"formula2":""},{"uid":"XlFuI6","type":"list","formula1":"=IF(D3=\\"中文\\",G1:I1,G2:I2)","ranges":[{"startRow":2,"startColumn":4,"endRow":2,"endColumn":4,"rangeType":0}],"formula2":""}]}', + }, + { + name: 'SHEET_CONDITIONAL_FORMATTING_PLUGIN', + data: '', + }, + ], + __env__: { + gitHash: '840580212', + gitBranch: 'feat/dv-list-formula', + buildTime: '2024-04-10T11:19:00.298Z', + }, +}; diff --git a/packages/data-validation/package.json b/packages/data-validation/package.json index 8369ed0453..8690088cb8 100644 --- a/packages/data-validation/package.json +++ b/packages/data-validation/package.json @@ -73,6 +73,7 @@ }, "devDependencies": { "@univerjs/core": "workspace:*", + "@univerjs/protocol": "^0.1.14", "@univerjs/shared": "workspace:*", "@univerjs/sheets": "workspace:*", "@wendellhu/redi": "^0.13.0", diff --git a/packages/data-validation/src/commands/mutations/data-validation.mutation.ts b/packages/data-validation/src/commands/mutations/data-validation.mutation.ts index f162f2278b..5e75267b74 100644 --- a/packages/data-validation/src/commands/mutations/data-validation.mutation.ts +++ b/packages/data-validation/src/commands/mutations/data-validation.mutation.ts @@ -34,13 +34,7 @@ export const AddDataValidationMutation: ICommand { - dataValidationModel.addRule(unitId, subUnitId, ruleItem, index); - }); - } else { - dataValidationModel.addRule(unitId, subUnitId, rule!, index); - } + dataValidationModel.addRule(unitId, subUnitId, rule, index); return true; }, diff --git a/packages/data-validation/src/common/util.ts b/packages/data-validation/src/common/util.ts index 7454cfdd46..5c6592c69d 100644 --- a/packages/data-validation/src/common/util.ts +++ b/packages/data-validation/src/common/util.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { DataValidationOperator, DataValidationType, type IDataValidationRule, type IDataValidationRuleBase, type IDataValidationRuleOptions, Tools } from '@univerjs/core'; +import { DataValidationOperator, DataValidationType, type IDataValidationRuleBase, type IDataValidationRuleOptions, Tools } from '@univerjs/core'; import { SelectionManagerService } from '@univerjs/sheets'; import type { IAccessor } from '@wendellhu/redi'; -export function getRuleSetting(rule: IDataValidationRule): IDataValidationRuleBase { +export function getRuleSetting(rule: IDataValidationRuleBase): IDataValidationRuleBase { return { type: rule.type, operator: rule.operator, @@ -28,7 +28,7 @@ export function getRuleSetting(rule: IDataValidationRule): IDataValidationRuleBa }; } -export function getRuleOptions(rule: IDataValidationRule): IDataValidationRuleOptions { +export function getRuleOptions(rule: IDataValidationRuleOptions): IDataValidationRuleOptions { return { error: rule.error, errorStyle: rule.errorStyle, diff --git a/packages/data-validation/src/models/data-validation-manager.ts b/packages/data-validation/src/models/data-validation-manager.ts index aa06eb5c39..1618601049 100644 --- a/packages/data-validation/src/models/data-validation-manager.ts +++ b/packages/data-validation/src/models/data-validation-manager.ts @@ -19,6 +19,7 @@ import { DataValidationStatus, Disposable } from '@univerjs/core'; import { Subject } from 'rxjs'; import type { IUpdateRulePayload } from '../types/interfaces/i-update-rule-payload'; import { UpdateRuleType } from '../types/enum/update-rule-type'; +import { getRuleOptions, getRuleSetting } from '../common/util'; export class DataValidationManager extends Disposable { private _dataValidations: T[]; @@ -66,22 +67,30 @@ export class DataValidationManager extends Dispos return this._dataValidations.findIndex((rule) => rule.uid === id); } - addRule(rule: T, index?: number) { + addRule(rule: T | T[], index?: number) { + const _rules = Array.isArray(rule) ? rule : [rule]; + const rules = _rules.filter((item) => !this._dataValidationMap.has(item.uid)); + if (typeof index === 'number' && index < this._dataValidations.length) { - this._dataValidations.splice(index, 0, rule); + this._dataValidations.splice(index, 0, ...rules); } else { - this._dataValidations.push(rule); + this._dataValidations.push(...rules); } - this._dataValidationMap.set(rule.uid, rule); + rules.forEach((item) => { + this._dataValidationMap.set(item.uid, item); + }); + this._notice(); } removeRule(ruleId: string) { const index = this._dataValidations.findIndex((item) => item.uid === ruleId); - this._dataValidations.splice(index, 1); - this._dataValidationMap.delete(ruleId); - this._notice(); + if (index > -1) { + this._dataValidations.splice(index, 1); + this._dataValidationMap.delete(ruleId); + this._notice(); + } } updateRule(ruleId: string, payload: IUpdateRulePayload) { @@ -94,18 +103,19 @@ export class DataValidationManager extends Dispos const rule = { ...oldRule }; + switch (payload.type) { case UpdateRuleType.RANGE: { rule.ranges = payload.payload; break; } case UpdateRuleType.SETTING: { - Object.assign(rule, payload.payload); + Object.assign(rule, getRuleSetting(payload.payload)); break; } case UpdateRuleType.OPTIONS: { - Object.assign(rule, payload.payload); + Object.assign(rule, getRuleOptions(payload.payload)); break; } default: diff --git a/packages/data-validation/src/models/data-validation-model.ts b/packages/data-validation/src/models/data-validation-model.ts index 4dfbe32708..5e728a2883 100644 --- a/packages/data-validation/src/models/data-validation-model.ts +++ b/packages/data-validation/src/models/data-validation-model.ts @@ -78,16 +78,29 @@ export class DataValidationModel { + this._addRuleSideEffect(unitId, subUnitId, item); }); + + manager.addRule(rule, index); } catch (error) { this._logService.error(error); } @@ -113,13 +126,15 @@ export class DataValidationModel; diff --git a/packages/design/src/components/draggable-list/DraggableList.tsx b/packages/design/src/components/draggable-list/DraggableList.tsx index 65bb5d7e9d..e4f6cbbc36 100644 --- a/packages/design/src/components/draggable-list/DraggableList.tsx +++ b/packages/design/src/components/draggable-list/DraggableList.tsx @@ -51,7 +51,6 @@ export function DraggableList(props: IDraggableListProps) { return ( { function RectPopup(props: IRectPopupProps) { const { children, anchorRect, direction = 'vertical', onClickOutside, excludeOutside } = props; const nodeRef = useRef(null); - const clickOtherFn = useEvent(onClickOutside ?? (() => {})); + const clickOtherFn = useEvent(onClickOutside ?? (() => { })); const [position, setPosition] = useState>({ top: -9999, @@ -102,7 +102,7 @@ function RectPopup(props: IRectPopupProps) { ) ); }, - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps [ anchorRect.left, anchorRect.top, @@ -112,36 +112,30 @@ function RectPopup(props: IRectPopupProps) { ]); useEffect(() => { - const current = nodeRef.current; - const parent = current?.parentElement; + const handleClickOther = (e: MouseEvent) => { + if (excludeOutside && (excludeOutside.indexOf(e.target as any) > -1)) { + return; + } - if (parent) { - const handleClickOther = (e: MouseEvent) => { - if (excludeOutside && (excludeOutside.indexOf(e.target as any) > -1)) { - return; - } - const bounding = parent.getBoundingClientRect(); + const x = e.clientX; + const y = e.clientY; + if (x <= anchorRect.right && x >= anchorRect.left && y <= anchorRect.bottom && y >= anchorRect.top) { + return; + } + clickOtherFn(e); + }; - const x = e.clientX - bounding.left; - const y = e.clientY - bounding.top; - if (x <= anchorRect.right && x >= anchorRect.left && y <= anchorRect.bottom && y >= anchorRect.top) { - return; - } - clickOtherFn(e); - }; + window.addEventListener('click', handleClickOther); - window.addEventListener('click', handleClickOther); - - return () => { - window.removeEventListener('click', handleClickOther); - }; - } + return () => { + window.removeEventListener('click', handleClickOther); + }; }, [anchorRect, anchorRect.bottom, anchorRect.left, anchorRect.right, anchorRect.top, clickOtherFn, excludeOutside]); return (
{ e.stopPropagation(); diff --git a/packages/sheets-data-validation/src/controllers/dv-ref-range.controller.ts b/packages/sheets-data-validation/src/controllers/dv-ref-range.controller.ts index 753a261489..4093447c1a 100644 --- a/packages/sheets-data-validation/src/controllers/dv-ref-range.controller.ts +++ b/packages/sheets-data-validation/src/controllers/dv-ref-range.controller.ts @@ -14,23 +14,22 @@ * limitations under the License. */ -import type { IRange, ISheetDataValidationRule, Workbook } from '@univerjs/core'; -import { DataValidationType, Disposable, DisposableCollection, ICommandService, isRangesEqual, IUniverInstanceService, LifecycleStages, OnLifecycle, Range, Rectangle, toDisposable, UniverInstanceType } from '@univerjs/core'; -import type { EffectRefRangeParams, ISetRangeValuesMutationParams } from '@univerjs/sheets'; +import type { IRange, ISheetDataValidationRule } from '@univerjs/core'; +import { DataValidationType, Disposable, DisposableCollection, ICommandService, isRangesEqual, IUniverInstanceService, LifecycleStages, OnLifecycle, toDisposable } from '@univerjs/core'; +import type { EffectRefRangeParams } from '@univerjs/sheets'; import { SheetSkeletonManagerService } from '@univerjs/sheets-ui'; -import { handleCommonDefaultRangeChangeWithEffectRefCommands, handleDefaultRangeChangeWithEffectRefCommands, RefRangeService, SetRangeValuesMutation } from '@univerjs/sheets'; +import { handleCommonDefaultRangeChangeWithEffectRefCommands, RefRangeService } from '@univerjs/sheets'; import { Inject, Injector } from '@wendellhu/redi'; import { merge, Observable } from 'rxjs'; import { distinctUntilChanged, map, switchMap } from 'rxjs/operators'; import type { IRemoveDataValidationMutationParams, IUpdateDataValidationMutationParams } from '@univerjs/data-validation'; import { DataValidationModel, RemoveDataValidationMutation, removeDataValidationUndoFactory, UpdateDataValidationMutation, UpdateRuleType } from '@univerjs/data-validation'; import { FormulaRefRangeService } from '@univerjs/sheets-formula'; -import { deserializeRangeWithSheet, isReferenceStringWithEffectiveColumn, serializeRangeWithSpreadsheet } from '@univerjs/engine-formula'; import { DataValidationCustomFormulaService } from '../services/dv-custom-formula.service'; import { DataValidationFormulaService } from '../services/dv-formula.service'; import { DataValidationCacheService } from '../services/dv-cache.service'; -@OnLifecycle(LifecycleStages.Rendered, DataValidationRefRangeController) +@OnLifecycle(LifecycleStages.Ready, DataValidationRefRangeController) export class DataValidationRefRangeController extends Disposable { private _disposableMap: Map void>> = new Map(); @@ -57,9 +56,9 @@ export class DataValidationRefRangeController extends Disposable { registerRule = (unitId: string, subUnitId: string, rule: ISheetDataValidationRule) => { this.register(unitId, subUnitId, rule); this.registerFormula(unitId, subUnitId, rule); - this.registerRange(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); @@ -97,7 +96,6 @@ export class DataValidationRefRangeController extends Disposable { type: oldRule.type, formula1: oldRule.formula1, formula2: oldRule.formula2, - [type]: formulaString, }, }, }; @@ -208,136 +206,6 @@ export class DataValidationRefRangeController extends Disposable { this._disposableMap.set(id, current); }; - registerRange(propUnitId: string, propSubUnitId: string, rule: ISheetDataValidationRule) { - const { uid: ruleId, formula1, type } = rule; - if ((type !== DataValidationType.LIST && type !== DataValidationType.LIST_MULTIPLE)) { - return; - } - - if (!isReferenceStringWithEffectiveColumn(formula1 ?? '')) { - return; - } - const gridRange = deserializeRangeWithSheet(formula1 ?? ''); - const id = this._getIdWithUnitId(propUnitId, propSubUnitId, ruleId); - const rangeUnitId = gridRange.unitId || propUnitId; - const workbook = this._univerInstanceService.getUniverSheetInstance(rangeUnitId) ?? this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!; - const sheetId = workbook.getSheetBySheetName(gridRange.sheetName)?.getSheetId() ?? propSubUnitId; - const worksheet = workbook.getSheetBySheetId(sheetId) ?? workbook.getActiveSheet(); - const disposableCollection = new DisposableCollection(); - const range = gridRange.range; - - const finalUnitId = workbook.getUnitId(); - const finalSubUnitId = worksheet.getSheetId(); - - disposableCollection.add( - this._refRangeService.registerRefRange( - range, - (info) => { - let newRange = handleDefaultRangeChangeWithEffectRefCommands(range, info); - if (!newRange) { - newRange = { - startColumn: -1, - endColumn: -1, - startRow: -1, - endRow: -1, - }; - } - - if (Rectangle.equals(newRange, range)) { - return { - redos: [], - undos: [], - }; - } - - const newRangeStr = serializeRangeWithSpreadsheet(finalUnitId, finalSubUnitId, newRange); - const oldRule = this._dataValidationModel.getRuleById(propUnitId, propSubUnitId, ruleId); - if (!oldRule) { - return { - redos: [], - undos: [], - }; - } - const redoParams: IUpdateDataValidationMutationParams = { - unitId: propUnitId, - subUnitId: propSubUnitId, - ruleId, - payload: { - type: UpdateRuleType.SETTING, - payload: { - formula1: newRangeStr, - formula2: oldRule.formula2, - type: oldRule.type, - }, - }, - }; - const undoParams: IUpdateDataValidationMutationParams = { - unitId: propUnitId, - subUnitId: propSubUnitId, - ruleId, - payload: { - type: UpdateRuleType.SETTING, - payload: { - formula1: oldRule.formula1, - formula2: oldRule.formula2, - type: oldRule.type, - }, - }, - }; - - return { - redos: [{ - id: UpdateDataValidationMutation.id, - params: redoParams, - }], - undos: [{ - id: UpdateDataValidationMutation.id, - params: undoParams, - }], - }; - }, - finalUnitId, - finalUnitId - ) - ); - - disposableCollection.add(this.disposeWithMe( - this._commandService.onCommandExecuted((commandInfo) => { - if (commandInfo.id === SetRangeValuesMutation.id && commandInfo.params) { - const params = commandInfo.params as ISetRangeValuesMutationParams; - const { - cellValue, - unitId, - subUnitId, - } = params; - - if (unitId === finalUnitId && subUnitId === finalSubUnitId) { - if (cellValue) { - let marked = false; - - Range.foreach(range, (row, col) => { - const rowValue = cellValue[row]; - if (rowValue && (col in rowValue)) { - if (marked) { - return; - } - marked = true; - this._dataValidationCacheService.markRangeDirty(unitId, subUnitId, rule.ranges); - } - }); - } else { - this._dataValidationCacheService.markRangeDirty(unitId, subUnitId, rule.ranges); - } - } - } - }) - )); - - const current = this._disposableMap.get(id) ?? new Set(); - current.add(() => disposableCollection.dispose()); - this._disposableMap.set(id, current); - } - private _initRefRange() { this.disposeWithMe( merge( diff --git a/packages/sheets-data-validation/src/locale/en-US.ts b/packages/sheets-data-validation/src/locale/en-US.ts index 05d1f431cd..ff759801ab 100644 --- a/packages/sheets-data-validation/src/locale/en-US.ts +++ b/packages/sheets-data-validation/src/locale/en-US.ts @@ -26,6 +26,7 @@ const locale: typeof zhCN = { integer: 'Please input integer or formula', date: 'Please input date or formula', list: 'Please input options', + listInvalid: 'The list source must be a delimited list or a reference to a single row or column', }, panel: { title: 'Data validation management', @@ -119,6 +120,7 @@ const locale: typeof zhCN = { options: 'Options', customOptions: 'Custom', refOptions: 'From a range', + formulaError: 'The list source must be a delimited list of data, or a reference to a single row or column.', }, listMultiple: { title: 'Dropdown-Multiple', diff --git a/packages/sheets-data-validation/src/locale/zh-CN.ts b/packages/sheets-data-validation/src/locale/zh-CN.ts index 8b255ee882..0e020d062b 100644 --- a/packages/sheets-data-validation/src/locale/zh-CN.ts +++ b/packages/sheets-data-validation/src/locale/zh-CN.ts @@ -24,6 +24,7 @@ const locale = { integer: '请输入合法的整数或公式', date: '请输入合法的日期或公式', list: '请输入至少一个合法选项', + listInvalid: '列表源必须是分隔列表或对单行或列的引用。', }, panel: { title: '管理数据验证', @@ -78,10 +79,10 @@ const locale = { title: '日期', operators: { between: '介于', - greaterThan: '大于', - greaterThanOrEqual: '大于或等于', - lessThan: '小于', - lessThanOrEqual: '小于或等于', + greaterThan: '晚于', + greaterThanOrEqual: '晚于或等于', + lessThan: '早于', + lessThanOrEqual: '早于或等于', equal: '等于', notEqual: '不等于', notBetween: '未介于', @@ -119,7 +120,7 @@ const locale = { options: '选项来源', customOptions: '自定义', refOptions: '引用数据', - + formulaError: '列表源必须是划定分界后的数据列表,或是对单一行或一列的引用。', }, listMultiple: { title: '下拉菜单-多选', diff --git a/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts b/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts index 28e84fa945..12efdfd3b1 100644 --- a/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts +++ b/packages/sheets-data-validation/src/models/sheet-data-validation-manager.ts @@ -16,10 +16,11 @@ import type { CellValue, ISheetDataValidationRule, Nullable } from '@univerjs/core'; import { DataValidationManager, DataValidatorRegistryService, UpdateRuleType } from '@univerjs/data-validation'; -import { DataValidationStatus, ObjectMatrix, Range } from '@univerjs/core'; +import { DataValidationStatus, DataValidationType, ObjectMatrix, Range } from '@univerjs/core'; import type { IUpdateRulePayload } from '@univerjs/data-validation'; import type { ISheetLocation } from '@univerjs/sheets'; import type { Injector } from '@wendellhu/redi'; +import { isReferenceString } from '@univerjs/engine-formula'; import type { IDataValidationResCache } from '../services/dv-cache.service'; import { DataValidationCacheService } from '../services/dv-cache.service'; import { DataValidationFormulaService } from '../services/dv-formula.service'; @@ -69,11 +70,30 @@ export class SheetDataValidationManager extends DataValidationManager { + this._addRuleSideEffect(item); + }); + super.addRule(rule, index); } diff --git a/packages/sheets-data-validation/src/services/register-formula.service.ts b/packages/sheets-data-validation/src/services/register-formula.service.ts index 407b6b2a68..149099ae0b 100644 --- a/packages/sheets-data-validation/src/services/register-formula.service.ts +++ b/packages/sheets-data-validation/src/services/register-formula.service.ts @@ -26,7 +26,7 @@ import { FormulaResultStatus, type IDataValidationFormulaResult } from './formul export class RegisterOtherFormulaService extends Disposable { private _formulaCacheMap: Map>> = new Map(); - private _formulaChange$ = new Subject<{ unitId: string; subUnitId: string; ruleId: string; formulaText: string; formulaId: string }>(); + private _formulaChange$ = new Subject<{ unitId: string; subUnitId: string; formulaText: string; formulaId: string }>(); public formulaChange$ = this._formulaChange$.asObservable(); private _formulaResult$ = new Subject>>(); @@ -149,7 +149,6 @@ export class RegisterOtherFormulaService extends Disposable { }); this._formulaChange$.next({ unitId, - ruleId, subUnitId, formulaText, formulaId, @@ -175,7 +174,7 @@ export class RegisterOtherFormulaService extends Disposable { return Promise.resolve(null); } - if (item.status === FormulaResultStatus.SUCCESS || FormulaResultStatus.ERROR) { + if (item.status === FormulaResultStatus.SUCCESS || item.status === FormulaResultStatus.ERROR) { return Promise.resolve(item); } @@ -186,6 +185,13 @@ export class RegisterOtherFormulaService extends Disposable { }); } + async getTempFormulaResult(unitId: string, subUnitId: string, formulaString: string) { + const formulaId = this.registerFormula(unitId, subUnitId, 'temp', formulaString); + const formulaValue = await this.getFormulaValue(unitId, subUnitId, formulaId); + this.deleteFormula(unitId, subUnitId, [formulaId]); + return formulaValue; + } + getFormulaValueSync(unitId: string, subUnitId: string, formulaId: string): Nullable { const cacheMap = this._ensureCacheMap(unitId, subUnitId); return cacheMap.get(formulaId); diff --git a/packages/sheets-data-validation/src/validators/list-multiple-validator.ts b/packages/sheets-data-validation/src/validators/list-multiple-validator.ts index 18319e123c..b59f63feb4 100644 --- a/packages/sheets-data-validation/src/validators/list-multiple-validator.ts +++ b/packages/sheets-data-validation/src/validators/list-multiple-validator.ts @@ -14,102 +14,16 @@ * limitations under the License. */ -import { DataValidationType, IUniverInstanceService, Tools, UniverInstanceType } from '@univerjs/core'; -import type { CellValue, DataValidationOperator, IDataValidationRule, IDataValidationRuleBase, Nullable, Workbook } from '@univerjs/core'; -import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation'; -import { BaseDataValidator } from '@univerjs/data-validation'; -import { deserializeRangeWithSheet, isReferenceStringWithEffectiveColumn } from '@univerjs/engine-formula'; -import { LIST_FORMULA_INPUT_NAME } from '../views/formula-input'; -import { LIST_DROPDOWN_KEY } from '../views'; +import { DataValidationType } from '@univerjs/core'; import { DropdownMultipleWidget } from '../widgets/dropdown-multiple-widget'; -import { deserializeListOptions, getSheetRangeValueSet } from './util'; - -export class ListMultipleValidator extends BaseDataValidator { - id: string = DataValidationType.LIST_MULTIPLE; - title: string = 'dataValidation.listMultiple.title'; - operators: DataValidationOperator[] = []; - scopes: string | string[] = ['sheet']; - formulaInput: string = LIST_FORMULA_INPUT_NAME; - - private _univerInstanceService = this.injector.get(IUniverInstanceService); +import { ListValidator } from './list-validator'; +export class ListMultipleValidator extends ListValidator { + override id: string = DataValidationType.LIST_MULTIPLE; + override title: string = 'dataValidation.listMultiple.title'; override canvasRender = this.injector.createInstance(DropdownMultipleWidget); - override dropdown: string | undefined = LIST_DROPDOWN_KEY; - override skipDefaultFontRender() { return true; } - - override validatorFormula(rule: IDataValidationRuleBase): IFormulaValidResult { - const success = !Tools.isBlank(rule.formula1); - - return { - success, - formula1: success ? undefined : this.localeService.t('dataValidation.validFail.list'), - }; - } - - parseCellValue(cellValue: CellValue, rule: IDataValidationRule) { - const cellString = cellValue.toString(); - if (!cellString) { - return []; - } - return deserializeListOptions(cellString); - } - - override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise> { - const { formula1 = '' } = rule; - - return { - formula1: isReferenceStringWithEffectiveColumn(formula1) ? getSheetRangeValueSet(deserializeRangeWithSheet(formula1), this._univerInstanceService, unitId, subUnitId) : deserializeListOptions(formula1), - formula2: undefined, - }; - } - - override async isValidType(cellInfo: IValidatorCellInfo>, formula: IFormulaResult, rule: IDataValidationRule): Promise { - const { value } = cellInfo; - const { formula1 } = formula; - const selected = this.parseCellValue(value!, rule); - return selected.every((i) => formula1.includes(i)); - } - - override generateRuleName() { - return this.localeService.t('dataValidation.list.name'); - } - - override generateRuleErrorMessage(): string { - return this.localeService.t('dataValidation.list.error'); - } - - getList(rule: IDataValidationRule, propUnitId?: string, propSubUnitId?: string) { - const { formula1 = '' } = rule; - const univerInstanceService = this.injector.get(IUniverInstanceService); - const workbook = (propUnitId ? univerInstanceService.getUniverSheetInstance(propUnitId) : undefined) ?? univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET); - if (!workbook) return []; - - const worksheet = (propSubUnitId ? workbook.getSheetBySheetId(propSubUnitId) : undefined) ?? workbook.getActiveSheet(); - const unitId = workbook.getUnitId(); - const subUnitId = worksheet.getSheetId(); - return isReferenceStringWithEffectiveColumn(formula1) ? getSheetRangeValueSet(deserializeRangeWithSheet(formula1), this._univerInstanceService, unitId, subUnitId) : deserializeListOptions(formula1); - } - - getListWithColor(rule: IDataValidationRule, currentUnitId?: string, currentSubUnitId?: string) { - const list = this.getList(rule, currentUnitId, currentSubUnitId); - const colorList = rule.formula2 ? rule.formula2.split(',') : []; - - return list.map((label, i) => ({ label, color: colorList[i] })); - } - - getListWithColorMap(rule: IDataValidationRule, currentUnitId?: string, currentSubUnitId?: string) { - const list = this.getListWithColor(rule, currentUnitId, currentSubUnitId); - const map: Record = {}; - - list.forEach((item) => { - if (item.color) { - map[item.label] = item.color; - } - }); - return map; - } } diff --git a/packages/sheets-data-validation/src/validators/list-validator.ts b/packages/sheets-data-validation/src/validators/list-validator.ts index 6b683869c1..45bd654737 100644 --- a/packages/sheets-data-validation/src/validators/list-validator.ts +++ b/packages/sheets-data-validation/src/validators/list-validator.ts @@ -14,27 +14,73 @@ * limitations under the License. */ -import { DataValidationRenderMode, DataValidationType, IUniverInstanceService, Tools, UniverInstanceType } from '@univerjs/core'; -import type { CellValue, DataValidationOperator, IDataValidationRule, IDataValidationRuleBase, ISheetDataValidationRule, Nullable, Workbook } from '@univerjs/core'; -import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation'; +import { DataValidationRenderMode, DataValidationType, isFormulaString, IUniverInstanceService, Tools, UniverInstanceType } from '@univerjs/core'; +import type { CellValue, DataValidationOperator, ICellData, IDataValidationRule, IDataValidationRuleBase, ISheetDataValidationRule, Nullable, Workbook } from '@univerjs/core'; +import type { IBaseDataValidationWidget, IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation'; import { BaseDataValidator } from '@univerjs/data-validation'; -import { deserializeRangeWithSheet, isReferenceStringWithEffectiveColumn } from '@univerjs/engine-formula'; +import { isReferenceString, LexerTreeBuilder, sequenceNodeType } from '@univerjs/engine-formula'; import { LIST_FORMULA_INPUT_NAME } from '../views/formula-input'; import { LIST_DROPDOWN_KEY } from '../views'; import { DropdownWidget } from '../widgets/dropdown-widget'; import { ListRenderModeInput } from '../views/render-mode'; -import { deserializeListOptions, getSheetRangeValueSet } from './util'; +import { DataValidationFormulaService } from '../services/dv-formula.service'; +import { getCellValueOrigin } from '../utils/get-cell-data-origin'; +import { deserializeListOptions } from './util'; + +export function getRuleFormulaResultSet(result: Nullable[][]>) { + if (!result) { + return []; + } + const resultSet = new Set(); + result.forEach( + (row) => { + row.forEach((cell) => { + const value = getCellValueOrigin(cell); + if (value !== null && value !== undefined) { + resultSet.add(value.toString()); + } + }); + } + ); + + return [...resultSet]; +} + +const supportedFormula = [ + 'if', + 'indirect', + 'choose', + 'offset', +]; + +// 1. must have REFERENCE or DEFINED_NAME node. +// 2. only support some formula +export function isValidListFormula(formula: string, lexer: LexerTreeBuilder) { + if (!isFormulaString(formula)) { + return true; + } + + const isRefString = isReferenceString(formula.slice(1)); + if (isRefString) { + return true; + } + + const nodes = lexer.sequenceNodesBuilder(formula); + + return (nodes) && nodes.some((node) => typeof node === 'object' && node.nodeType === sequenceNodeType.FUNCTION && supportedFormula.indexOf(node.token.toLowerCase()) > -1); +} export class ListValidator extends BaseDataValidator { + protected formulaService = this.injector.get(DataValidationFormulaService); + private _lexer = this.injector.get(LexerTreeBuilder); + id: string = DataValidationType.LIST; title: string = 'dataValidation.list.title'; operators: DataValidationOperator[] = []; scopes: string | string[] = ['sheet']; formulaInput: string = LIST_FORMULA_INPUT_NAME; - private _univerInstanceService = this.injector.get(IUniverInstanceService); - - override canvasRender = this.injector.createInstance(DropdownWidget); + override canvasRender: Nullable = this.injector.createInstance(DropdownWidget); override dropdown: string | undefined = LIST_DROPDOWN_KEY; @@ -46,31 +92,37 @@ export class ListValidator extends BaseDataValidator { override validatorFormula(rule: IDataValidationRuleBase): IFormulaValidResult { const success = !Tools.isBlank(rule.formula1); + const valid = isValidListFormula(rule.formula1 ?? '', this._lexer); return { - success, - formula1: success ? undefined : this.localeService.t('dataValidation.validFail.list'), + success: Boolean(success && valid), + formula1: success + ? valid ? + undefined : + this.localeService.t('dataValidation.validFail.listInvalid') + : this.localeService.t('dataValidation.validFail.list'), }; } - private _parseCellValue(cellValue: CellValue, rule: IDataValidationRule) { + parseCellValue(cellValue: CellValue) { const cellString = cellValue.toString(); return deserializeListOptions(cellString); } override async parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): Promise> { const { formula1 = '' } = rule; + const results = await this.formulaService.getRuleFormulaResult(unitId, subUnitId, rule.uid); return { - formula1: isReferenceStringWithEffectiveColumn(formula1) ? getSheetRangeValueSet(deserializeRangeWithSheet(formula1), this._univerInstanceService, unitId, subUnitId) : deserializeListOptions(formula1), + formula1: isFormulaString(formula1) ? getRuleFormulaResultSet(results?.[0]?.result) : deserializeListOptions(formula1), formula2: undefined, }; } - override async isValidType(cellInfo: IValidatorCellInfo>, formula: IFormulaResult, rule: IDataValidationRule): Promise { + override async isValidType(cellInfo: IValidatorCellInfo>, formula: IFormulaResult, rule: IDataValidationRule): Promise { const { value } = cellInfo; - const { formula1 } = formula; - const selected = this._parseCellValue(value!, rule); + const { formula1 = [] } = formula; + const selected = this.parseCellValue(value!); return selected.every((i) => formula1.includes(i)); } @@ -91,9 +143,22 @@ export class ListValidator extends BaseDataValidator { const worksheet = (currentSubUnitId ? workbook.getSheetBySheetId(currentSubUnitId) : undefined) ?? workbook.getActiveSheet(); const unitId = workbook.getUnitId(); const subUnitId = worksheet.getSheetId(); - return isReferenceStringWithEffectiveColumn(formula1) - ? getSheetRangeValueSet(deserializeRangeWithSheet(formula1), this._univerInstanceService, unitId, subUnitId) - : deserializeListOptions(formula1); + const results = this.formulaService.getRuleFormulaResultSync(unitId, subUnitId, rule.uid); + return isFormulaString(formula1) ? getRuleFormulaResultSet(results?.[0]?.result) : deserializeListOptions(formula1); + } + + async getListAsync(rule: IDataValidationRule, currentUnitId?: string, currentSubUnitId?: string) { + const { formula1 = '' } = rule; + const univerInstanceService = this.injector.get(IUniverInstanceService); + const workbook = (currentUnitId ? univerInstanceService.getUniverSheetInstance(currentUnitId) : undefined) ?? univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET); + if (!workbook) { + return []; + } + const worksheet = (currentSubUnitId ? workbook.getSheetBySheetId(currentSubUnitId) : undefined) ?? workbook.getActiveSheet(); + const unitId = workbook.getUnitId(); + const subUnitId = worksheet.getSheetId(); + const results = await this.formulaService.getRuleFormulaResult(unitId, subUnitId, rule.uid); + return isFormulaString(formula1) ? getRuleFormulaResultSet(results?.[0]?.result) : deserializeListOptions(formula1); } getListWithColor(rule: IDataValidationRule, currentUnitId?: string, currentSubUnitId?: string) { diff --git a/packages/sheets-data-validation/src/views/detail/index.tsx b/packages/sheets-data-validation/src/views/detail/index.tsx index c695e68244..12cbd837da 100644 --- a/packages/sheets-data-validation/src/views/detail/index.tsx +++ b/packages/sheets-data-validation/src/views/detail/index.tsx @@ -14,8 +14,8 @@ * limitations under the License. */ -import type { DataValidationOperator, IDataValidationRuleBase, IDataValidationRuleOptions, IExecutionOptions, ISheetDataValidationRule, IUnitRange } from '@univerjs/core'; -import { createInternalEditorID, DataValidationType, debounce, ICommandService, isUnitRangesEqual, isValidRange, LocaleService, RedoCommand, shallowEqual, UndoCommand } from '@univerjs/core'; +import type { DataValidationOperator, DataValidationType, IDataValidationRuleBase, IDataValidationRuleOptions, IExecutionOptions, ISheetDataValidationRule, IUnitRange } from '@univerjs/core'; +import { createInternalEditorID, debounce, ICommandService, isUnitRangesEqual, isValidRange, LocaleService, RedoCommand, shallowEqual, UndoCommand } from '@univerjs/core'; import type { IUpdateDataValidationSettingCommandParams } from '@univerjs/data-validation'; import { DataValidationModel, DataValidatorRegistryScope, DataValidatorRegistryService, getRuleOptions, getRuleSetting, RemoveDataValidationCommand, TWO_FORMULA_OPERATOR_COUNT, UpdateDataValidationOptionsCommand, UpdateDataValidationSettingCommand } from '@univerjs/data-validation'; import { Button, FormLayout, Select } from '@univerjs/design'; @@ -41,6 +41,7 @@ const debounceExecuteFactory = (commandService: ICommandService) => debounce( 275 ); +// eslint-disable-next-line max-lines-per-function export function DataValidationDetail() { const [key, setKey] = useState(0); const dataValidationPanelService = useDependency(DataValidationPanelService); @@ -101,6 +102,11 @@ export function DataValidationDetail() { ...localRule, ranges, }); + + if (ranges.length === 0) { + return; + } + const params: IUpdateSheetDataValidationRangeCommandParams = { unitId, subUnitId, @@ -158,18 +164,18 @@ export function DataValidationDetail() { } const operators = validator.operators; - - const newRule = { - ...localRule, - type: newType as DataValidationType, - operator: operators[0], - ...(newType === DataValidationType.CHECKBOX - ? { - formula1: undefined, - formula2: undefined, - } - : null), - }; + const rule = dataValidationModel.getRuleById(unitId, subUnitId, ruleId); + const newRule = newType === rule?.type + ? { + ...rule, + } + : { + ...localRule, + type: newType as DataValidationType, + operator: operators[0], + formula1: undefined, + formula2: undefined, + }; setLocalRule(newRule); commandService.executeCommand(UpdateDataValidationSettingCommand.id, { @@ -274,6 +280,7 @@ export function DataValidationDetail() { validResult={validator.validatorFormula(localRule)} unitId={unitId} subUnitId={subUnitId} + ruleId={ruleId} /> ) : null} diff --git a/packages/sheets-data-validation/src/views/formula-input/custom-formula-input.tsx b/packages/sheets-data-validation/src/views/formula-input/custom-formula-input.tsx index 39fb543058..81c19c1654 100644 --- a/packages/sheets-data-validation/src/views/formula-input/custom-formula-input.tsx +++ b/packages/sheets-data-validation/src/views/formula-input/custom-formula-input.tsx @@ -37,6 +37,8 @@ export function CustomFormulaInput(props: IFormulaInputProps) { }); }} onlyInputFormula + openForSheetUnitId={unitId} + openForSheetSubUnitId={subUnitId} /> ); diff --git a/packages/sheets-data-validation/src/views/formula-input/index.module.less b/packages/sheets-data-validation/src/views/formula-input/index.module.less index 61fda630e3..f937656638 100644 --- a/packages/sheets-data-validation/src/views/formula-input/index.module.less +++ b/packages/sheets-data-validation/src/views/formula-input/index.module.less @@ -53,7 +53,6 @@ width: 92px; margin-left: 4px; margin-right: 8px; - height: 31px; } &-color-select-panel { @@ -80,6 +79,5 @@ height: 16px; box-sizing: border-box; font-size: 16px; - margin-top: 2px; } } diff --git a/packages/sheets-data-validation/src/views/formula-input/list-formula-input.tsx b/packages/sheets-data-validation/src/views/formula-input/list-formula-input.tsx index 6e16f171d1..fa4d77bff6 100644 --- a/packages/sheets-data-validation/src/views/formula-input/list-formula-input.tsx +++ b/packages/sheets-data-validation/src/views/formula-input/list-formula-input.tsx @@ -14,24 +14,19 @@ * limitations under the License. */ -import { RangeSelector, useEvent } from '@univerjs/ui'; +import { TextEditor, useEvent, useObservable } from '@univerjs/ui'; import React, { useEffect, useMemo, useRef, useState } from 'react'; import { DraggableList, FormLayout, Input, Radio, RadioGroup, Select } from '@univerjs/design'; -import { deserializeRangeWithSheet, isReferenceStringWithEffectiveColumn, serializeRangeWithSheet } from '@univerjs/engine-formula'; import { useDependency } from '@wendellhu/redi/react-bindings'; -import type { IRange, Workbook } from '@univerjs/core'; -import { createInternalEditorID, IUniverInstanceService, LocaleService, Tools, UniverInstanceType } from '@univerjs/core'; -import type { IFormulaInputProps } from '@univerjs/data-validation'; +import { createInternalEditorID, DataValidationType, isFormulaString, LocaleService, Tools } from '@univerjs/core'; +import { DataValidationModel, DataValidatorRegistryService, type IFormulaInputProps } from '@univerjs/data-validation'; import { DeleteSingle, IncreaseSingle, SequenceSingle } from '@univerjs/icons'; import cs from 'clsx'; -import { deserializeListOptions, getSheetRangeValueSet, serializeListOptions } from '../../validators/util'; +import { deserializeListOptions, serializeListOptions } from '../../validators/util'; import { DROP_DOWN_DEFAULT_COLOR } from '../../common/const'; +import type { ListValidator } from '../../validators'; import styles from './index.module.less'; -function isRangeInValid(range: IRange) { - return Number.isNaN(range.startColumn) || Number.isNaN(range.endColumn) || Number.isNaN(range.startRow) || Number.isNaN(range.endRow); -} - const DEFAULT_COLOR_PRESET = [ '#FFFFFF', '#FEE7E7', @@ -146,24 +141,50 @@ const Template = (props: { item: IDropdownItem; commonProps: any; style?: React. ); }; + export function ListFormulaInput(props: IFormulaInputProps) { - const { value, onChange: _onChange = () => { }, unitId, subUnitId, validResult, showError } = props; + const { value, onChange: _onChange = () => { }, unitId, subUnitId, validResult, showError, ruleId } = props; const { formula1 = '', formula2 = '' } = value || {}; const containerRef = useRef(null); - const [isRefRange, setIsRefRange] = useState(() => isReferenceStringWithEffectiveColumn(formula1) ? '1' : '0'); - const [refRange, setRefRange] = useState(isRefRange === '1' ? formula1 : ''); + const [isFormulaStr, setIsFormulaStr] = useState(() => isFormulaString(formula1) ? '1' : '0'); + const [formulaStr, setFormulaStr] = useState(isFormulaStr === '1' ? formula1 : '='); + const [formulaStrCopy, setFormulaStrCopy] = useState(isFormulaStr === '1' ? formula1 : '='); const localeService = useDependency(LocaleService); - const univerInstanceService = useDependency(IUniverInstanceService); - const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!; - const worksheet = workbook.getActiveSheet(); + const dataValidatorRegistryService = useDependency(DataValidatorRegistryService); + const dataValidationModel = useDependency(DataValidationModel); const [refColors, setRefColors] = useState(() => formula2.split(',')); - + const listValidator = dataValidatorRegistryService.getValidatorItem(DataValidationType.LIST) as ListValidator; + const [refOptions, setRefOptions] = useState([]); + const [localError, setLocalError] = useState(''); const formula1Res = showError ? validResult?.formula1 : ''; + const ruleChange = useObservable(dataValidationModel.ruleChange$); const onChange = useEvent(_onChange); + useEffect(() => { + (async () => { + await new Promise((resolve) => { + setTimeout(() => resolve(true), 100); + }); + + const rule = dataValidationModel.getRuleById(unitId, subUnitId, ruleId); + const formula1 = rule?.formula1; + if (isFormulaString(formula1) && listValidator && rule) { + const res = await listValidator.getListAsync(rule, unitId, subUnitId); + setRefOptions(res); + } + })(); + }, [dataValidationModel, ruleChange, listValidator, ruleId, subUnitId, unitId]); + + useEffect(() => { + if (isFormulaString(formula1) && formula1 !== formulaStrCopy) { + setFormulaStr(formula1); + setFormulaStrCopy(formulaStrCopy); + } + }, [formulaStrCopy, formula1]); + const [strList, setStrList] = useState(() => { - const strOptions = isRefRange !== '1' ? deserializeListOptions(formula1) : []; + const strOptions = isFormulaStr !== '1' ? deserializeListOptions(formula1) : []; const strColors = formula2.split(','); return strOptions.map((label, i) => ({ label, @@ -193,13 +214,6 @@ export function ListFormulaInput(props: IFormulaInputProps) { } }; - const refOptions = useMemo(() => getSheetRangeValueSet( - deserializeRangeWithSheet(refRange), - univerInstanceService, - workbook.getUnitId(), - worksheet.getSheetId() - ), [refRange, univerInstanceService, workbook, worksheet]); - const colorList = formula2.split(','); const refFinalList: IDropdownItem[] = useMemo(() => refOptions.map((label, i) => ({ @@ -259,43 +273,49 @@ export function ListFormulaInput(props: IFormulaInputProps) { }); }, [strList, onChange]); + const updateFormula = useMemo( + () => + async (str: string) => { + if (!isFormulaString(str)) { + onChange?.({ + formula1: '', + formula2, + }); + return; + } + + + onChange?.({ + formula1: isFormulaString(str) ? str : '', + formula2, + }); + }, + [formula2, onChange] + ); + return ( <> - setIsRefRange(v as string)}> + setIsFormulaStr(v as string)}> {localeService.t('dataValidation.list.customOptions')} {localeService.t('dataValidation.list.refOptions')} - {isRefRange === '1' + {isFormulaStr === '1' ? ( <> - - + { - const range = ranges[0]; - if (!range || isRangeInValid(range.range)) { - onChange?.({ - formula1: '', - formula2, - }); - setRefRange(''); - } else { - const workbook = univerInstanceService.getUniverSheetInstance(range.unitId) ?? univerInstanceService.getCurrentUnitForType(UniverInstanceType.SHEET)!; - const worksheet = workbook?.getSheetBySheetId(range.sheetId) ?? workbook.getActiveSheet(); - const rangeStr = serializeRangeWithSheet(worksheet.getName(), range.range); - onChange?.({ - formula1: rangeStr, - formula2, - }); - setRefRange(rangeStr); - } + onlyInputFormula + onChange={async (newString) => { + const str = newString ?? ''; + setFormulaStrCopy(str); + updateFormula(str); }} - isSingleChoice /> @@ -309,12 +329,13 @@ export function ListFormulaInput(props: IFormulaInputProps) { ) : ( -
+
(