feat(sheets-data-validation): daily bugfix for data-validation (#2637)

* fix: #1267

* fix: checkbox formula #1363

* fix: checkbox

* fix: data-validation checkbox

* feat: update

* feat: update

* feat: update remove-sheet

* feat: lint

* feat: update

* feat: update

* fix: #1391

* fix: univer-pro #1407

* feat: update sidebar

* fix: code-review issues

* feat: update
This commit is contained in:
zhangw
2024-07-01 18:34:36 +08:00
committed by GitHub
parent 909f2ee1ed
commit 21008f9724
29 changed files with 461 additions and 136 deletions
@@ -276,27 +276,31 @@ export class CommandService implements ICommandService {
params?: P,
options?: IExecutionOptions
): Promise<R> {
const item = this._commandRegistry.getCommand(id);
if (item) {
const [command] = item;
const commandInfo: ICommandInfo = {
id: command.id,
type: command.type,
params,
};
try {
const item = this._commandRegistry.getCommand(id);
if (item) {
const [command] = item;
const commandInfo: ICommandInfo = {
id: command.id,
type: command.type,
params,
};
const stackItemDisposable = this._pushCommandExecutionStack(commandInfo);
const stackItemDisposable = this._pushCommandExecutionStack(commandInfo);
this._beforeCommandExecutionListeners.forEach((listener) => listener(commandInfo, options));
const result = await this._execute<P, R>(command as ICommand<P, R>, params, options);
this._commandExecutedListeners.forEach((listener) => listener(commandInfo, options));
this._beforeCommandExecutionListeners.forEach((listener) => listener(commandInfo, options));
const result = await this._execute<P, R>(command as ICommand<P, R>, params, options);
this._commandExecutedListeners.forEach((listener) => listener(commandInfo, options));
stackItemDisposable.dispose();
stackItemDisposable.dispose();
return result;
return result;
}
throw new Error(`[CommandService]: command "${id}" is not registered.`);
} catch (error) {
this._logService.error(error);
throw error;
}
throw new Error(`[CommandService]: command "${id}" is not registered.`);
}
syncExecuteCommand<P extends object = object, R = boolean>(
+6 -2
View File
@@ -400,7 +400,9 @@ export class ObjectMatrix<T> {
insertColumns(start: number, count: number): void {
for (let c = start; c < start + count; c++) {
this.forEach((row, data) => {
insertMatrixArray(c, undefined, data);
if (data) {
insertMatrixArray(c, undefined, data);
}
});
}
}
@@ -411,7 +413,9 @@ export class ObjectMatrix<T> {
removeColumns(start: number, count: number): void {
this.forEach((row, value) => {
spliceArray(start, count, value);
if (value) {
spliceArray(start, count, value);
}
});
}
@@ -27,7 +27,7 @@ export interface ICellRenderContext {
data: ICellDataForSheetInterceptor;
style: Nullable<IStyleData>;
primaryWithCoord: ISelectionCellWithCoord;
unitId?: string;
unitId: string;
subUnitId: string;
row: number;
col: number;
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { CommandType, ICommandService, IUndoRedoService } from '@univerjs/core';
import { CommandType, ICommandService, ILogService, IUndoRedoService } from '@univerjs/core';
import type { ICommand, IDataValidationRule, IDataValidationRuleBase, IDataValidationRuleOptions, IMutationInfo, IRange, ISheetDataValidationRule } from '@univerjs/core';
import type { Injector } from '@wendellhu/redi';
import type { ISheetCommandSharedParams } from '@univerjs/sheets';
@@ -32,10 +32,15 @@ export interface IAddDataValidationCommandParams extends ISheetCommandSharedPara
index?: number;
}
/**
* @deprecated
*/
export const AddDataValidationCommand: ICommand<IAddDataValidationCommandParams> = {
type: CommandType.COMMAND,
id: 'data-validation.command.addRule',
async handler(accessor, params) {
const logService = accessor.get(ILogService);
logService.warn('[Deprecated] AddDataValidationCommand is deprecated, please use AddSheetDataValidationCommand in @univerjs/sheets-data-validation instead!');
if (!params) {
return false;
}
@@ -151,10 +156,15 @@ export interface IUpdateDataValidationOptionsCommandParams extends ISheetCommand
options: IDataValidationRuleOptions;
}
/**
* @deprecated
*/
export const UpdateDataValidationOptionsCommand: ICommand<IUpdateDataValidationOptionsCommandParams> = {
type: CommandType.COMMAND,
id: 'data-validation.command.updateDataValidationSetting',
handler(accessor, params) {
const logService = accessor.get(ILogService);
logService.warn('[Deprecated] UpdateDataValidationOptionsCommand is deprecated, please use UpdateSheetDataValidationOptionsCommand in @univerjs/sheets-data-validation instead!');
if (!params) {
return false;
}
@@ -213,10 +223,15 @@ export interface IUpdateDataValidationSettingCommandParams extends ISheetCommand
setting: IDataValidationRuleBase;
}
/**
* @deprecated
*/
export const UpdateDataValidationSettingCommand: ICommand<IUpdateDataValidationSettingCommandParams> = {
type: CommandType.COMMAND,
id: 'data-validation.command.updateDataValidationOptions',
handler(accessor, params) {
const logService = accessor.get(ILogService);
logService.warn('[Deprecated] UpdateDataValidationSettingCommand is deprecated, please use UpdateSheetDataValidationSettingCommand in @univerjs/sheets-data-validation instead!');
if (!params) {
return false;
}
-3
View File
@@ -21,8 +21,6 @@ export { DataValidationModel } from './models/data-validation-model';
export type {
IRemoveDataValidationCommandParams,
IAddDataValidationCommandParams,
IUpdateDataValidationOptionsCommandParams,
IUpdateDataValidationSettingCommandParams,
IRemoveAllDataValidationCommandParams,
} from './commands/commands/data-validation.command';
@@ -30,7 +28,6 @@ export {
AddDataValidationMutation,
RemoveDataValidationMutation,
UpdateDataValidationMutation,
} from './commands/mutations/data-validation.mutation';
export type {
@@ -86,6 +86,7 @@ export class Custom extends SheetExtension {
row,
col,
worksheet,
unitId: worksheet.unitId,
};
// current cell is hidden
@@ -14,14 +14,18 @@
* limitations under the License.
*/
import { CommandType, ICommandService, IUndoRedoService, IUniverInstanceService, sequenceExecuteAsync } from '@univerjs/core';
import type { ICommand, IMutationInfo, IRange, ISheetDataValidationRule } from '@univerjs/core';
import { CommandType, DataValidationType, ICommandService, IUndoRedoService, IUniverInstanceService, ObjectMatrix, Range, sequenceExecute, sequenceExecuteAsync, Tools } from '@univerjs/core';
import type { CellValue, ICellData, ICommand, IDataValidationRuleBase, IDataValidationRuleOptions, IMutationInfo, IRange, ISheetDataValidationRule, Nullable } from '@univerjs/core';
import type { IAddDataValidationMutationParams, IUpdateDataValidationMutationParams } from '@univerjs/data-validation';
import { AddDataValidationMutation, createDefaultNewRule, DataValidationModel, RemoveDataValidationMutation, UpdateDataValidationMutation, UpdateRuleType } from '@univerjs/data-validation';
import { getSheetCommandTarget } from '@univerjs/sheets';
import { AddDataValidationMutation, createDefaultNewRule, DataValidationModel, DataValidatorRegistryService, getRuleOptions, getRuleSetting, RemoveDataValidationMutation, UpdateDataValidationMutation, UpdateRuleType } from '@univerjs/data-validation';
import type { ISetRangeValuesMutationParams, ISheetCommandSharedParams } from '@univerjs/sheets';
import { getSheetCommandTarget, SetRangeValuesMutation, SetRangeValuesUndoMutationFactory } from '@univerjs/sheets';
import type { IAccessor } from '@wendellhu/redi';
import type { SheetDataValidationManager } from '../../models/sheet-data-validation-manager';
import { OpenValidationPanelOperation } from '../operations/data-validation.operation';
import type { RangeMutation } from '../../models/rule-matrix';
import { CHECKBOX_FORMULA_1, CHECKBOX_FORMULA_2, type CheckboxValidator } from '../../validators';
import { getStringCellValue } from '../../utils/get-cell-data-origin';
export interface IUpdateSheetDataValidationRangeCommandParams {
unitId: string;
@@ -30,12 +34,57 @@ export interface IUpdateSheetDataValidationRangeCommandParams {
ranges: IRange[];
}
function isBlankCell(cellData: Nullable<ICellData>) {
if (!cellData) {
return true;
}
if (!cellData.p) {
return Tools.isBlank(cellData.v);
}
const dataStream = (cellData.p.body?.dataStream ?? '').slice(0, -2).trim();
return !dataStream;
}
// eslint-disable-next-line max-lines-per-function
export function getDataValidationDiffMutations(unitId: string, subUnitId: string, diffs: RangeMutation[]) {
export function getDataValidationDiffMutations(
unitId: string,
subUnitId: string,
diffs: RangeMutation[],
accessor: IAccessor
) {
const redoMutations: IMutationInfo[] = [];
const undoMutations: IMutationInfo[] = [];
const model = accessor.get(DataValidationModel);
const manager = model.ensureManager(unitId, subUnitId) as SheetDataValidationManager;
const univerInstanceService = accessor.get(IUniverInstanceService);
const target = getSheetCommandTarget(univerInstanceService, { unitId, subUnitId });
if (!target) {
return {
redoMutations,
undoMutations,
};
}
const { worksheet } = target;
const redoMatrix = new ObjectMatrix<ICellData>();
function setRangesDefaultValue(ranges: IRange[], defaultValue: CellValue) {
ranges.forEach((range) => {
Range.foreach(range, (row, column) => {
const cellData = worksheet.getCellRaw(row, column);
const value = getStringCellValue(cellData);
if (isBlankCell(cellData) || value === defaultValue) {
redoMatrix.setValue(row, column, {
v: defaultValue,
p: null,
});
}
});
});
}
// eslint-disable-next-line max-lines-per-function
diffs.forEach((diff) => {
switch (diff.type) {
case 'delete':
@@ -82,6 +131,12 @@ export function getDataValidationDiffMutations(unitId: string, subUnitId: string
},
} as IUpdateDataValidationMutationParams,
});
const rule = manager.getRuleById(diff.ruleId);
if (rule && rule.type === DataValidationType.CHECKBOX) {
const validator = manager.getValidator(DataValidationType.CHECKBOX) as CheckboxValidator;
const formula = validator.parseFormulaSync(rule, unitId, subUnitId);
setRangesDefaultValue(diff.newRanges, formula.formula2!);
}
break;
}
case 'add': {
@@ -101,6 +156,11 @@ export function getDataValidationDiffMutations(unitId: string, subUnitId: string
ruleId: diff.rule.uid,
},
});
if (diff.rule.type === DataValidationType.CHECKBOX) {
const validator = manager.getValidator(DataValidationType.CHECKBOX) as CheckboxValidator;
const formula = validator.parseFormulaSync(diff.rule, unitId, subUnitId);
setRangesDefaultValue(diff.rule.ranges, formula.formula2!);
}
break;
}
default:
@@ -108,6 +168,23 @@ export function getDataValidationDiffMutations(unitId: string, subUnitId: string
}
});
const redoSetRangeValues = {
id: SetRangeValuesMutation.id,
params: {
unitId,
subUnitId,
cellValue: redoMatrix.getData(),
} as ISetRangeValuesMutationParams,
};
const undoSetRangeValues = {
id: SetRangeValuesMutation.id,
params: SetRangeValuesUndoMutationFactory(accessor, redoSetRangeValues.params),
};
redoMutations.push(redoSetRangeValues);
undoMutations.push(undoSetRangeValues);
return {
redoMutations,
undoMutations,
@@ -117,7 +194,7 @@ export function getDataValidationDiffMutations(unitId: string, subUnitId: string
export const UpdateSheetDataValidationRangeCommand: ICommand<IUpdateSheetDataValidationRangeCommandParams> = {
type: CommandType.COMMAND,
id: 'sheet.command.updateDataValidationRuleRange',
async handler(accessor, params) {
async handler(accessor, params) {
if (!params) {
return false;
}
@@ -135,7 +212,7 @@ export const UpdateSheetDataValidationRangeCommand: ICommand<IUpdateSheetDataVal
matrix.updateRange(ruleId, oldRanges, ranges);
const diffs = matrix.diff(manager.getDataValidations());
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs);
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs, accessor);
undoRedoService.pushUndoRedo({
undoMutations,
@@ -176,7 +253,7 @@ export const AddSheetDataValidationCommand: ICommand<IAddSheetDataValidationComm
rule,
};
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs);
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs, accessor);
redoMutations.push({
id: AddDataValidationMutation.id,
@@ -236,3 +313,185 @@ export const AddSheetDataValidationAndOpenCommand: ICommand = {
return false;
},
};
export interface IUpdateSheetDataValidationSettingCommandParams extends ISheetCommandSharedParams {
ruleId: string;
setting: IDataValidationRuleBase;
}
export const UpdateSheetDataValidationSettingCommand: ICommand<IUpdateSheetDataValidationSettingCommandParams> = {
type: CommandType.COMMAND,
id: 'sheets.command.update-data-validation-setting',
// eslint-disable-next-line max-lines-per-function
handler(accessor, params) {
if (!params) {
return false;
}
const commandService = accessor.get(ICommandService);
const redoUndoService = accessor.get(IUndoRedoService);
const dataValidationModel = accessor.get(DataValidationModel);
const dataValidatorRegistryService = accessor.get(DataValidatorRegistryService);
const { unitId, subUnitId, ruleId, setting } = params;
const validator = dataValidatorRegistryService.getValidatorItem(setting.type);
if (!validator) {
return false;
}
const rule = dataValidationModel.getRuleById(unitId, subUnitId, ruleId);
if (!rule) {
return false;
}
if (!validator.validatorFormula({ ...rule, ...setting }, unitId, subUnitId).success) {
return false;
}
const mutationParams: IUpdateDataValidationMutationParams = {
unitId,
subUnitId,
ruleId,
payload: {
type: UpdateRuleType.SETTING,
payload: setting,
},
};
const redoMutations: IMutationInfo[] = [{
id: UpdateDataValidationMutation.id,
params: mutationParams,
}];
const undoMutationParams: IUpdateDataValidationMutationParams = {
unitId,
subUnitId,
ruleId,
payload: {
type: UpdateRuleType.SETTING,
payload: getRuleSetting(rule),
},
};
const undoMutations: IMutationInfo[] = [{
id: UpdateDataValidationMutation.id,
params: undoMutationParams,
}];
if (setting.type === DataValidationType.CHECKBOX) {
const ranges = rule.ranges as IRange[];
const univerInstanceService = accessor.get(IUniverInstanceService);
const target = getSheetCommandTarget(univerInstanceService, { unitId, subUnitId });
if (target) {
const redoMatrix = new ObjectMatrix<ICellData>();
const { worksheet } = target;
const { formula2: oldFormula2 = CHECKBOX_FORMULA_2, formula1: oldFormula1 = CHECKBOX_FORMULA_1 } = rule;
const { formula2 = CHECKBOX_FORMULA_2, formula1 = CHECKBOX_FORMULA_1 } = setting;
ranges.forEach((range) => {
Range.foreach(range, (row, column) => {
const cellData = worksheet.getCellRaw(row, column);
const value = getStringCellValue(cellData);
if (isBlankCell(cellData) || value === String(oldFormula2)) {
redoMatrix.setValue(row, column, {
v: formula2,
p: null,
});
} else if (value === String(oldFormula1)) {
redoMatrix.setValue(row, column, {
v: formula1,
p: null,
});
}
});
});
const redoSetRangeValues = {
id: SetRangeValuesMutation.id,
params: {
unitId,
subUnitId,
cellValue: redoMatrix.getData(),
} as ISetRangeValuesMutationParams,
};
const undoSetRangeValues = {
id: SetRangeValuesMutation.id,
params: SetRangeValuesUndoMutationFactory(accessor, redoSetRangeValues.params),
};
redoMutations.push(redoSetRangeValues);
undoMutations.push(undoSetRangeValues);
}
}
const res = sequenceExecute(redoMutations, commandService);
if (res.result) {
redoUndoService.pushUndoRedo({
unitID: unitId,
redoMutations,
undoMutations,
});
return true;
}
return false;
},
};
export interface IUpdateSheetDataValidationOptionsCommandParams extends ISheetCommandSharedParams {
ruleId: string;
options: IDataValidationRuleOptions;
}
export const UpdateSheetDataValidationOptionsCommand: ICommand<IUpdateSheetDataValidationOptionsCommandParams> = {
type: CommandType.COMMAND,
id: 'sheets.command.update-data-validation-options',
handler(accessor, params) {
if (!params) {
return false;
}
const commandService = accessor.get(ICommandService);
const redoUndoService = accessor.get(IUndoRedoService);
const dataValidationModel = accessor.get(DataValidationModel);
const { unitId, subUnitId, ruleId, options } = params;
const rule = dataValidationModel.getRuleById(unitId, subUnitId, ruleId);
if (!rule) {
return false;
}
const mutationParams: IUpdateDataValidationMutationParams = {
unitId,
subUnitId,
ruleId,
payload: {
type: UpdateRuleType.OPTIONS,
payload: options,
},
};
const redoMutations: IMutationInfo[] = [{
id: UpdateDataValidationMutation.id,
params: mutationParams,
}];
const undoMutationParams: IUpdateDataValidationMutationParams = {
unitId,
subUnitId,
ruleId,
payload: {
type: UpdateRuleType.OPTIONS,
payload: getRuleOptions(rule),
},
};
const undoMutations: IMutationInfo[] = [{
id: UpdateDataValidationMutation.id,
params: undoMutationParams,
}];
redoUndoService.pushUndoRedo({
unitID: unitId,
redoMutations,
undoMutations,
});
commandService.executeCommand(UpdateDataValidationMutation.id, mutationParams);
return true;
},
};
@@ -17,7 +17,7 @@
import { DataValidationType, Disposable, LifecycleStages, OnLifecycle, Range, Rectangle } from '@univerjs/core';
import type { IAutoFillLocation, ISheetAutoFillHook } from '@univerjs/sheets-ui';
import { APPLY_TYPE, getAutoFillRepeatRange, IAutoFillService, virtualizeDiscreteRanges } from '@univerjs/sheets-ui';
import { Inject } from '@wendellhu/redi';
import { Inject, Injector } from '@wendellhu/redi';
import { DataValidationModel } from '@univerjs/data-validation';
import { DATA_VALIDATION_PLUGIN_NAME } from '../common/const';
import type { SheetDataValidationManager } from '../models/sheet-data-validation-manager';
@@ -27,7 +27,8 @@ import { getDataValidationDiffMutations } from '../commands/commands/data-valida
export class DataValidationAutoFillController extends Disposable {
constructor(
@IAutoFillService private readonly _autoFillService: IAutoFillService,
@Inject(DataValidationModel) private readonly _dataValidationModel: DataValidationModel
@Inject(DataValidationModel) private readonly _dataValidationModel: DataValidationModel,
@Inject(Injector) private readonly _injector: Injector
) {
super();
this._initAutoFill();
@@ -95,7 +96,7 @@ export class DataValidationAutoFillController extends Disposable {
});
const diffs = ruleMatrixCopy.diff(manager.getDataValidations());
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs);
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs, this._injector);
return {
undos: undoMutations,
redos: redoMutations,
@@ -153,7 +153,8 @@ export class DataValidationCopyPasteController extends Disposable {
const { redoMutations, undoMutations } = getDataValidationDiffMutations(
copyInfo.unitId,
copyInfo.subUnitId,
ruleMatrix.diffWithAddition(manager.getDataValidations(), additionRules.values())
ruleMatrix.diffWithAddition(manager.getDataValidations(), additionRules.values()),
this._injector
);
return {
@@ -187,7 +188,8 @@ export class DataValidationCopyPasteController extends Disposable {
const { redoMutations, undoMutations } = getDataValidationDiffMutations(
unitId,
subUnitId,
ruleMatrix.diff(manager.getDataValidations())
ruleMatrix.diff(manager.getDataValidations()),
this._injector
);
return {
@@ -27,7 +27,7 @@ import { INTERCEPTOR_POINT, SheetInterceptorService } from '@univerjs/sheets';
import { DataValidationPanel, DATE_DROPDOWN_KEY, DateDropdown, LIST_DROPDOWN_KEY, ListDropDown } from '../views';
import { FORMULA_INPUTS } from '../views/formula-input';
import { getCellValueOrigin } from '../utils/get-cell-data-origin';
import type { CheckboxValidator, ListValidator } from '../validators';
import type { ListValidator } from '../validators';
import type { SheetDataValidationManager } from '../models/sheet-data-validation-manager';
import { CellDropdown, DROP_DOWN_KEY } from '../views/drop-down';
import { DataValidationDropdownManagerService } from '../services/dropdown-manager.service';
@@ -256,18 +256,12 @@ export class SheetsDataValidationRenderController extends RxDisposable {
}
if (rule.type === DataValidationType.CHECKBOX) {
const { formula2 } = (validator as CheckboxValidator).parseFormulaSync(rule, pos.unitId, pos.subUnitId);
if (!cellValue) {
extra = {
v: formula2,
t: 1,
p: null,
interceptorStyle: {
...cell?.interceptorStyle,
tb: WrapStrategy.CLIP,
},
};
}
extra = {
interceptorStyle: {
...cell?.interceptorStyle,
tb: WrapStrategy.CLIP,
},
};
}
if (rule.type === DataValidationType.LIST && (rule.renderMode === DataValidationRenderMode.ARROW || rule.renderMode === DataValidationRenderMode.TEXT)) {
@@ -454,18 +448,13 @@ export class SheetsDataValidationMobileRenderController extends RxDisposable {
}
if (rule.type === DataValidationType.CHECKBOX) {
const { formula2 } = (validator as CheckboxValidator).parseFormulaSync(rule, pos.unitId, pos.subUnitId);
if (!cellValue) {
extra = {
v: formula2,
t: 1,
p: null,
interceptorStyle: {
...cell?.interceptorStyle,
tb: WrapStrategy.CLIP,
},
};
}
extra = {
interceptorStyle: {
...cell?.interceptorStyle,
tb: WrapStrategy.CLIP,
},
};
}
if (rule.type === DataValidationType.LIST && (rule.renderMode === DataValidationRenderMode.ARROW || rule.renderMode === DataValidationRenderMode.TEXT)) {
@@ -123,7 +123,7 @@ export class DataValidationController extends RxDisposable {
selections && ruleMatrix.removeRange(selections);
const diffs = ruleMatrix.diff(manager.getDataValidations());
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs);
const { redoMutations, undoMutations } = getDataValidationDiffMutations(unitId, subUnitId, diffs, this._injector);
return {
undos: undoMutations,
@@ -20,6 +20,7 @@ const locale: typeof zhCN = {
dataValidation: {
title: 'Data validation',
validFail: {
value: 'Please input a value',
common: 'Please input value or formula',
number: 'Please input number or formula',
formula: 'Please input formula',
@@ -30,6 +31,7 @@ const locale: typeof zhCN = {
checkboxEqual: 'Enter different values for ticked and unticked cell contents.',
formulaError: 'The reference range contains invisible data, please readjust the range',
listIntersects: 'The selected range cannot intersect with the scope of the rules',
primitive: 'Formulas are not permitted for custom ticked and unticked values.',
},
panel: {
title: 'Data validation management',
@@ -43,6 +45,7 @@ const locale: typeof zhCN = {
removeRule: 'Remove',
done: 'Done',
formulaPlaceholder: 'Please input value or formula',
valuePlaceholder: 'Please input value',
formulaAnd: 'and',
invalid: 'Invalid',
showWarning: 'Show warning',
@@ -20,6 +20,7 @@ const locale: typeof zhCN = {
dataValidation: {
title: 'Проверка данных',
validFail: {
value: 'Пожалуйста, введите значение',
common: 'Пожалуйста, введите значение или формулу',
number: 'Пожалуйста, введите число или формулу',
formula: 'Пожалуйста, введите формулу',
@@ -30,6 +31,7 @@ const locale: typeof zhCN = {
checkboxEqual: 'Для выбранных и не выбранных ячеек введите разные значения',
formulaError: 'Диапазон ссылок содержит невидимые данные, пожалуйста, пересмотрите диапазон',
listIntersects: 'Выбранный диапазон не может пересекаться с диапазоном правила.',
primitive: 'Formulas are not permitted for custom ticked and unticked values.',
},
panel: {
title: 'Управление проверкой данных',
@@ -43,6 +45,7 @@ const locale: typeof zhCN = {
removeRule: 'Удалить',
done: 'Готово',
formulaPlaceholder: 'Пожалуйста, введите значение или формулу',
valuePlaceholder: 'Пожалуйста, введите значение',
formulaAnd: 'и',
invalid: 'Недопустимо',
showWarning: 'Показать предупреждение',
@@ -18,9 +18,10 @@ const locale = {
dataValidation: {
title: '数据验证',
validFail: {
value: '请输入一个合法值',
common: '请输入值或公式',
number: '请输入合法的数字或公式',
formula: '请输入合法的公式或公式',
formula: '请输入合法的公式',
integer: '请输入合法的整数或公式',
date: '请输入合法的日期或公式',
list: '请输入至少一个合法选项',
@@ -28,6 +29,7 @@ const locale = {
checkboxEqual: '为勾选和未勾选的单元格内容输入不同的值。',
formulaError: '引用范围内包含不可见的数据,请重新调整范围',
listIntersects: '所选范围不能和规则范围相交',
primitive: '自定义勾选和未勾选值不允许使用公式。',
},
panel: {
title: '管理数据验证',
@@ -41,6 +43,7 @@ const locale = {
removeRule: '删除规则',
done: '确认',
formulaPlaceholder: '请输入值或公式',
valuePlaceholder: '请输入值',
formulaAnd: '与',
invalid: '数据无效时',
showWarning: '显示警告',
@@ -126,6 +126,10 @@ export class SheetDataValidationManager extends DataValidationManager<ISheetData
super.removeRule(ruleId);
}
getValidator(type: DataValidationType) {
return this._dataValidatorRegistryService.getValidatorItem(type);
}
getRuleIdByLocation(row: number, col: number): string | undefined {
return this._ruleMatrix.getValue(row, col);
}
@@ -142,7 +146,7 @@ export class SheetDataValidationManager extends DataValidationManager<ISheetData
override validator(cellValue: Nullable<CellValue>, rule: ISheetDataValidationRule, pos: ISheetLocationBase, onCompete: (status: DataValidationStatus, changed: boolean) => void): DataValidationStatus {
const { col, row, unitId, subUnitId } = pos;
const ruleId = rule.uid;
const validator = this._dataValidatorRegistryService.getValidatorItem(rule.type);
const validator = this.getValidator(rule.type);
if (validator) {
const current = this._cache.getValue(row, col);
if (!current || current.value !== cellValue) {
@@ -24,7 +24,7 @@ import { DefaultSheetsDataValidation, SheetsDataValidationRenderController } fro
import { DataValidationController } from './controllers/dv.controller';
import { SheetDataValidationService } from './services/dv.service';
import { DataValidationAlertController } from './controllers/dv-alert.controller';
import { AddSheetDataValidationAndOpenCommand, AddSheetDataValidationCommand, UpdateSheetDataValidationRangeCommand } from './commands/commands/data-validation.command';
import { AddSheetDataValidationAndOpenCommand, AddSheetDataValidationCommand, UpdateSheetDataValidationOptionsCommand, UpdateSheetDataValidationRangeCommand, UpdateSheetDataValidationSettingCommand } from './commands/commands/data-validation.command';
import { DataValidationCacheService } from './services/dv-cache.service';
import { DataValidationFormulaService } from './services/dv-formula.service';
import { DataValidationCustomFormulaService } from './services/dv-custom-formula.service';
@@ -91,6 +91,8 @@ export class UniverSheetsDataValidationPlugin extends Plugin {
AddSheetDataValidationCommand,
AddSheetDataValidationAndOpenCommand,
UpdateSheetDataValidationRangeCommand,
UpdateSheetDataValidationSettingCommand,
UpdateSheetDataValidationOptionsCommand,
// operation
ShowDataValidationDropdown,
@@ -37,3 +37,7 @@ export function getCellValueOrigin(cell: Nullable<ICellData>) {
return cell?.v;
}
export function getStringCellValue(cell: Nullable<ICellData>) {
return String(getCellValueOrigin(cell) ?? '');
}
@@ -15,10 +15,9 @@
*/
import { DataValidationType, isFormulaString, Tools } from '@univerjs/core';
import type { CellValue, DataValidationOperator, IDataValidationRule, IDataValidationRuleBase, ISheetDataValidationRule, Nullable } from '@univerjs/core';
import type { CellValue, DataValidationOperator, IDataValidationRule, IDataValidationRuleBase, ISheetDataValidationRule, LocaleService, Nullable } from '@univerjs/core';
import { BaseDataValidator } from '@univerjs/data-validation';
import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '@univerjs/data-validation';
import type { ISheetLocation } from '@univerjs/sheets';
import { CheckboxRender } from '../widgets/checkbox-widget';
import { DataValidationFormulaService } from '../services/dv-formula.service';
import { getFormulaResult } from '../utils/formula';
@@ -27,6 +26,18 @@ import { CHECKBOX_FORMULA_INPUT_NAME } from '../views/formula-input';
export const CHECKBOX_FORMULA_1 = 1;
export const CHECKBOX_FORMULA_2 = 0;
function getFailMessage(formula: string | undefined, localeService: LocaleService) {
if (Tools.isBlank(formula)) {
return localeService.t('dataValidation.validFail.value');
}
if (isFormulaString(formula)) {
return localeService.t('dataValidation.validFail.primitive');
}
return '';
}
export class CheckboxValidator extends BaseDataValidator {
override id: string = DataValidationType.CHECKBOX;
override title: string = 'dataValidation.checkbox.title';
@@ -38,31 +49,40 @@ export class CheckboxValidator extends BaseDataValidator {
private _formulaService = this.injector.get(DataValidationFormulaService);
override skipDefaultFontRender(rule: ISheetDataValidationRule, cellValue: Nullable<CellValue>, pos: ISheetLocation) {
override skipDefaultFontRender(rule: ISheetDataValidationRule, cellValue: Nullable<CellValue>, pos: { unitId: string; subUnitId: string }) {
const { formula1, formula2 } = this.parseFormulaSync(rule, pos.unitId, pos.subUnitId);
const valueStr = `${cellValue ?? ''}`;
return !valueStr || (valueStr === (`${formula1}`) || valueStr === `${formula2}`);
const res = !valueStr || (valueStr === (`${formula1}`) || valueStr === `${formula2}`);
return res;
}
override validatorFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): IFormulaValidResult {
const { formula1, formula2 } = rule;
const formula1Success = !Tools.isBlank(formula1);
const formula2Success = !Tools.isBlank(formula2);
const isEqual = formula1 === formula2;
if (Tools.isBlank(formula1) && Tools.isBlank(formula2)) {
return {
success: true,
};
}
if (isEqual) {
return {
success: false,
formula1: this.localeService.t('dataValidation.validFail.checkboxEqual'),
formula2: this.localeService.t('dataValidation.validFail.checkboxEqual'),
};
}
const error1 = getFailMessage(formula1, this.localeService);
const error2 = getFailMessage(formula2, this.localeService);
return {
success: (formula1Success && formula2Success && !isEqual) || (!formula1Success && !formula2Success),
formula1: !formula1Success ?
this.localeService.t('dataValidation.validFail.common')
: isEqual ?
this.localeService.t('dataValidation.validFail.checkboxEqual')
: '',
formula2: !formula2Success ?
this.localeService.t('dataValidation.validFail.common')
: isEqual ?
this.localeService.t('dataValidation.validFail.checkboxEqual')
: '',
success: (!error1 && !error2),
formula1: error1,
formula2: error2,
};
}
@@ -16,15 +16,14 @@
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 { DataValidationModel, DataValidatorRegistryScope, DataValidatorRegistryService, getRuleOptions, getRuleSetting, RemoveDataValidationCommand, TWO_FORMULA_OPERATOR_COUNT } from '@univerjs/data-validation';
import { Button, FormLayout, Select } from '@univerjs/design';
import { ComponentManager, RangeSelector, useEvent, useObservable } from '@univerjs/ui';
import { useDependency } from '@wendellhu/redi/react-bindings';
import React, { useEffect, useMemo, useState } from 'react';
import { serializeRange } from '@univerjs/engine-formula';
import type { IUpdateSheetDataValidationRangeCommandParams } from '../../commands/commands/data-validation.command';
import { UpdateSheetDataValidationRangeCommand } from '../../commands/commands/data-validation.command';
import { UpdateSheetDataValidationOptionsCommand, UpdateSheetDataValidationRangeCommand, UpdateSheetDataValidationSettingCommand } from '../../commands/commands/data-validation.command';
import { DataValidationOptions } from '../options';
import { DataValidationPanelService } from '../../services/data-validation-panel.service';
import styles from './index.module.less';
@@ -124,7 +123,7 @@ export function DataValidationDetail() {
...localRule,
...setting,
});
const params: IUpdateDataValidationSettingCommandParams = {
const params = {
unitId,
subUnitId,
ruleId,
@@ -132,7 +131,7 @@ export function DataValidationDetail() {
};
debounceExecute(
UpdateDataValidationSettingCommand.id,
UpdateSheetDataValidationSettingCommand.id,
params,
undefined
);
@@ -176,7 +175,7 @@ export function DataValidationDetail() {
};
setLocalRule(newRule);
commandService.executeCommand(UpdateDataValidationSettingCommand.id, {
commandService.executeCommand(UpdateSheetDataValidationSettingCommand.id, {
unitId,
subUnitId,
ruleId: localRule.uid,
@@ -199,7 +198,7 @@ export function DataValidationDetail() {
});
debounceExecute(
UpdateDataValidationOptionsCommand.id,
UpdateSheetDataValidationOptionsCommand.id,
{
unitId,
subUnitId,
@@ -55,7 +55,7 @@ export function CheckboxFormulaInput(props: IFormulaInputProps) {
<FormLayout label={localeService.t('dataValidation.checkbox.checked')} error={formula1Res}>
<Input
className={styles.dataValidationFormula}
placeholder={localeService.t('dataValidation.panel.formulaPlaceholder')}
placeholder={localeService.t('dataValidation.panel.valuePlaceholder')}
value={value?.formula1}
onChange={(newValue) => {
onChange?.({
@@ -72,7 +72,7 @@ export function CheckboxFormulaInput(props: IFormulaInputProps) {
<FormLayout label={localeService.t('dataValidation.checkbox.unchecked')} error={formula2Res}>
<Input
className={styles.dataValidationFormula}
placeholder={localeService.t('dataValidation.panel.formulaPlaceholder')}
placeholder={localeService.t('dataValidation.panel.valuePlaceholder')}
value={value?.formula2}
onChange={(newValue) => {
onChange?.({
@@ -28,7 +28,6 @@ import { DataValidationItem } from '../item';
import type { IAddSheetDataValidationCommandParams } from '../../commands/commands/data-validation.command';
import { AddSheetDataValidationCommand } from '../../commands/commands/data-validation.command';
import { DataValidationPanelService } from '../../services/data-validation-panel.service';
import { DataValidationController } from '../../controllers/dv.controller';
import styles from './index.module.less';
export function DataValidationList() {
@@ -50,8 +49,6 @@ function DataValidationListWithWorkbook(props: { workbook: Workbook }) {
const commandService = useDependency(ICommandService);
const injector = useDependency(Injector);
const dataValidationPanelService = useDependency(DataValidationPanelService);
const dataValidationController = useDependency(DataValidationController);
const localeService = useDependency(LocaleService);
const [rules, setRules] = useState<ISheetDataValidationRule[]>([]);
@@ -22,6 +22,7 @@ import type { ISetRangeValuesCommandParams } from '@univerjs/sheets';
import { SetRangeValuesCommand } from '@univerjs/sheets';
import type { BaseDataValidator, IBaseDataValidationWidget, IFormulaResult } from '@univerjs/data-validation';
import { Inject } from '@wendellhu/redi';
import type { CheckboxValidator } from '../validators/checkbox-validator';
import { CHECKBOX_FORMULA_1, CHECKBOX_FORMULA_2 } from '../validators/checkbox-validator';
import { DataValidationFormulaService } from '../services/dv-formula.service';
import { getFormulaResult } from '../utils/formula';
@@ -97,18 +98,18 @@ export class CheckboxRender implements IBaseDataValidationWidget {
const cellBounding = primaryWithCoord.isMergedMainCell ? primaryWithCoord.mergeInfo : primaryWithCoord;
const value = getCellValueOrigin(data);
const rule = data.dataValidation?.rule;
const validator = data.dataValidation?.validator as BaseDataValidator;
const validator = data.dataValidation?.validator as CheckboxValidator;
if (!rule || !validator) {
return;
}
const colors = this._themeService.getCurrentTheme();
if (!validator.skipDefaultFontRender(rule, value, { unitId, subUnitId })) {
if (!validator.skipDefaultFontRender(rule, value, { unitId: unitId!, subUnitId })) {
return;
}
const { formula1 = CHECKBOX_FORMULA_1 } = rule;
const result = validator.parseFormulaSync(rule, unitId, subUnitId);
const { formula1 } = result;
const layout = this._calc(cellBounding, style);
const { a: scaleX, d: scaleY } = ctx.getTransform();
const left = fixLineWidthByScale(layout.left, scaleX);
+1 -1
View File
@@ -91,7 +91,7 @@ export function getClearContentMutationParamsForRanges(
function getClearContentMutationParamForRange(worksheet: Worksheet, range: IRange): ObjectMatrix<Nullable<ICellData>> {
const { startRow, startColumn, endColumn, endRow } = range;
const cellMatrix = worksheet.getMatrixWithMergedCells(startRow, startColumn, endRow, endColumn);
const cellMatrix = worksheet.getMatrixWithMergedCells(startRow, startColumn, endRow, endColumn, true);
const redoMatrix = new ObjectMatrix<Nullable<ICellData>>();
let leftTopCellValue: Nullable<ICellData> = null;
cellMatrix.forValue((row, col, cellData) => {
@@ -56,6 +56,7 @@ export class CellCustomRenderController extends Disposable implements IRenderMod
const currentRender = this._renderManagerService.getRenderById(unitId);
if (currentRender && currentRender.mainComponent) {
const spreadsheet = currentRender.mainComponent as Spreadsheet;
// eslint-disable-next-line max-lines-per-function
const getActiveRender = (evt: IPointerEvent | IMouseEvent) => {
const { offsetX, offsetY } = evt;
const scene = currentRender.scene;
@@ -14,10 +14,9 @@
* limitations under the License.
*/
import type { Nullable, Workbook, Worksheet } from '@univerjs/core';
import { Disposable, type Nullable, type Workbook, type Worksheet } from '@univerjs/core';
import type { IRenderContext, IRenderModule } from '@univerjs/engine-render';
import { SpreadsheetSkeleton } from '@univerjs/engine-render';
import type { IDisposable } from '@wendellhu/redi';
import { Inject, Injector } from '@wendellhu/redi';
import { BehaviorSubject } from 'rxjs';
@@ -43,7 +42,7 @@ export interface ISheetSkeletonManagerSearch {
* The viewModel is also a temporary storage variable, which does not need to be persisted,
* so it is managed uniformly through the service.
*/
export class SheetSkeletonManagerService implements IDisposable, IRenderModule {
export class SheetSkeletonManagerService extends Disposable implements IRenderModule {
private _currentSkeletonSearchParam: ISheetSkeletonManagerSearch = {
sheetId: '',
};
@@ -64,12 +63,23 @@ export class SheetSkeletonManagerService implements IDisposable, IRenderModule {
@Inject(Injector) private readonly _injector: Injector
) {
// empty
super();
this.disposeWithMe(() => {
this._currentSkeletonBefore$.complete();
this._currentSkeleton$.complete();
this._sheetSkeletonParam = [];
});
this._initRemoveSheet();
}
dispose(): void {
this._currentSkeletonBefore$.complete();
this._currentSkeleton$.complete();
this._sheetSkeletonParam = [];
private _initRemoveSheet() {
this.disposeWithMe(this._context.unit.sheetDisposed$.subscribe((sheet) => {
this.disposeSkeleton({
sheetId: sheet.getSheetId(),
});
}));
}
getCurrentSkeleton(): SpreadsheetSkeleton {
@@ -162,6 +172,15 @@ export class SheetSkeletonManagerService implements IDisposable, IRenderModule {
return newSkeleton;
}
disposeSkeleton(searchParm: ISheetSkeletonManagerSearch) {
const index = this._sheetSkeletonParam.findIndex((param) => param.sheetId === searchParm.sheetId);
if (index > -1) {
const skeleton = this._sheetSkeletonParam[index];
skeleton.skeleton.dispose();
this._sheetSkeletonParam.splice(index, 1);
}
}
private _getSkeleton(searchParm: ISheetSkeletonManagerSearch): Nullable<ISheetSkeletonManagerParam> {
const item = this._sheetSkeletonParam.find((param) => param.sheetId === searchParm.sheetId);
if (item != null) {
@@ -150,6 +150,7 @@ export class UniverSheetsUIPlugin extends Plugin {
[SheetPermissionInterceptorBaseController],
[SheetPermissionInitController],
[MoveRangeController],
] as Dependency[]
).forEach((d) => injector.add(d));
@@ -218,6 +219,7 @@ export class UniverSheetsUIPlugin extends Plugin {
SheetPermissionInterceptorCanvasRenderController,
SheetPermissionInterceptorFormulaRenderController,
SheetPermissionRenderController,
]).forEach((m) => {
this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m));
});
@@ -104,7 +104,7 @@ function getClearContentMutationParamsForRanges(
function getClearContentMutationParamForRange(worksheet: Worksheet, range: IRange): ObjectMatrix<Nullable<ICellData>> {
const { startRow, startColumn, endColumn, endRow } = range;
const cellMatrix = worksheet.getMatrixWithMergedCells(startRow, startColumn, endRow, endColumn);
const cellMatrix = worksheet.getMatrixWithMergedCells(startRow, startColumn, endRow, endColumn, true);
const redoMatrix = new ObjectMatrix<Nullable<ICellData>>();
cellMatrix.forValue((row, col, cellData) => {
if (cellData && (row !== startRow || col !== startColumn)) {
@@ -68,7 +68,6 @@ class WatchRange extends Disposable {
if (mutation.params.unitId !== this._unitId || mutation.params.subUnitId !== this._subUnitId) {
return;
}
if (!this._range) {
return;
}
@@ -122,7 +121,6 @@ export class RefRangeService extends Disposable {
if (this._watchRanges.size === 0) {
watchRangesListener = this._commandService.onCommandExecuted((command) => {
if (command.type !== CommandType.MUTATION) return false;
for (const watchRange of this._watchRanges) {
watchRange.onMutation(command as IMutationInfo<ISheetCommandSharedParams>);
}
@@ -17,51 +17,49 @@
import { CloseSingle } from '@univerjs/icons';
import { useDependency } from '@wendellhu/redi/react-bindings';
import clsx from 'clsx';
import React, { useEffect, useMemo, useState } from 'react';
import React, { useMemo } from 'react';
import { CustomLabel } from '../../../components/custom-label/CustomLabel';
import { ISidebarService } from '../../../services/sidebar/sidebar.service';
import { useObservable } from '../../../components/hooks/observable';
import styles from './index.module.less';
import type { ISidebarMethodOptions } from './interface';
export function Sidebar() {
const sidebarService = useDependency(ISidebarService);
const sidebarOptions = useObservable<ISidebarMethodOptions>(sidebarService.sidebarOptions$);
const [sidebarOptions, setSidebarOptions] = useState<ISidebarMethodOptions>({});
const options = useMemo(() => {
if (!sidebarOptions) {
return null;
}
useEffect(() => {
const subscribtion = sidebarService.sidebarOptions$.subscribe((options: ISidebarMethodOptions) => {
setSidebarOptions(options);
});
return () => {
subscribtion.unsubscribe();
const copy = { ...sidebarOptions } as Omit<ISidebarMethodOptions, 'children'> & {
children?: React.ReactNode;
header?: React.ReactNode;
footer?: React.ReactNode;
};
}, []);
const options = sidebarOptions as Omit<ISidebarMethodOptions, 'children'> & {
children?: React.ReactNode;
header?: React.ReactNode;
footer?: React.ReactNode;
};
for (const key of ['children', 'header', 'footer']) {
const k = key as keyof ISidebarMethodOptions;
for (const key of ['children', 'header', 'footer']) {
const k = key as keyof ISidebarMethodOptions;
if (sidebarOptions[k]) {
const props = sidebarOptions[k] as any;
if (sidebarOptions[k]) {
const props = sidebarOptions[k] as any;
if (props) {
(options as any)[k] = <CustomLabel {...props} />;
if (props) {
(copy as any)[k] = <CustomLabel {...props} />;
}
}
}
}
return copy;
}, [sidebarOptions]);
const _className = clsx(styles.sidebar, {
[styles.sidebarOpen]: options.visible,
[styles.sidebarOpen]: options?.visible,
});
const width = useMemo(() => {
if (!options.visible) return 0;
if (!options?.visible) return 0;
if (typeof options.width === 'number') {
return `${options.width}px`;
@@ -76,7 +74,6 @@ export function Sidebar() {
visible: false,
};
setSidebarOptions(options);
sidebarService.sidebarOptions$.next(options);
options?.onClose?.();
}