snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 05:09:45 +05:00
commit bcf6ef63ad
7118 changed files with 1236854 additions and 0 deletions

View File

@@ -0,0 +1,317 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ILogService, Injector, ISheetDataValidationRule, IWorkbookData, Workbook } from '@univerjs/core';
import {
DataValidationOperator,
DataValidationType,
ICommandService,
IResourceLoaderService,
IResourceManagerService,
LocaleType,
Univer,
UniverInstanceType,
} from '@univerjs/core';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
AddDataValidationMutation,
RemoveDataValidationMutation,
UpdateDataValidationMutation,
} from '../commands/mutations/data-validation.mutation';
import { DataValidationResourceController } from '../controllers/dv-resource.controller';
import { DataValidationModel } from '../models/data-validation-model';
import { UpdateRuleType } from '../types/enum/update-rule-type';
function createWorkbookData(): IWorkbookData {
return {
id: 'unit-1',
appVersion: '3.0.0-alpha',
locale: LocaleType.EN_US,
name: 'Workbook',
sheetOrder: ['sheet-1', 'sheet-2'],
styles: {},
sheets: {
'sheet-1': {
id: 'sheet-1',
name: 'Sheet1',
cellData: {},
},
'sheet-2': {
id: 'sheet-2',
name: 'Sheet2',
cellData: {},
},
},
};
}
function createRule(uid: string, overrides: Partial<ISheetDataValidationRule> = {}): ISheetDataValidationRule {
return {
uid,
type: DataValidationType.DECIMAL,
operator: DataValidationOperator.BETWEEN,
formula1: '1',
formula2: '5',
allowBlank: true,
showErrorMessage: true,
error: `Rule ${uid} failed`,
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
...overrides,
} as ISheetDataValidationRule;
}
describe('data validation integration', () => {
let univer: Univer;
let get: Injector['get'];
let commandService: ICommandService;
let resourceManagerService: IResourceManagerService;
let resourceLoaderService: IResourceLoaderService;
let dataValidationModel: DataValidationModel;
beforeEach(() => {
univer = new Univer();
const injector = univer.__getInjector();
get = injector.get.bind(injector);
injector.add([DataValidationModel]);
injector.add([DataValidationResourceController]);
commandService = get(ICommandService);
commandService.registerCommand(AddDataValidationMutation);
commandService.registerCommand(RemoveDataValidationMutation);
commandService.registerCommand(UpdateDataValidationMutation);
dataValidationModel = get(DataValidationModel);
resourceManagerService = get(IResourceManagerService);
resourceLoaderService = get(IResourceLoaderService);
get(DataValidationResourceController);
univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
});
afterEach(() => {
univer.dispose();
});
it('applies add, update, and remove mutations through the real command service', async () => {
const changes: Array<{ type: string; ruleId: string; source: string }> = [];
const sub = dataValidationModel.ruleChange$.subscribe((change) => {
changes.push({ type: change.type, ruleId: change.rule.uid, source: change.source });
});
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
rule: createRule('rule-1'),
});
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
index: 0,
rule: [createRule('rule-2'), createRule('rule-1'), createRule('rule-3')],
});
await commandService.executeCommand(UpdateDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
ruleId: 'rule-1',
payload: {
type: UpdateRuleType.SETTING,
payload: {
type: DataValidationType.TEXT_LENGTH,
operator: DataValidationOperator.GREATER_THAN,
formula1: '3',
formula2: undefined,
allowBlank: false,
},
},
});
await commandService.executeCommand(UpdateDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
ruleId: 'rule-1',
payload: {
type: UpdateRuleType.OPTIONS,
payload: {
prompt: 'Type a longer value',
promptTitle: 'Length rule',
showErrorMessage: false,
bizInfo: {
source: 'integration-spec',
},
},
},
});
await commandService.executeCommand(UpdateDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
ruleId: 'rule-2',
payload: {
type: UpdateRuleType.RANGE,
payload: [{ startRow: 2, endRow: 3, startColumn: 1, endColumn: 2 }],
},
});
await commandService.executeCommand(UpdateDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
ruleId: 'rule-3',
payload: {
type: UpdateRuleType.ALL,
payload: createRule('rule-3', {
type: DataValidationType.LIST,
operator: undefined,
formula1: 'Yes,No',
formula2: undefined,
showDropDown: false,
ranges: [{ startRow: 4, endRow: 4, startColumn: 0, endColumn: 1 }],
}),
},
});
await commandService.executeCommand(RemoveDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
ruleId: ['rule-2', 'rule-3'],
});
const rules = dataValidationModel.getRules('unit-1', 'sheet-1');
expect(rules.map((rule) => rule.uid)).toEqual(['rule-1']);
expect(dataValidationModel.getRuleIndex('unit-1', 'sheet-1', 'rule-1')).toBe(0);
expect(dataValidationModel.getRuleById('unit-1', 'sheet-1', 'rule-1')).toMatchObject({
type: DataValidationType.TEXT_LENGTH,
operator: DataValidationOperator.GREATER_THAN,
formula1: '3',
allowBlank: false,
prompt: 'Type a longer value',
promptTitle: 'Length rule',
showErrorMessage: false,
bizInfo: {
source: 'integration-spec',
},
});
expect(changes.map((change) => change.type)).toEqual([
'add',
'add',
'add',
'update',
'update',
'update',
'update',
'remove',
'remove',
]);
expect(changes.every((change) => change.source === 'command')).toBe(true);
sub.unsubscribe();
});
it('serializes, unloads, and loads rule resources through the resource manager', async () => {
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-1',
rule: createRule('sheet-1-rule'),
});
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId: 'unit-1',
subUnitId: 'sheet-2',
rule: createRule('sheet-2-rule', {
ranges: [{ startRow: 9, endRow: 9, startColumn: 0, endColumn: 0 }],
}),
});
const snapshot = resourceLoaderService.saveUnit('unit-1');
const resource = snapshot?.resources.find((item) => item.name === 'SHEET_DATA_VALIDATION_PLUGIN');
expect(resource).toBeDefined();
expect(JSON.parse(resource!.data)).toEqual({
'sheet-1': [createRule('sheet-1-rule')],
'sheet-2': [createRule('sheet-2-rule', {
ranges: [{ startRow: 9, endRow: 9, startColumn: 0, endColumn: 0 }],
})],
});
resourceManagerService.unloadResources('unit-1', UniverInstanceType.UNIVER_SHEET);
expect(dataValidationModel.getUnitRules('unit-1')).toEqual([]);
resourceManagerService.loadResources('unit-1', [{
name: 'SHEET_DATA_VALIDATION_PLUGIN',
data: JSON.stringify({
'sheet-1': [createRule('loaded-rule')],
'sheet-2': [createRule('loaded-rule-2', {
type: DataValidationType.LIST,
formula1: 'A,B',
operator: undefined,
})],
}),
}]);
expect(dataValidationModel.getRules('unit-1', 'sheet-1')).toEqual([createRule('loaded-rule')]);
expect(dataValidationModel.getRules('unit-1', 'sheet-2')).toEqual([
createRule('loaded-rule-2', {
type: DataValidationType.LIST,
formula1: 'A,B',
operator: undefined,
}),
]);
resourceManagerService.loadResources('unit-1', [{
name: 'SHEET_DATA_VALIDATION_PLUGIN',
data: '{invalid-json',
}]);
expect(dataValidationModel.getSubUnitIds('unit-1')).toEqual(['sheet-1', 'sheet-2']);
});
it('reports model errors and exposes unit level getters without changing source code', () => {
const logService = {
error: vi.fn(),
} as unknown as ILogService;
const model = new DataValidationModel(logService);
model.addRule('unit-2', 'sheet-x', createRule('rule-a'), 'patched');
model.addRule('unit-2', 'sheet-y', createRule('rule-b'), 'patched');
expect(model.getSubUnitIds('unit-2')).toEqual(['sheet-x', 'sheet-y']);
expect(model.getUnitRules('unit-2')).toEqual([
['sheet-x', [createRule('rule-a')]],
['sheet-y', [createRule('rule-b')]],
]);
expect(model.getAll()).toContainEqual([
'unit-2',
[
['sheet-x', [createRule('rule-a')]],
['sheet-y', [createRule('rule-b')]],
],
]);
model.updateRule('unit-2', 'sheet-x', 'missing-rule', {
type: UpdateRuleType.ALL,
payload: createRule('missing-rule'),
}, 'patched');
expect(logService.error).toHaveBeenCalled();
model.deleteUnitRules('unit-2');
expect(model.getUnitRules('unit-2')).toEqual([]);
model.dispose();
});
});

View File

@@ -0,0 +1,282 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IDataValidationRule, Injector } from '@univerjs/core';
import type { IFormulaResult, IFormulaValidResult, IValidatorCellInfo } from '../validators/base-data-validator';
import {
DataValidationOperator,
DataValidationType,
LocaleService,
LocaleType,
Univer,
} from '@univerjs/core';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { DataValidatorRegistryScope, DataValidatorRegistryService } from '../services/data-validator-registry.service';
import { BaseDataValidator } from '../validators/base-data-validator';
function createRule(overrides: Partial<IDataValidationRule> = {}): IDataValidationRule {
return {
uid: 'rule-1',
type: DataValidationType.DECIMAL,
operator: DataValidationOperator.BETWEEN,
formula1: '1',
formula2: '5',
allowBlank: true,
showErrorMessage: true,
error: 'Explicit error',
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
...overrides,
} as IDataValidationRule;
}
function createCellInfo(value: unknown): IValidatorCellInfo {
return {
value: value as never,
interceptValue: null,
row: 0,
column: 0,
unitId: 'unit-1',
subUnitId: 'sheet-1',
worksheet: null as never,
workbook: null as never,
t: null,
};
}
class TestDataValidator extends BaseDataValidator {
id = 'validator.number';
title = 'validator.number';
operators = [
DataValidationOperator.BETWEEN,
DataValidationOperator.EQUAL,
DataValidationOperator.GREATER_THAN,
DataValidationOperator.GREATER_THAN_OR_EQUAL,
DataValidationOperator.LESS_THAN,
DataValidationOperator.LESS_THAN_OR_EQUAL,
DataValidationOperator.NOT_BETWEEN,
DataValidationOperator.NOT_EQUAL,
];
scopes = [DataValidatorRegistryScope.SHEET, 'custom-scope'];
order = 1;
formulaResult: IFormulaResult<number | undefined> = {
formula1: 1,
formula2: 5,
isFormulaValid: true,
};
validType = true;
override async parseFormula(rule: IDataValidationRule, _unitId: string, _subUnitId: string, _row: number, _column: number): Promise<IFormulaResult<number | undefined>> {
return {
...this.formulaResult,
formula1: Number(rule.formula1 ?? this.formulaResult.formula1),
formula2: rule.formula2 == null ? undefined : Number(rule.formula2),
};
}
override validatorFormula(rule: IDataValidationRule, _unitId: string, _subUnitId: string): IFormulaValidResult {
return {
success: true,
formula1: rule.formula1,
formula2: rule.formula2,
};
}
override async isValidType(): Promise<boolean> {
return this.validType;
}
override transform(cellInfo: IValidatorCellInfo): IValidatorCellInfo<number> {
return {
...cellInfo,
value: Number(cellInfo.value),
};
}
}
describe('data validator behavior', () => {
let univer: Univer;
let get: Injector['get'];
let localeService: LocaleService;
let registryService: DataValidatorRegistryService;
let validator: TestDataValidator;
beforeEach(() => {
univer = new Univer();
const injector = univer.__getInjector();
get = injector.get.bind(injector);
injector.add([DataValidatorRegistryService]);
localeService = get(LocaleService);
localeService.load({
[LocaleType.EN_US]: {
validator: {
number: 'Number',
},
'data-validation': {
operators: {
between: 'between',
equal: 'equal',
greaterThan: 'greater than',
greaterThanOrEqual: 'greater than or equal',
lessThan: 'less than',
lessThanOrEqual: 'less than or equal',
notBetween: 'not between',
notEqual: 'not equal',
},
ruleName: {
between: 'between {FORMULA1} and {FORMULA2}',
equal: 'equal to {FORMULA1}',
greaterThan: 'greater than {FORMULA1}',
greaterThanOrEqual: 'greater than or equal to {FORMULA1}',
lessThan: 'less than {FORMULA1}',
lessThanOrEqual: 'less than or equal to {FORMULA1}',
notBetween: 'not between {FORMULA1} and {FORMULA2}',
notEqual: 'not equal to {FORMULA1}',
legal: 'Legal {TYPE}',
},
errorMsg: {
between: 'Value must be between {FORMULA1} and {FORMULA2}',
equal: 'Value must equal {FORMULA1}',
greaterThan: 'Value must be greater than {FORMULA1}',
greaterThanOrEqual: 'Value must be greater than or equal to {FORMULA1}',
lessThan: 'Value must be less than {FORMULA1}',
lessThanOrEqual: 'Value must be less than or equal to {FORMULA1}',
notBetween: 'Value must not be between {FORMULA1} and {FORMULA2}',
notEqual: 'Value must not equal {FORMULA1}',
legal: 'Value must satisfy {TYPE}',
},
},
},
});
localeService.setLocale(LocaleType.EN_US);
registryService = get(DataValidatorRegistryService);
validator = new TestDataValidator(localeService, injector);
});
afterEach(() => {
univer.dispose();
});
it('registers validators by scope and removes them with the returned disposable', () => {
const changes: number[] = [];
const sub = registryService.validatorsChange$.subscribe(() => changes.push(changes.length));
const disposable = registryService.register(validator);
expect(registryService.getValidatorItem(validator.id)).toBe(validator);
expect(registryService.getValidatorsByScope(DataValidatorRegistryScope.SHEET)).toContain(validator);
expect(registryService.getValidatorsByScope('custom-scope')).toContain(validator);
expect(() => registryService.register(new TestDataValidator(localeService, get as never))).toThrow(/same id/);
disposable.dispose();
expect(registryService.getValidatorItem(validator.id)).toBeUndefined();
expect(registryService.getValidatorsByScope(DataValidatorRegistryScope.SHEET)).toEqual([]);
expect(changes.length).toBeGreaterThanOrEqual(3);
sub.unsubscribe();
});
it('generates translated rule labels and validates values through the main validator flow', async () => {
expect(validator.operatorNames).toEqual([
'between',
'equal',
'greater than',
'greater than or equal',
'less than',
'less than or equal',
'not between',
'not equal',
]);
expect(validator.titleStr).toBe('Number');
expect(validator.generateRuleName(createRule())).toBe('Number between 1 and 5');
expect(validator.generateRuleName(createRule({ operator: undefined }))).toBe('Legal Number');
expect(validator.generateRuleErrorMessage(createRule({ operator: DataValidationOperator.LESS_THAN, formula1: '10' }), {
row: 0,
col: 0,
unitId: 'unit-1',
subUnitId: 'sheet-1',
})).toBe('Value must be less than 10');
expect(validator.getRuleFinalError(createRule(), {
row: 0,
col: 0,
unitId: 'unit-1',
subUnitId: 'sheet-1',
})).toBe('Explicit error');
expect(validator.getRuleFinalError(createRule({ showErrorMessage: false, error: 'ignored' }), {
row: 0,
col: 0,
unitId: 'unit-1',
subUnitId: 'sheet-1',
})).toBe('Value must be between 1 and 5');
expect(validator.isEmptyCellValue('')).toBe(true);
expect(validator.isEmptyCellValue(null)).toBe(true);
expect(validator.isEmptyCellValue(0)).toBe(false);
expect(validator.normalizeFormula(createRule({ formula1: '=A1', formula2: '=B1' }), 'unit-1', 'sheet-1')).toEqual({
formula1: '=A1',
formula2: '=B1',
});
expect(validator.validatorFormula(createRule({ formula1: '7', formula2: '9' }), 'unit-1', 'sheet-1')).toEqual({
success: true,
formula1: '7',
formula2: '9',
});
await expect(validator.validator(createCellInfo(''), createRule({ allowBlank: false }))).resolves.toBe(false);
validator.formulaResult = {
formula1: 1,
formula2: 5,
isFormulaValid: false,
};
await expect(validator.validator(createCellInfo(3), createRule())).resolves.toBe(false);
validator.formulaResult = {
formula1: 1,
formula2: 5,
isFormulaValid: true,
};
validator.validType = false;
await expect(validator.validator(createCellInfo(3), createRule())).resolves.toBe(false);
validator.validType = true;
await expect(validator.validator(createCellInfo(3), createRule({ operator: undefined }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(3), createRule({ operator: DataValidationOperator.BETWEEN }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(9), createRule({ operator: DataValidationOperator.NOT_BETWEEN }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(5), createRule({ operator: DataValidationOperator.EQUAL, formula1: '5' }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(5), createRule({ operator: DataValidationOperator.NOT_EQUAL, formula1: '6' }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(6), createRule({ operator: DataValidationOperator.GREATER_THAN, formula1: '5' }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(5), createRule({ operator: DataValidationOperator.GREATER_THAN_OR_EQUAL, formula1: '5' }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(4), createRule({ operator: DataValidationOperator.LESS_THAN, formula1: '5' }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(5), createRule({ operator: DataValidationOperator.LESS_THAN_OR_EQUAL, formula1: '5' }))).resolves.toBe(true);
validator.formulaResult = {
formula1: Number.NaN,
formula2: Number.NaN,
isFormulaValid: true,
};
await expect(validator.validator(createCellInfo(10), createRule({ operator: DataValidationOperator.BETWEEN, formula1: 'NaN', formula2: 'NaN' }))).resolves.toBe(true);
await expect(validator.validator(createCellInfo(10), createRule({ operator: 'unexpected' as DataValidationOperator }))).rejects.toThrow('Unknown operator.');
});
});

View File

@@ -0,0 +1,134 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ISheetDataValidationRule } from '@univerjs/core';
import { DataValidationOperator, DataValidationType, ICommandService, Univer } from '@univerjs/core';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { DataValidationModel } from '../../../models/data-validation-model';
import { UpdateRuleType } from '../../../types/enum/update-rule-type';
import {
AddDataValidationMutation,
RemoveDataValidationMutation,
UpdateDataValidationMutation,
} from '../data-validation.mutation';
const unitId = 'validation-workbook';
const subUnitId = 'validation-sheet';
function createRule(uid: string, overrides: Partial<ISheetDataValidationRule> = {}): ISheetDataValidationRule {
return {
uid,
type: DataValidationType.DECIMAL,
operator: DataValidationOperator.BETWEEN,
formula1: '1',
formula2: '10',
allowBlank: true,
showErrorMessage: true,
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
...overrides,
} as ISheetDataValidationRule;
}
describe('data validation mutations', () => {
let univer: Univer;
let commandService: ICommandService;
let dataValidationModel: DataValidationModel;
beforeEach(() => {
univer = new Univer();
const injector = univer.__getInjector();
injector.add([DataValidationModel]);
commandService = injector.get(ICommandService);
commandService.registerCommand(AddDataValidationMutation);
commandService.registerCommand(UpdateDataValidationMutation);
commandService.registerCommand(RemoveDataValidationMutation);
dataValidationModel = injector.get(DataValidationModel);
});
afterEach(() => {
univer.dispose();
});
it('keeps validation rules in user-defined order while ignoring duplicate rule ids', async () => {
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId,
subUnitId,
rule: createRule('rule-1'),
});
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId,
subUnitId,
index: 0,
rule: [createRule('rule-2'), createRule('rule-1'), createRule('rule-3')],
});
const ruleIds: string[] = [];
for (const rule of dataValidationModel.getRules(unitId, subUnitId)) {
ruleIds.push(rule.uid);
}
expect(ruleIds).toEqual(['rule-2', 'rule-3', 'rule-1']);
expect(dataValidationModel.getRuleIndex(unitId, subUnitId, 'rule-1')).toBe(2);
});
it('updates validation behavior and removes obsolete rules from the worksheet', async () => {
await commandService.executeCommand(AddDataValidationMutation.id, {
unitId,
subUnitId,
rule: [
createRule('keep-rule'),
createRule('remove-rule'),
],
});
await commandService.executeCommand(UpdateDataValidationMutation.id, {
unitId,
subUnitId,
ruleId: 'keep-rule',
payload: {
type: UpdateRuleType.ALL,
payload: createRule('keep-rule', {
type: DataValidationType.TEXT_LENGTH,
operator: DataValidationOperator.GREATER_THAN,
formula1: '3',
formula2: undefined,
allowBlank: false,
prompt: 'Enter at least four characters',
promptTitle: 'Length check',
ranges: [{ startRow: 2, endRow: 4, startColumn: 1, endColumn: 1 }],
}),
},
});
await commandService.executeCommand(RemoveDataValidationMutation.id, {
unitId,
subUnitId,
ruleId: 'remove-rule',
});
expect(dataValidationModel.getRuleById(unitId, subUnitId, 'remove-rule')).toBeUndefined();
expect(dataValidationModel.getRuleById(unitId, subUnitId, 'keep-rule')).toMatchObject({
type: DataValidationType.TEXT_LENGTH,
operator: DataValidationOperator.GREATER_THAN,
formula1: '3',
formula2: undefined,
allowBlank: false,
prompt: 'Enter at least four characters',
promptTitle: 'Length check',
ranges: [{ startRow: 2, endRow: 4, startColumn: 1, endColumn: 1 }],
});
});
});

View File

@@ -0,0 +1,96 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ICommand, IDataValidationRule } from '@univerjs/core';
import type { DataValidationChangeSource } from '../../models/data-validation-model';
import type { IUpdateRulePayload } from '../../types/interfaces/i-update-rule-payload';
import { CommandType } from '@univerjs/core';
import { DataValidationModel } from '../../models/data-validation-model';
export interface IAddDataValidationMutationParams {
rule: IDataValidationRule | IDataValidationRule[];
index?: number;
source?: DataValidationChangeSource;
unitId: string;
subUnitId: string;
}
export const AddDataValidationMutation: ICommand<IAddDataValidationMutationParams> = {
type: CommandType.MUTATION,
id: 'data-validation.mutation.addRule',
handler(accessor, params) {
if (!params) {
return false;
}
const { unitId, subUnitId, rule, index, source = 'command' } = params;
const dataValidationModel = accessor.get(DataValidationModel);
dataValidationModel.addRule(unitId, subUnitId, rule, source, index);
return true;
},
};
export interface IRemoveDataValidationMutationParams {
ruleId: string | string[];
source?: DataValidationChangeSource;
unitId: string;
subUnitId: string;
}
export const RemoveDataValidationMutation: ICommand<IRemoveDataValidationMutationParams> = {
type: CommandType.MUTATION,
id: 'data-validation.mutation.removeRule',
handler(accessor, params) {
if (!params) {
return false;
}
const { unitId, subUnitId, ruleId, source = 'command' } = params;
const dataValidationModel = accessor.get(DataValidationModel);
if (Array.isArray(ruleId)) {
ruleId.forEach((item) => {
dataValidationModel.removeRule(unitId, subUnitId, item, source);
});
} else {
dataValidationModel.removeRule(unitId, subUnitId, ruleId, source);
}
return true;
},
};
export interface IUpdateDataValidationMutationParams {
payload: IUpdateRulePayload;
ruleId: string;
source?: DataValidationChangeSource;
unitId: string;
subUnitId: string;
}
export const UpdateDataValidationMutation: ICommand<IUpdateDataValidationMutationParams> = {
type: CommandType.MUTATION,
id: 'data-validation.mutation.updateRule',
handler(accessor, params) {
if (!params) {
return false;
}
const { unitId, subUnitId, ruleId, payload, source = 'command' } = params;
const dataValidationModel = accessor.get(DataValidationModel);
dataValidationModel.updateRule(unitId, subUnitId, ruleId, payload, source);
return true;
},
};

View File

@@ -0,0 +1,43 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IDataValidationRuleBase, IDataValidationRuleOptions } from '@univerjs/core';
export function getRuleSetting(rule: IDataValidationRuleBase): IDataValidationRuleBase {
return {
type: rule.type,
operator: rule.operator,
formula1: rule.formula1,
formula2: rule.formula2,
allowBlank: rule.allowBlank,
};
}
export function getRuleOptions(rule: IDataValidationRuleOptions): IDataValidationRuleOptions {
return {
error: rule.error,
errorStyle: rule.errorStyle,
errorTitle: rule.errorTitle,
imeMode: rule.imeMode,
prompt: rule.prompt,
promptTitle: rule.promptTitle,
showDropDown: rule.showDropDown,
showErrorMessage: rule.showErrorMessage,
showInputMessage: rule.showInputMessage,
renderMode: rule.renderMode,
bizInfo: rule.bizInfo,
};
}

View File

@@ -0,0 +1,24 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 DATA_VALIDATION_PLUGIN_CONFIG_KEY = 'data-validation.config';
export const configSymbol = Symbol(DATA_VALIDATION_PLUGIN_CONFIG_KEY);
export interface IUniverDataValidationConfig {
}
export const defaultPluginConfig: IUniverDataValidationConfig = {};

View File

@@ -0,0 +1,77 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ISheetDataValidationRule } from '@univerjs/core';
import { Disposable, Inject, IResourceManagerService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
import { DataValidationModel } from '../models/data-validation-model';
type DataValidationJSON = Record<string, ISheetDataValidationRule[]>;
const DATA_VALIDATION_PLUGIN_NAME = 'SHEET_DATA_VALIDATION_PLUGIN';
export class DataValidationResourceController extends Disposable {
constructor(
@IResourceManagerService private readonly _resourceManagerService: IResourceManagerService,
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
@Inject(DataValidationModel) private readonly _dataValidationModel: DataValidationModel
) {
super();
this._initSnapshot();
}
private _initSnapshot() {
const toJson = (unitID: string) => {
const map = this._dataValidationModel.getUnitRules(unitID);
const resultMap: DataValidationJSON = {};
if (map) {
map.forEach(([key, v]) => {
resultMap[key] = v;
});
return JSON.stringify(resultMap);
}
return '';
};
const parseJson = (json: string): DataValidationJSON => {
if (!json) {
return {};
}
try {
return JSON.parse(json);
} catch (err) {
return {};
}
};
this.disposeWithMe(
this._resourceManagerService.registerPluginResource<DataValidationJSON>({
pluginName: DATA_VALIDATION_PLUGIN_NAME,
businesses: [UniverInstanceType.UNIVER_SHEET],
toJson: (unitID) => toJson(unitID),
parseJson: (json) => parseJson(json),
onUnLoad: (unitID) => {
this._dataValidationModel.deleteUnitRules(unitID);
},
onLoad: (unitID, value) => {
Object.keys(value).forEach((subunitId) => {
const ruleList = value[subunitId];
ruleList.forEach((rule) => {
this._dataValidationModel.addRule(unitID, subunitId, rule, 'patched');
});
});
},
})
);
}
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 {
AddDataValidationMutation,
RemoveDataValidationMutation,
UpdateDataValidationMutation,
} from './commands/mutations/data-validation.mutation';
export type {
IAddDataValidationMutationParams,
IRemoveDataValidationMutationParams,
IUpdateDataValidationMutationParams,
} from './commands/mutations/data-validation.mutation';
export { getRuleOptions, getRuleSetting } from './common/util';
export { type IUniverDataValidationConfig } from './config/config';
export { DataValidationResourceController } from './controllers/dv-resource.controller';
export { DataValidationModel } from './models/data-validation-model';
export type { DataValidationChangeSource, DataValidationChangeType, IRuleChange } from './models/data-validation-model';
export { UniverDataValidationPlugin } from './plugin';
export { DataValidatorRegistryScope, DataValidatorRegistryService } from './services/data-validator-registry.service';
export { TWO_FORMULA_OPERATOR_COUNT } from './types/const/two-formula-operators';
export { UpdateRuleType } from './types/enum/update-rule-type';
export type { IDataValidationDropdownProps } from './types/interfaces/i-data-validation-drop-down';
export type { IDataValidatorOperatorConfig } from './types/interfaces/i-data-validator-operator-config';
export type { IFormulaValue } from './types/interfaces/i-formula-input';
export type {
IUpdateRuleOptionsPayload,
IUpdateRulePayload,
IUpdateRuleRangePayload,
IUpdateRuleSettingPayload,
} from './types/interfaces/i-update-rule-payload';
export { BaseDataValidator } from './validators/base-data-validator';
export type { IFormulaResult, IValidatorCellInfo } from './validators/base-data-validator';
export type { IFormulaValidResult } from './validators/base-data-validator';
export { DataValidatorDropdownType } from './validators/base-data-validator';
export { FORMULA1, FORMULA2, TYPE } from './validators/base-data-validator';
export type { IBaseDataValidationWidget } from './validators/base-widget';

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'بين',
greaterThan: 'أكبر من',
greaterThanOrEqual: 'أكبر من أو يساوي',
lessThan: 'أقل من',
lessThanOrEqual: 'أقل من أو يساوي',
equal: 'يساوي',
notEqual: 'لا يساوي',
notBetween: 'ليس بين',
},
ruleName: {
between: 'بين {FORMULA1} و{FORMULA2}',
greaterThan: 'أكبر من {FORMULA1}',
greaterThanOrEqual: 'أكبر من أو يساوي {FORMULA1}',
lessThan: 'أقل من {FORMULA1}',
lessThanOrEqual: 'أقل من أو يساوي {FORMULA1}',
equal: 'يساوي {FORMULA1}',
notEqual: 'لا يساوي {FORMULA1}',
notBetween: 'ليس بين {FORMULA1} و{FORMULA2}',
legal: 'نوع قانوني {TYPE}',
},
errorMsg: {
between: 'يجب أن تكون القيمة بين {FORMULA1} و{FORMULA2}',
greaterThan: 'يجب أن تكون القيمة أكبر من {FORMULA1}',
greaterThanOrEqual: 'يجب أن تكون القيمة أكبر من أو تساوي {FORMULA1}',
lessThan: 'يجب أن تكون القيمة أقل من {FORMULA1}',
lessThanOrEqual: 'يجب أن تكون القيمة أقل من أو تساوي {FORMULA1}',
equal: 'يجب أن تكون القيمة تساوي {FORMULA1}',
notEqual: 'يجب ألا تكون القيمة تساوي {FORMULA1}',
notBetween: 'يجب ألا تكون القيمة بين {FORMULA1} و{FORMULA2}',
legal: 'يجب أن تكون القيمة من نوع قانوني {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'entre',
greaterThan: 'més gran que',
greaterThanOrEqual: 'més gran o igual que',
lessThan: 'menys que',
lessThanOrEqual: 'menys o igual que',
equal: 'igual',
notEqual: 'no igual',
notBetween: 'no entre',
},
ruleName: {
between: 'Està entre {FORMULA1} i {FORMULA2}',
greaterThan: 'És més gran que {FORMULA1}',
greaterThanOrEqual: 'És més gran o igual que {FORMULA1}',
lessThan: 'És menys que {FORMULA1}',
lessThanOrEqual: 'És menys o igual que {FORMULA1}',
equal: 'És igual a {FORMULA1}',
notEqual: 'No és igual a {FORMULA1}',
notBetween: 'No està entre {FORMULA1} i {FORMULA2}',
legal: 'És un {TYPE} legal',
},
errorMsg: {
between: 'El valor ha d’estar entre {FORMULA1} i {FORMULA2}',
greaterThan: 'El valor ha de ser més gran que {FORMULA1}',
greaterThanOrEqual: 'El valor ha de ser més gran o igual que {FORMULA1}',
lessThan: 'El valor ha de ser menys que {FORMULA1}',
lessThanOrEqual: 'El valor ha de ser menys o igual que {FORMULA1}',
equal: 'El valor ha de ser igual a {FORMULA1}',
notEqual: 'El valor no ha de ser igual a {FORMULA1}',
notBetween: 'El valor no ha d’estar entre {FORMULA1} i {FORMULA2}',
legal: 'El valor ha de ser un {TYPE} legal',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'zwischen',
greaterThan: 'größer als',
greaterThanOrEqual: 'größer als oder gleich',
lessThan: 'kleiner als',
lessThanOrEqual: 'kleiner als oder gleich',
equal: 'gleich',
notEqual: 'ungleich',
notBetween: 'nicht zwischen',
},
ruleName: {
between: 'Ist zwischen {FORMULA1} und {FORMULA2}',
greaterThan: 'Ist größer als {FORMULA1}',
greaterThanOrEqual: 'Ist größer als oder gleich {FORMULA1}',
lessThan: 'Ist kleiner als {FORMULA1}',
lessThanOrEqual: 'Ist kleiner als oder gleich {FORMULA1}',
equal: 'Ist gleich {FORMULA1}',
notEqual: 'Ist ungleich {FORMULA1}',
notBetween: 'Ist nicht zwischen {FORMULA1} und {FORMULA2}',
legal: 'Ist ein gültiger {TYPE}',
},
errorMsg: {
between: 'Wert muss zwischen {FORMULA1} und {FORMULA2} liegen',
greaterThan: 'Wert muss größer als {FORMULA1} sein',
greaterThanOrEqual: 'Wert muss größer als oder gleich {FORMULA1} sein',
lessThan: 'Wert muss kleiner als {FORMULA1} sein',
lessThanOrEqual: 'Wert muss kleiner als oder gleich {FORMULA1} sein',
equal: 'Wert muss gleich {FORMULA1} sein',
notEqual: 'Wert muss ungleich {FORMULA1} sein',
notBetween: 'Wert muss nicht zwischen {FORMULA1} und {FORMULA2} liegen',
legal: 'Wert muss ein gültiger {TYPE} sein',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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.
*/
const locale = {
'data-validation': {
operators: {
between: 'between',
greaterThan: 'greater than',
greaterThanOrEqual: 'greater than or equal',
lessThan: 'less than',
lessThanOrEqual: 'less than or equal',
equal: 'equal',
notEqual: 'not equal',
notBetween: 'not between',
},
ruleName: {
between: 'Is between {FORMULA1} and {FORMULA2}',
greaterThan: 'Is greater than {FORMULA1}',
greaterThanOrEqual: 'Is greater than or equal to {FORMULA1}',
lessThan: 'Is less than {FORMULA1}',
lessThanOrEqual: 'Is less than or equal to {FORMULA1}',
equal: 'Is equal to {FORMULA1}',
notEqual: 'Is not equal to {FORMULA1}',
notBetween: 'Is not between {FORMULA1} and {FORMULA2}',
legal: 'Is a legal {TYPE}',
},
errorMsg: {
between: 'Value must be between {FORMULA1} and {FORMULA2}',
greaterThan: 'Value must be greater than {FORMULA1}',
greaterThanOrEqual: 'Value must be greater than or equal to {FORMULA1}',
lessThan: 'Value must be less than {FORMULA1}',
lessThanOrEqual: 'Value must be less than or equal to {FORMULA1}',
equal: 'Value must be equal to {FORMULA1}',
notEqual: 'Value must be not equal to {FORMULA1}',
notBetween: 'Value must be not between {FORMULA1} and {FORMULA2}',
legal: 'Value must be a legal {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'entre',
greaterThan: 'mayor que',
greaterThanOrEqual: 'mayor o igual que',
lessThan: 'menor que',
lessThanOrEqual: 'menor o igual que',
equal: 'igual',
notEqual: 'no igual',
notBetween: 'no entre',
},
ruleName: {
between: 'Está entre {FORMULA1} y {FORMULA2}',
greaterThan: 'Es mayor que {FORMULA1}',
greaterThanOrEqual: 'Es mayor o igual que {FORMULA1}',
lessThan: 'Es menor que {FORMULA1}',
lessThanOrEqual: 'Es menor o igual que {FORMULA1}',
equal: 'Es igual a {FORMULA1}',
notEqual: 'No es igual a {FORMULA1}',
notBetween: 'No está entre {FORMULA1} y {FORMULA2}',
legal: 'Es un {TYPE} legal',
},
errorMsg: {
between: 'El valor debe estar entre {FORMULA1} y {FORMULA2}',
greaterThan: 'El valor debe ser mayor que {FORMULA1}',
greaterThanOrEqual: 'El valor debe ser mayor o igual que {FORMULA1}',
lessThan: 'El valor debe ser menor que {FORMULA1}',
lessThanOrEqual: 'El valor debe ser menor o igual que {FORMULA1}',
equal: 'El valor debe ser igual a {FORMULA1}',
notEqual: 'El valor no debe ser igual a {FORMULA1}',
notBetween: 'El valor no debe estar entre {FORMULA1} y {FORMULA2}',
legal: 'El valor debe ser un {TYPE} legal',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'بین',
greaterThan: 'بزرگتر از',
greaterThanOrEqual: 'بزرگتر از یا برابر با',
lessThan: 'کوچکتر از',
lessThanOrEqual: 'کوچکتر از یا برابر با',
equal: 'برابر با',
notEqual: 'نابرابر با',
notBetween: 'بین نیست',
},
ruleName: {
between: 'بین {FORMULA1} و {FORMULA2} است',
greaterThan: 'بزرگتر از {FORMULA1} است',
greaterThanOrEqual: 'بزرگتر از یا برابر با {FORMULA1} است',
lessThan: 'کوچکتر از {FORMULA1} است',
lessThanOrEqual: 'کوچکتر از یا برابر با {FORMULA1} است',
equal: 'برابر است با {FORMULA1}',
notEqual: 'برابر نیست با {FORMULA1}',
notBetween: 'بین {FORMULA1} و {FORMULA2} نیست',
legal: 'یک {TYPE} قانونی است',
},
errorMsg: {
between: 'مقدار باید بین {FORMULA1} و {FORMULA2} باشد',
greaterThan: 'مقدار باید بزرگتر از {FORMULA1} باشد',
greaterThanOrEqual: 'مقدار باید بزرگتر از یا برابر با {FORMULA1} باشد',
lessThan: 'مقدار باید کوچکتر از {FORMULA1} باشد',
lessThanOrEqual: 'مقدار باید کوچکتر از یا برابر با {FORMULA1} باشد',
equal: 'مقدار باید برابر با {FORMULA1} باشد',
notEqual: 'مقدار باید برابر نیست با {FORMULA1}',
notBetween: 'مقدار باید بین {FORMULA1} و {FORMULA2} نباشد',
legal: 'مقدار باید یک {TYPE} قانونی باشد',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'entre',
greaterThan: 'supérieur à',
greaterThanOrEqual: 'supérieur ou égal à',
lessThan: 'inférieur à',
lessThanOrEqual: 'inférieur ou égal à',
equal: 'égal à',
notEqual: 'différent de',
notBetween: 'pas entre',
},
ruleName: {
between: 'est entre {FORMULA1} et {FORMULA2}',
greaterThan: 'est supérieur à {FORMULA1}',
greaterThanOrEqual: 'est supérieur ou égal à {FORMULA1}',
lessThan: 'est inférieur à {FORMULA1}',
lessThanOrEqual: 'est inférieur ou égal à {FORMULA1}',
equal: 'est égal à {FORMULA1}',
notEqual: 'est différent de {FORMULA1}',
notBetween: 'n\'est pas entre {FORMULA1} et {FORMULA2}',
legal: 'est un {TYPE} légal',
},
errorMsg: {
between: 'La valeur doit être entre {FORMULA1} et {FORMULA2}',
greaterThan: 'La valeur doit être supérieure à {FORMULA1}',
greaterThanOrEqual: 'La valeur doit être supérieure ou égale à {FORMULA1}',
lessThan: 'La valeur doit être inférieure à {FORMULA1}',
lessThanOrEqual: 'La valeur doit être inférieure ou égale à {FORMULA1}',
equal: 'La valeur doit être égale à {FORMULA1}',
notEqual: 'La valeur doit être différente de {FORMULA1}',
notBetween: 'La valeur ne doit pas être entre {FORMULA1} et {FORMULA2}',
legal: 'La valeur doit être un {TYPE} légal',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'antara',
greaterThan: 'lebih besar dari',
greaterThanOrEqual: 'lebih besar dari atau sama dengan',
lessThan: 'lebih kecil dari',
lessThanOrEqual: 'lebih kecil dari atau sama dengan',
equal: 'sama dengan',
notEqual: 'tidak sama dengan',
notBetween: 'tidak antara',
},
ruleName: {
between: 'Di antara {FORMULA1} dan {FORMULA2}',
greaterThan: 'Lebih besar dari {FORMULA1}',
greaterThanOrEqual: 'Lebih besar dari atau sama dengan {FORMULA1}',
lessThan: 'Lebih kecil dari {FORMULA1}',
lessThanOrEqual: 'Lebih kecil dari atau sama dengan {FORMULA1}',
equal: 'Sama dengan {FORMULA1}',
notEqual: 'Tidak sama dengan {FORMULA1}',
notBetween: 'Tidak di antara {FORMULA1} dan {FORMULA2}',
legal: 'Adalah {TYPE} yang valid',
},
errorMsg: {
between: 'Nilai harus di antara {FORMULA1} dan {FORMULA2}',
greaterThan: 'Nilai harus lebih besar dari {FORMULA1}',
greaterThanOrEqual: 'Nilai harus lebih besar dari atau sama dengan {FORMULA1}',
lessThan: 'Nilai harus lebih kecil dari {FORMULA1}',
lessThanOrEqual: 'Nilai harus lebih kecil dari atau sama dengan {FORMULA1}',
equal: 'Nilai harus sama dengan {FORMULA1}',
notEqual: 'Nilai harus tidak sama dengan {FORMULA1}',
notBetween: 'Nilai harus tidak di antara {FORMULA1} dan {FORMULA2}',
legal: 'Nilai harus merupakan {TYPE} yang valid',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'tra',
greaterThan: 'maggiore di',
greaterThanOrEqual: 'maggiore o uguale a',
lessThan: 'minore di',
lessThanOrEqual: 'minore o uguale a',
equal: 'uguale a',
notEqual: 'diverso da',
notBetween: 'non tra',
},
ruleName: {
between: 'È tra {FORMULA1} e {FORMULA2}',
greaterThan: 'È maggiore di {FORMULA1}',
greaterThanOrEqual: 'È maggiore o uguale a {FORMULA1}',
lessThan: 'È minore di {FORMULA1}',
lessThanOrEqual: 'È minore o uguale a {FORMULA1}',
equal: 'È uguale a {FORMULA1}',
notEqual: 'È diverso da {FORMULA1}',
notBetween: 'Non è tra {FORMULA1} e {FORMULA2}',
legal: 'È un {TYPE} valido',
},
errorMsg: {
between: 'Il valore deve essere tra {FORMULA1} e {FORMULA2}',
greaterThan: 'Il valore deve essere maggiore di {FORMULA1}',
greaterThanOrEqual: 'Il valore deve essere maggiore o uguale a {FORMULA1}',
lessThan: 'Il valore deve essere minore di {FORMULA1}',
lessThanOrEqual: 'Il valore deve essere minore o uguale a {FORMULA1}',
equal: 'Il valore deve essere uguale a {FORMULA1}',
notEqual: 'Il valore deve essere diverso da {FORMULA1}',
notBetween: 'Il valore non deve essere tra {FORMULA1} e {FORMULA2}',
legal: 'Il valore deve essere un {TYPE} valido',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: '次の値の間',
greaterThan: '次の値より大きい',
greaterThanOrEqual: '次の値以上',
lessThan: '次の値より小さい',
lessThanOrEqual: '次の値以下',
equal: '次の値に等しい',
notEqual: '次の値に等しくない',
notBetween: '次の値の間以外',
},
ruleName: {
between: '{FORMULA1} と {FORMULA2} の間',
greaterThan: '{FORMULA1} より大きい',
greaterThanOrEqual: '{FORMULA1} 以上',
lessThan: '{FORMULA1} より小さい',
lessThanOrEqual: '{FORMULA1} 以下',
equal: '{FORMULA1} に等しい',
notEqual: '{FORMULA1} に等しくない',
notBetween: '{FORMULA1} と {FORMULA2} の間以外',
legal: '有効な {TYPE} 形式',
},
errorMsg: {
between: '値は {FORMULA1} と {FORMULA2} の間である必要があります',
greaterThan: '値は {FORMULA1} より大きい必要があります',
greaterThanOrEqual: '値は {FORMULA1} 以上である必要があります',
lessThan: '値は {FORMULA1} より小さい必要があります',
lessThanOrEqual: '値は {FORMULA1} 以下である必要があります',
equal: '値は {FORMULA1} に等しい必要があります',
notEqual: '値は {FORMULA1} に等しくない必要があります',
notBetween: '値は {FORMULA1} と {FORMULA2} の間以外である必要があります',
legal: '値は有効な {TYPE} 形式である必要があります',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: '사이',
greaterThan: '초과',
greaterThanOrEqual: '이상',
lessThan: '미만',
lessThanOrEqual: '이하',
equal: '같음',
notEqual: '같지 않음',
notBetween: '사이가 아님',
},
ruleName: {
between: '{FORMULA1}와 {FORMULA2} 사이',
greaterThan: '{FORMULA1} 초과',
greaterThanOrEqual: '{FORMULA1} 이상',
lessThan: '{FORMULA1} 미만',
lessThanOrEqual: '{FORMULA1} 이하',
equal: '{FORMULA1}와 같음',
notEqual: '{FORMULA1}와 다름',
notBetween: '{FORMULA1}와 {FORMULA2} 사이 아님',
legal: '유효한 {TYPE} 형식',
},
errorMsg: {
between: '값은 {FORMULA1}와 {FORMULA2} 사이여야 합니다',
greaterThan: '값은 {FORMULA1} 초과여야 합니다',
greaterThanOrEqual: '값은 {FORMULA1} 이상이어야 합니다',
lessThan: '값은 {FORMULA1} 미만이어야 합니다',
lessThanOrEqual: '값은 {FORMULA1} 이하이어야 합니다',
equal: '값은 {FORMULA1}와 같아야 합니다',
notEqual: '값은 {FORMULA1}와 달라야 합니다',
notBetween: '값은 {FORMULA1}와 {FORMULA2} 사이가 아니어야 합니다',
legal: '값은 유효한 {TYPE} 형식이어야 합니다',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'między',
greaterThan: 'większe niż',
greaterThanOrEqual: 'większe lub równe',
lessThan: 'mniejsze niż',
lessThanOrEqual: 'mniejsze lub równe',
equal: 'równe',
notEqual: 'różne od',
notBetween: 'nie między',
},
ruleName: {
between: 'Jest między {FORMULA1} a {FORMULA2}',
greaterThan: 'Jest większe niż {FORMULA1}',
greaterThanOrEqual: 'Jest większe lub równe {FORMULA1}',
lessThan: 'Jest mniejsze niż {FORMULA1}',
lessThanOrEqual: 'Jest mniejsze lub równe {FORMULA1}',
equal: 'Jest równe {FORMULA1}',
notEqual: 'Jest różne od {FORMULA1}',
notBetween: 'Nie jest między {FORMULA1} a {FORMULA2}',
legal: 'Jest prawidłowym {TYPE}',
},
errorMsg: {
between: 'Wartość musi być między {FORMULA1} a {FORMULA2}',
greaterThan: 'Wartość musi być większa niż {FORMULA1}',
greaterThanOrEqual: 'Wartość musi być większa lub równa {FORMULA1}',
lessThan: 'Wartość musi być mniejsza niż {FORMULA1}',
lessThanOrEqual: 'Wartość musi być mniejsza lub równa {FORMULA1}',
equal: 'Wartość musi być równa {FORMULA1}',
notEqual: 'Wartość musi być różna od {FORMULA1}',
notBetween: 'Wartość nie może być między {FORMULA1} a {FORMULA2}',
legal: 'Wartość musi być prawidłowym {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'entre',
greaterThan: 'maior que',
greaterThanOrEqual: 'maior ou igual',
lessThan: 'menor que',
lessThanOrEqual: 'menor ou igual',
equal: 'igual',
notEqual: 'diferente',
notBetween: 'não está entre',
},
ruleName: {
between: 'Está entre {FORMULA1} e {FORMULA2}',
greaterThan: 'É maior que {FORMULA1}',
greaterThanOrEqual: 'É maior ou igual a {FORMULA1}',
lessThan: 'É menor que {FORMULA1}',
lessThanOrEqual: 'É menor ou igual a {FORMULA1}',
equal: 'É igual a {FORMULA1}',
notEqual: 'É diferente de {FORMULA1}',
notBetween: 'Não está entre {FORMULA1} e {FORMULA2}',
legal: 'É um {TYPE} válido',
},
errorMsg: {
between: 'O valor deve estar entre {FORMULA1} e {FORMULA2}',
greaterThan: 'O valor deve ser maior que {FORMULA1}',
greaterThanOrEqual: 'O valor deve ser maior ou igual a {FORMULA1}',
lessThan: 'O valor deve ser menor que {FORMULA1}',
lessThanOrEqual: 'O valor deve ser menor ou igual a {FORMULA1}',
equal: 'O valor deve ser igual a {FORMULA1}',
notEqual: 'O valor deve ser diferente de {FORMULA1}',
notBetween: 'O valor não deve estar entre {FORMULA1} e {FORMULA2}',
legal: 'O valor deve ser um {TYPE} válido',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'между',
greaterThan: 'больше чем',
greaterThanOrEqual: 'больше или равно',
lessThan: 'меньше чем',
lessThanOrEqual: 'меньше или равно',
equal: 'равно',
notEqual: 'не равно',
notBetween: 'не между',
},
ruleName: {
between: 'между {FORMULA1} и {FORMULA2}',
greaterThan: 'больше чем {FORMULA1}',
greaterThanOrEqual: 'больше или равно {FORMULA1}',
lessThan: 'меньше чем {FORMULA1}',
lessThanOrEqual: 'меньше или равно {FORMULA1}',
equal: 'равно {FORMULA1}',
notEqual: 'не равно {FORMULA1}',
notBetween: 'не между {FORMULA1} и {FORMULA2}',
legal: 'является допустимым {TYPE}',
},
errorMsg: {
between: 'Значение должно быть между {FORMULA1} и {FORMULA2}',
greaterThan: 'Значение должно быть больше {FORMULA1}',
greaterThanOrEqual: 'Значение должно быть больше или равно {FORMULA1}',
lessThan: 'Значение должно быть меньше {FORMULA1}',
lessThanOrEqual: 'Значение должно быть меньше или равно {FORMULA1}',
equal: 'Значение должно быть равно {FORMULA1}',
notEqual: 'Значение должно быть не равно {FORMULA1}',
notBetween: 'Значение должно быть не между {FORMULA1} и {FORMULA2}',
legal: 'Значение должно быть допустимым {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'medzi',
greaterThan: 'väčšie ako',
greaterThanOrEqual: 'väčšie alebo rovné',
lessThan: 'menšie ako',
lessThanOrEqual: 'menšie alebo rovné',
equal: 'rovné',
notEqual: 'nerovné',
notBetween: 'nie medzi',
},
ruleName: {
between: 'Je medzi {FORMULA1} a {FORMULA2}',
greaterThan: 'Je väčšie ako {FORMULA1}',
greaterThanOrEqual: 'Je väčšie alebo rovné {FORMULA1}',
lessThan: 'Je menšie ako {FORMULA1}',
lessThanOrEqual: 'Je menšie alebo rovné {FORMULA1}',
equal: 'Je rovné {FORMULA1}',
notEqual: 'Nie je rovné {FORMULA1}',
notBetween: 'Nie je medzi {FORMULA1} a {FORMULA2}',
legal: 'Je platný {TYPE}',
},
errorMsg: {
between: 'Hodnota musí byť medzi {FORMULA1} a {FORMULA2}',
greaterThan: 'Hodnota musí byť väčšia ako {FORMULA1}',
greaterThanOrEqual: 'Hodnota musí byť väčšia alebo rovná {FORMULA1}',
lessThan: 'Hodnota musí byť menšia ako {FORMULA1}',
lessThanOrEqual: 'Hodnota musí byť menšia alebo rovná {FORMULA1}',
equal: 'Hodnota musí byť rovná {FORMULA1}',
notEqual: 'Hodnota nesmie byť rovná {FORMULA1}',
notBetween: 'Hodnota nesmie byť medzi {FORMULA1} a {FORMULA2}',
legal: 'Hodnota musí byť platný {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,20 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { LocaleLeafKeys } from '@univerjs/core';
import type enUS from './en-US';
export type LocaleKey = LocaleLeafKeys<typeof enUS>;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: 'Giữa',
greaterThan: 'Lớn hơn',
greaterThanOrEqual: 'Lớn hơn hoặc bằng',
lessThan: 'Nhỏ hơn',
lessThanOrEqual: 'Nhỏ hơn hoặc bằng',
equal: 'Bằng',
notEqual: 'Không bằng',
notBetween: 'Không nằm giữa',
},
ruleName: {
between: 'Giữa {FORMULA1} và {FORMULA2}',
greaterThan: 'Lớn hơn {FORMULA1}',
greaterThanOrEqual: 'Lớn hơn hoặc bằng {FORMULA1}',
lessThan: 'Nhỏ hơn {FORMULA1}',
lessThanOrEqual: 'Nhỏ hơn hoặc bằng {FORMULA1}',
equal: 'Bằng {FORMULA1}',
notEqual: 'Không bằng {FORMULA1}',
notBetween: 'Không nằm giữa {FORMULA1} và {FORMULA2}',
legal: 'là một {TYPE} hợp lệ',
},
errorMsg: {
between: 'Giá trị phải nằm giữa {FORMULA1} và {FORMULA2}',
greaterThan: 'Giá trị phải lớn hơn {FORMULA1}',
greaterThanOrEqual: 'Giá trị phải lớn hơn hoặc bằng {FORMULA1}',
lessThan: 'Giá trị phải nhỏ hơn {FORMULA1}',
lessThanOrEqual: 'Giá trị phải nhỏ hơn hoặc bằng {FORMULA1}',
equal: 'Giá trị phải bằng {FORMULA1}',
notEqual: 'Giá trị phải không bằng {FORMULA1}',
notBetween: 'Giá trị phải không nằm giữa {FORMULA1} và {FORMULA2}',
legal: 'Giá trị phải là một {TYPE} hợp lệ',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: '介于',
greaterThan: '大于',
greaterThanOrEqual: '大于或等于',
lessThan: '小于',
lessThanOrEqual: '小于或等于',
equal: '等于',
notEqual: '不等于',
notBetween: '未介于',
},
ruleName: {
between: '介于 {FORMULA1} 和 {FORMULA2} 之间',
greaterThan: '大于 {FORMULA1}',
greaterThanOrEqual: '大于或等于 {FORMULA1}',
lessThan: '小于 {FORMULA1}',
lessThanOrEqual: '小于或等于 {FORMULA1}',
equal: '等于 {FORMULA1}',
notEqual: '不等于 {FORMULA1}',
notBetween: '在 {FORMULA1} 和 {FORMULA2} 范围之外',
legal: '是一个合法的 {TYPE}',
},
errorMsg: {
between: '值必须介于 {FORMULA1} 和 {FORMULA2} 之间',
greaterThan: '值必须大于 {FORMULA1}',
greaterThanOrEqual: '值必须大于或等于 {FORMULA1}',
lessThan: '值必须小于 {FORMULA1}',
lessThanOrEqual: '值必须小于或等于 {FORMULA1}',
equal: '值必须等于 {FORMULA1}',
notEqual: '值必须不等于 {FORMULA1}',
notBetween: '值必须在 {FORMULA1} 和 {FORMULA2} 范围之外',
legal: '值必须是一个合法的 {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: '介於',
greaterThan: '大於',
greaterThanOrEqual: '大於或等於',
lessThan: '小於',
lessThanOrEqual: '小於或等於',
equal: '等於',
notEqual: '不等於',
notBetween: '未介於',
},
ruleName: {
between: '介於 {FORMULA1} 和 {FORMULA2} 之間',
greaterThan: '大於 {FORMULA1}',
greaterThanOrEqual: '大於或等於 {FORMULA1}',
lessThan: '小於 {FORMULA1}',
lessThanOrEqual: '小於或等於 {FORMULA1}',
equal: '等於 {FORMULA1}',
notEqual: '不等於 {FORMULA1}',
notBetween: '在 {FORMULA1} 和 {FORMULA2} 範圍之外',
legal: '是一個合法的 {TYPE}',
},
errorMsg: {
between: '值必須介於 {FORMULA1} 和 {FORMULA2} 之間',
greaterThan: '值必須大於 {FORMULA1}',
greaterThanOrEqual: '值必須大於或等於 {FORMULA1}',
lessThan: '值必須小於 {FORMULA1}',
lessThanOrEqual: '值必須小於或等於 {FORMULA1}',
equal: '值必須等於 {FORMULA1}',
notEqual: '值必須不等於 {FORMULA1}',
notBetween: '值必須在 {FORMULA1} 和 {FORMULA2} 範圍之外',
legal: '值必須是一個合法的 {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,56 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'data-validation': {
operators: {
between: '介於',
greaterThan: '大於',
greaterThanOrEqual: '大於或等於',
lessThan: '小於',
lessThanOrEqual: '小於或等於',
equal: '等於',
notEqual: '不等於',
notBetween: '未介於',
},
ruleName: {
between: '介於 {FORMULA1} 和 {FORMULA2} 之間',
greaterThan: '大於 {FORMULA1}',
greaterThanOrEqual: '大於或等於 {FORMULA1}',
lessThan: '小於 {FORMULA1}',
lessThanOrEqual: '小於或等於 {FORMULA1}',
equal: '等於 {FORMULA1}',
notEqual: '不等於 {FORMULA1}',
notBetween: '在 {FORMULA1} 和 {FORMULA2} 範圍之外',
legal: '是一個合法的 {TYPE}',
},
errorMsg: {
between: '值必須介於 {FORMULA1} 和 {FORMULA2} 之間',
greaterThan: '值必須大於 {FORMULA1}',
greaterThanOrEqual: '值必須大於或等於 {FORMULA1}',
lessThan: '值必須小於 {FORMULA1}',
lessThanOrEqual: '值必須小於或等於 {FORMULA1}',
equal: '值必須等於 {FORMULA1}',
notEqual: '值必須不等於 {FORMULA1}',
notBetween: '值必須在 {FORMULA1} 和 {FORMULA2} 範圍之外',
legal: '值必須是一個合法的 {TYPE}',
},
},
};
export default locale;

View File

@@ -0,0 +1,255 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IDataValidationRule } from '@univerjs/core';
import type { IUpdateRulePayload } from '../types/interfaces/i-update-rule-payload';
import { Disposable, ILogService, Tools } from '@univerjs/core';
import { debounceTime, Subject } from 'rxjs';
import { getRuleOptions, getRuleSetting } from '../common/util';
import { UpdateRuleType } from '../types/enum/update-rule-type';
export type DataValidationChangeType = 'update' | 'add' | 'remove';
export type DataValidationChangeSource = 'command' | 'patched';
export interface IRuleChange {
rule: IDataValidationRule;
type: DataValidationChangeType;
unitId: string;
subUnitId: string;
source: DataValidationChangeSource;
updatePayload?: IUpdateRulePayload;
oldRule?: IDataValidationRule;
}
interface ISubUnitDataValidation {
map: Map<string, IDataValidationRule>;
list: IDataValidationRule[];
}
export class DataValidationModel extends Disposable {
private readonly _model = new Map<string, Map<string, ISubUnitDataValidation>>();
private readonly _ruleChange$ = new Subject<IRuleChange>();
ruleChange$ = this._ruleChange$.asObservable();
ruleChangeDebounce$ = this.ruleChange$.pipe(debounceTime(20));
constructor(
@ILogService private readonly _logService: ILogService
) {
super();
this.disposeWithMe({
dispose: () => {
this._ruleChange$.complete();
},
});
}
private _ensureMap(unitId: string, subUnitId: string) {
if (!this._model.has(unitId)) {
this._model.set(unitId, new Map());
}
const unitMap = this._model.get(unitId)!;
if (unitMap.has(subUnitId)) {
return unitMap.get(subUnitId)!;
}
const map = { map: new Map<string, IDataValidationRule>(), list: [] as IDataValidationRule[] };
unitMap.set(subUnitId, map);
return map;
}
private _addSubUnitRule(subUnit: ISubUnitDataValidation, rule: IDataValidationRule | IDataValidationRule[], index?: number) {
const { map: dataValidationMap, list: dataValidations } = subUnit;
const _rules = Array.isArray(rule) ? rule : [rule];
const rules = _rules.filter((item) => !dataValidationMap.has(item.uid));
if (typeof index === 'number' && index < dataValidations.length) {
dataValidations.splice(index, 0, ...rules);
} else {
dataValidations.push(...rules);
}
rules.forEach((item) => {
dataValidationMap.set(item.uid, item);
});
}
private _removeSubUnitRule(subUnit: ISubUnitDataValidation, ruleId: string) {
const { map: dataValidationMap, list: dataValidations } = subUnit;
const index = dataValidations.findIndex((item) => item.uid === ruleId);
if (index > -1) {
dataValidations.splice(index, 1);
dataValidationMap.delete(ruleId);
}
}
private _updateSubUnitRule(subUnit: ISubUnitDataValidation, ruleId: string, payload: IUpdateRulePayload): IDataValidationRule {
const { map: dataValidationMap, list: dataValidations } = subUnit;
const oldRule = dataValidationMap.get(ruleId);
const index = dataValidations.findIndex((rule) => ruleId === rule.uid);
if (!oldRule) {
throw new Error(`Data validation rule is not found, ruleId: ${ruleId}.`);
}
const rule = { ...oldRule };
switch (payload.type) {
case UpdateRuleType.RANGE: {
rule.ranges = payload.payload;
break;
}
case UpdateRuleType.SETTING: {
Object.assign(rule, getRuleSetting(payload.payload));
break;
}
case UpdateRuleType.OPTIONS: {
Object.assign(rule, getRuleOptions(payload.payload));
break;
}
case UpdateRuleType.ALL: {
Object.assign(rule, payload.payload);
break;
}
default:
break;
}
dataValidations[index] = rule;
dataValidationMap.set(ruleId, rule);
return rule;
}
private _addRuleSideEffect(unitId: string, subUnitId: string, rule: IDataValidationRule, source: DataValidationChangeSource) {
const subUnitMap = this._ensureMap(unitId, subUnitId);
const oldRule = subUnitMap.map.get(rule.uid);
if (oldRule) {
return;
}
return {
rule,
type: 'add',
unitId,
subUnitId,
source,
} as const;
}
addRule(unitId: string, subUnitId: string, rule: IDataValidationRule | IDataValidationRule[], source: DataValidationChangeSource, index?: number) {
try {
const subUnitMap = this._ensureMap(unitId, subUnitId);
const rules = Array.isArray(rule) ? rule : [rule];
const effects = rules.map((item) => this._addRuleSideEffect(unitId, subUnitId, item, source));
this._addSubUnitRule(subUnitMap, rule, index);
effects.forEach((effect) => {
if (effect) {
this._ruleChange$.next(effect);
}
});
} catch (error) {
this._logService.error(error);
}
}
updateRule(unitId: string, subUnitId: string, ruleId: string, payload: IUpdateRulePayload, source: DataValidationChangeSource) {
try {
const subUnitMap = this._ensureMap(unitId, subUnitId);
const oldRule = Tools.deepClone(subUnitMap.map.get(ruleId));
if (!oldRule) {
throw new Error(`Data validation rule is not found, ruleId: ${ruleId}.`);
}
const rule = this._updateSubUnitRule(subUnitMap, ruleId, payload);
this._ruleChange$.next({
rule,
type: 'update',
unitId,
subUnitId,
source,
updatePayload: payload,
oldRule,
});
} catch (error) {
this._logService.error(error);
}
}
removeRule(unitId: string, subUnitId: string, ruleId: string, source: DataValidationChangeSource) {
try {
const map = this._ensureMap(unitId, subUnitId);
const oldRule = map.map.get(ruleId);
if (oldRule) {
this._removeSubUnitRule(map, ruleId);
this._ruleChange$.next({
rule: oldRule,
type: 'remove',
unitId,
subUnitId,
source,
});
}
} catch (error) {
this._logService.error(error);
}
}
getRuleById(unitId: string, subUnitId: string, ruleId: string) {
const map = this._ensureMap(unitId, subUnitId);
return map.map.get(ruleId);
}
getRuleIndex(unitId: string, subUnitId: string, ruleId: string) {
const map = this._ensureMap(unitId, subUnitId);
return map.list.findIndex((rule) => rule.uid === ruleId);
}
getRules(unitId: string, subUnitId: string) {
const manager = this._ensureMap(unitId, subUnitId);
return [...manager.list];
}
getUnitRules(unitId: string) {
const unitMap = this._model.get(unitId);
if (!unitMap) {
return [];
}
const res = [] as [string, IDataValidationRule[]][];
unitMap.forEach((manager, subUnitId) => {
res.push([subUnitId, manager.list]);
});
return res;
}
deleteUnitRules(unitId: string) {
this._model.delete(unitId);
}
getSubUnitIds(unitId: string) {
return Array.from(this._model.get(unitId)?.keys() ?? []);
}
getAll() {
return Array.from(this._model.keys()).map((unitId) => [unitId, this.getUnitRules(unitId)] as const);
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { Dependency } from '@univerjs/core';
import type { IUniverDataValidationConfig } from './config/config';
import { ICommandService, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core';
import pkg from '../package.json';
import {
AddDataValidationMutation,
RemoveDataValidationMutation,
UpdateDataValidationMutation,
} from './commands/mutations/data-validation.mutation';
import { DATA_VALIDATION_PLUGIN_CONFIG_KEY, defaultPluginConfig } from './config/config';
import { DataValidationResourceController } from './controllers/dv-resource.controller';
import { DataValidationModel } from './models/data-validation-model';
import { DataValidatorRegistryService } from './services/data-validator-registry.service';
export class UniverDataValidationPlugin extends Plugin {
static override pluginName = 'UNIVER_DATA_VALIDATION_PLUGIN';
static override packageName = pkg.name;
static override version = pkg.version;
static override type = UniverInstanceType.UNIVER_SHEET;
constructor(
private readonly _config: Partial<IUniverDataValidationConfig> = defaultPluginConfig,
@Inject(Injector) protected _injector: Injector,
@ICommandService private _commandService: ICommandService,
@IConfigService private readonly _configService: IConfigService
) {
super();
// Manage the plugin configuration.
const { ...rest } = merge(
{},
defaultPluginConfig,
this._config
);
this._configService.setConfig(DATA_VALIDATION_PLUGIN_CONFIG_KEY, rest);
}
override onStarting(): void {
([
[DataValidationModel],
[DataValidatorRegistryService],
[DataValidationResourceController],
] as Dependency[]).forEach((d) => this._injector.add(d));
[
// mutation
AddDataValidationMutation,
UpdateDataValidationMutation,
RemoveDataValidationMutation,
].forEach((command) => {
this._commandService.registerCommand(command);
});
}
override onReady(): void {
this._injector.get(DataValidationResourceController);
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injector } from '@univerjs/core';
import { beforeEach, describe, expect, it } from 'vitest';
import { DataValidatorRegistryScope, DataValidatorRegistryService } from '../data-validator-registry.service';
describe('DataValidatorRegistryService', () => {
let service: DataValidatorRegistryService;
beforeEach(() => {
const injector = new Injector();
injector.add([DataValidatorRegistryService]);
service = injector.get(DataValidatorRegistryService);
});
it('registers validators by business scope and unregisters them with the disposable', () => {
const changes: number[] = [];
service.validatorsChange$.subscribe(() => changes.push(1));
const validator = { id: 'decimal', scopes: DataValidatorRegistryScope.SHEET };
const disposable = service.register(validator as never);
expect(service.getValidatorItem('decimal')).toBe(validator);
expect(service.getValidatorsByScope(DataValidatorRegistryScope.SHEET)).toEqual([validator]);
disposable.dispose();
expect(service.getValidatorItem('decimal')).toBeUndefined();
expect(service.getValidatorsByScope(DataValidatorRegistryScope.SHEET)).toEqual([]);
expect(changes.length).toBe(3);
});
it('rejects duplicate validators in the same scope', () => {
service.register({ id: 'decimal', scopes: DataValidatorRegistryScope.SHEET } as never);
expect(() => service.register({ id: 'decimal', scopes: DataValidatorRegistryScope.SHEET } as never)).toThrow(
'Validator item with the same id decimal has already been added!'
);
});
});

View File

@@ -0,0 +1,95 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { BaseDataValidator } from '../validators/base-data-validator';
import { toDisposable } from '@univerjs/core';
import { BehaviorSubject } from 'rxjs';
export enum DataValidatorRegistryScope {
SHEET = 'sheet',
}
/**
* Register data validator
*/
export class DataValidatorRegistryService {
private _validatorByScopes = new Map<string, Array<BaseDataValidator>>();
private _validatorMap = new Map<string, BaseDataValidator>();
private _validatorsChange$ = new BehaviorSubject<void>(undefined);
validatorsChange$ = this._validatorsChange$.asObservable();
private _addValidatorToScope(validator: BaseDataValidator, scope: string) {
if (!this._validatorByScopes.has(scope)) {
this._validatorByScopes.set(scope, []);
}
const validators = this._validatorByScopes.get(scope)!;
if (validators.findIndex((m) => m.id === validator.id) > -1) {
throw new Error(`Validator item with the same id ${validator.id} has already been added!`);
}
validators.push(validator);
}
private _removeValidatorFromScope(validator: BaseDataValidator, scope: string) {
const validators = this._validatorByScopes.get(scope);
if (!validators) {
return;
}
const index = validators.findIndex((v) => v.id === validator.id);
if (index > -1) {
validators.splice(index, 1);
}
}
register(validator: BaseDataValidator) {
this._validatorMap.set(validator.id, validator);
if (Array.isArray(validator.scopes)) {
validator.scopes.forEach((scope) => {
this._addValidatorToScope(validator, scope);
});
} else {
this._addValidatorToScope(validator, validator.scopes);
}
this._validatorsChange$.next();
return toDisposable(() => {
this._validatorMap.delete(validator.id);
if (Array.isArray(validator.scopes)) {
validator.scopes.forEach((scope) => {
this._removeValidatorFromScope(validator, scope);
});
} else {
this._removeValidatorFromScope(validator, validator.scopes);
}
this._validatorsChange$.next();
});
}
getValidatorItem(id: string) {
return this._validatorMap.get(id);
}
getValidatorsByScope(scope: string) {
return this._validatorByScopes.get(scope);
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DataValidationOperator } from '@univerjs/core';
export const OperatorTextMap: Record<DataValidationOperator, string> = {
[DataValidationOperator.BETWEEN]: 'data-validation.operators.between',
[DataValidationOperator.EQUAL]: 'data-validation.operators.equal',
[DataValidationOperator.GREATER_THAN]: 'data-validation.operators.greaterThan',
[DataValidationOperator.GREATER_THAN_OR_EQUAL]: 'data-validation.operators.greaterThanOrEqual',
[DataValidationOperator.LESS_THAN]: 'data-validation.operators.lessThan',
[DataValidationOperator.LESS_THAN_OR_EQUAL]: 'data-validation.operators.lessThanOrEqual',
[DataValidationOperator.NOT_BETWEEN]: 'data-validation.operators.notBetween',
[DataValidationOperator.NOT_EQUAL]: 'data-validation.operators.notEqual',
};
export const OperatorTitleMap: Record<DataValidationOperator | 'NONE', string> = {
[DataValidationOperator.BETWEEN]: 'data-validation.ruleName.between',
[DataValidationOperator.EQUAL]: 'data-validation.ruleName.equal',
[DataValidationOperator.GREATER_THAN]: 'data-validation.ruleName.greaterThan',
[DataValidationOperator.GREATER_THAN_OR_EQUAL]: 'data-validation.ruleName.greaterThanOrEqual',
[DataValidationOperator.LESS_THAN]: 'data-validation.ruleName.lessThan',
[DataValidationOperator.LESS_THAN_OR_EQUAL]: 'data-validation.ruleName.lessThanOrEqual',
[DataValidationOperator.NOT_BETWEEN]: 'data-validation.ruleName.notBetween',
[DataValidationOperator.NOT_EQUAL]: 'data-validation.ruleName.notEqual',
NONE: 'data-validation.ruleName.legal',
};
export const OperatorErrorTitleMap: Record<DataValidationOperator | 'NONE', string> = {
[DataValidationOperator.BETWEEN]: 'data-validation.errorMsg.between',
[DataValidationOperator.EQUAL]: 'data-validation.errorMsg.equal',
[DataValidationOperator.GREATER_THAN]: 'data-validation.errorMsg.greaterThan',
[DataValidationOperator.GREATER_THAN_OR_EQUAL]: 'data-validation.errorMsg.greaterThanOrEqual',
[DataValidationOperator.LESS_THAN]: 'data-validation.errorMsg.lessThan',
[DataValidationOperator.LESS_THAN_OR_EQUAL]: 'data-validation.errorMsg.lessThanOrEqual',
[DataValidationOperator.NOT_BETWEEN]: 'data-validation.errorMsg.notBetween',
[DataValidationOperator.NOT_EQUAL]: 'data-validation.errorMsg.notEqual',
NONE: 'data-validation.errorMsg.legal',
};

View File

@@ -0,0 +1,22 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DataValidationOperator } from '@univerjs/core';
export const TWO_FORMULA_OPERATOR_COUNT: DataValidationOperator[] = [
DataValidationOperator.BETWEEN,
DataValidationOperator.NOT_BETWEEN,
];

View File

@@ -0,0 +1,22 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 enum UpdateRuleType {
SETTING,
RANGE,
OPTIONS,
ALL,
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 interface IDataValidationDropdownProps {
top: number;
left: number;
width: number;
height: number;
unitId: string;
subUnitId: string;
row: number;
col: number;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DataValidationOperator } from '@univerjs/core';
export interface IDataValidatorOperatorConfig {
operator: DataValidationOperator;
text: string;
placeholder1?: string;
placeholder2?: string;
formulaCount: 1 | 2;
}

View File

@@ -0,0 +1,20 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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 interface IFormulaValue {
formula1?: string;
formula2?: string;
}

View File

@@ -0,0 +1,44 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { IDataValidationRule, IDataValidationRuleBase, IDataValidationRuleOptions } from '@univerjs/core';
import type { UpdateRuleType } from '../enum/update-rule-type';
export interface IUpdateRuleRangePayload {
type: UpdateRuleType.RANGE;
payload: any[];
}
export interface IUpdateRuleSettingPayload {
type: UpdateRuleType.SETTING;
payload: IDataValidationRuleBase;
}
export interface IUpdateRuleOptionsPayload {
type: UpdateRuleType.OPTIONS;
payload: Partial<IDataValidationRuleOptions>;
}
export interface IUpdateRuleAllPayload {
type: UpdateRuleType.ALL;
payload: Omit<IDataValidationRule, 'uid'>;
}
export type IUpdateRulePayload =
IUpdateRuleRangePayload
| IUpdateRuleSettingPayload
| IUpdateRuleOptionsPayload
| IUpdateRuleAllPayload;

View File

@@ -0,0 +1,281 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { CellValue, IDataValidationRule, IDataValidationRuleBase, IStyleData, Nullable, Workbook, Worksheet } from '@univerjs/core';
import type { CellValueType } from '@univerjs/protocol';
import type { ISheetLocationBase } from '@univerjs/sheets';
import type { IBaseDataValidationWidget } from './base-widget';
import { DataValidationOperator, Inject, Injector, LocaleService } from '@univerjs/core';
import { OperatorErrorTitleMap, OperatorTitleMap } from '../types/const/operator-text-map';
export const FORMULA1 = '{FORMULA1}';
export const FORMULA2 = '{FORMULA2}';
export const TYPE = '{TYPE}';
const operatorNameMap: Record<DataValidationOperator, string> = {
[DataValidationOperator.BETWEEN]: 'data-validation.operators.between',
[DataValidationOperator.EQUAL]: 'data-validation.operators.equal',
[DataValidationOperator.GREATER_THAN]: 'data-validation.operators.greaterThan',
[DataValidationOperator.GREATER_THAN_OR_EQUAL]: 'data-validation.operators.greaterThanOrEqual',
[DataValidationOperator.LESS_THAN]: 'data-validation.operators.lessThan',
[DataValidationOperator.LESS_THAN_OR_EQUAL]: 'data-validation.operators.lessThanOrEqual',
[DataValidationOperator.NOT_BETWEEN]: 'data-validation.operators.notBetween',
[DataValidationOperator.NOT_EQUAL]: 'data-validation.operators.notEqual',
};
export interface IValidatorCellInfo<DataType = Nullable<CellValue>> {
value: DataType;
interceptValue: Nullable<CellValue>;
row: number;
column: number;
unitId: string;
subUnitId: string;
worksheet: Worksheet;
workbook: Workbook;
t: Nullable<CellValueType>;
}
export interface IFormulaResult<T = any> {
formula1: T;
formula2: T;
isFormulaValid: boolean;
}
export interface IFormulaValidResult {
success: boolean;
formula1?: string;
formula2?: string;
}
export enum DataValidatorDropdownType {
DATE = 'date',
TIME = 'time',
DATETIME = 'datetime',
LIST = 'list',
MULTIPLE_LIST = 'multipleList',
COLOR = 'color',
CASCADE = 'cascade',
}
export abstract class BaseDataValidator {
abstract id: string;
abstract title: string;
abstract operators: DataValidationOperator[];
abstract scopes: string[] | string;
abstract order: number;
offsetFormulaByRange = true;
// #region UI related
formulaInput: string | undefined = undefined;
canvasRender: Nullable<IBaseDataValidationWidget> = null;
dropdownType: DataValidatorDropdownType | undefined = undefined;
optionsInput: string | undefined = undefined;
// #endregion
constructor(
@Inject(LocaleService) readonly localeService: LocaleService,
@Inject(Injector) readonly injector: Injector
) {
// empty
}
get operatorNames() {
return this.operators.map((operator) => this.localeService.t(operatorNameMap[operator]));
}
get titleStr() {
return this.localeService.t(this.title);
}
skipDefaultFontRender: ((rule: IDataValidationRule, cellValue: Nullable<CellValue>, pos: any) => boolean) | undefined;
generateRuleName(rule: IDataValidationRuleBase): string {
if (!rule.operator) {
return this.localeService.t(OperatorTitleMap.NONE).replace(TYPE, this.titleStr);
}
const ruleName = this.localeService.t(OperatorTitleMap[rule.operator]).replace(FORMULA1, rule.formula1 ?? '').replace(FORMULA2, rule.formula2 ?? '');
return `${this.titleStr} ${ruleName}`;
}
generateRuleErrorMessage(rule: IDataValidationRuleBase, position: ISheetLocationBase) {
if (!rule.operator) {
return this.localeService.t(OperatorErrorTitleMap.NONE).replace(TYPE, this.titleStr); ;
}
const errorMsg = this.localeService.t(OperatorErrorTitleMap[rule.operator]).replace(FORMULA1, rule.formula1 ?? '').replace(FORMULA2, rule.formula2 ?? '');
return `${errorMsg}`;
}
getExtraStyle(rule: IDataValidationRuleBase, value: Nullable<CellValue>, ctx: { style: IStyleData }, row: number, column: number): Nullable<IStyleData> {}
getRuleFinalError(rule: IDataValidationRule, position: ISheetLocationBase) {
if (rule.showErrorMessage && rule.error) {
return rule.error;
}
return this.generateRuleErrorMessage(rule, position);
}
isEmptyCellValue(cellValue: Nullable<CellValue>): cellValue is null | undefined | void {
if (cellValue === '' || cellValue === undefined || cellValue === null) {
return true;
}
return false;
}
abstract parseFormula(rule: IDataValidationRule, unitId: string, subUnitId: string, row: number, column: number): Promise<IFormulaResult<number | undefined>>;
abstract validatorFormula(rule: IDataValidationRule, unitId: string, subUnitId: string): IFormulaValidResult;
normalizeFormula(rule: IDataValidationRule, unitId: string, subUnitId: string) {
return {
formula1: rule.formula1,
formula2: rule.formula2,
};
}
async isValidType(cellInfo: IValidatorCellInfo, formula: IFormulaResult, rule: IDataValidationRule): Promise<boolean> {
return true;
};
transform(cellInfo: IValidatorCellInfo, formula: IFormulaResult, rule: IDataValidationRule): IValidatorCellInfo<number> {
return cellInfo as IValidatorCellInfo<number>;
};
async validatorIsEqual(cellInfo: IValidatorCellInfo<CellValue>, formula: IFormulaResult, rule: IDataValidationRule) {
const { formula1 } = formula;
const { value: cellValue } = cellInfo;
if (Number.isNaN(formula1)) {
return true;
}
return cellValue === formula1;
}
async validatorIsNotEqual(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1 } = formula;
if (Number.isNaN(formula1)) {
return true;
}
return cellInfo.value !== formula1;
}
async validatorIsBetween(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1, formula2 } = formula;
if (Number.isNaN(formula1) || Number.isNaN(formula2)) {
return true;
}
const start = Math.min(formula1, formula2);
const end = Math.max(formula1, formula2);
return cellInfo.value >= start && cellInfo.value <= end;
}
async validatorIsNotBetween(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1, formula2 } = formula;
if (Number.isNaN(formula1) || Number.isNaN(formula2)) {
return true;
}
const start = Math.min(formula1, formula2);
const end = Math.max(formula1, formula2);
return cellInfo.value < start || cellInfo.value > end;
}
async validatorIsGreaterThan(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1 } = formula;
if (Number.isNaN(formula1)) {
return true;
}
return cellInfo.value > formula1;
}
async validatorIsGreaterThanOrEqual(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1 } = formula;
if (Number.isNaN(formula1)) {
return true;
}
return cellInfo.value >= formula1;
}
async validatorIsLessThan(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1 } = formula;
if (Number.isNaN(formula1)) {
return true;
}
return cellInfo.value < formula1;
}
async validatorIsLessThanOrEqual(cellInfo: IValidatorCellInfo<number>, formula: IFormulaResult, _rule: IDataValidationRule) {
const { formula1 } = formula;
if (Number.isNaN(formula1)) {
return true;
}
return cellInfo.value <= formula1;
}
async validator(cellInfo: IValidatorCellInfo, rule: IDataValidationRule): Promise<boolean> {
const { value: cellValue, unitId, subUnitId } = cellInfo;
const isEmpty = this.isEmptyCellValue(cellValue);
const { allowBlank = true, operator } = rule;
if (isEmpty) {
return allowBlank;
}
const formulaInfo = await this.parseFormula(rule, unitId, subUnitId, cellInfo.row, cellInfo.column);
if (!formulaInfo.isFormulaValid) {
return false;
}
if (!(await this.isValidType(cellInfo, formulaInfo, rule))) {
return false;
}
if (!operator) {
return true;
}
const transformedCell = this.transform(cellInfo, formulaInfo, rule);
switch (operator) {
case DataValidationOperator.BETWEEN:
return this.validatorIsBetween(transformedCell, formulaInfo, rule);
case DataValidationOperator.EQUAL:
return this.validatorIsEqual(transformedCell, formulaInfo, rule);
case DataValidationOperator.GREATER_THAN:
return this.validatorIsGreaterThan(transformedCell, formulaInfo, rule);
case DataValidationOperator.GREATER_THAN_OR_EQUAL:
return this.validatorIsGreaterThanOrEqual(transformedCell, formulaInfo, rule);
case DataValidationOperator.LESS_THAN:
return this.validatorIsLessThan(transformedCell, formulaInfo, rule);
case DataValidationOperator.LESS_THAN_OR_EQUAL:
return this.validatorIsLessThanOrEqual(transformedCell, formulaInfo, rule);
case DataValidationOperator.NOT_BETWEEN:
return this.validatorIsNotBetween(transformedCell, formulaInfo, rule);
case DataValidationOperator.NOT_EQUAL:
return this.validatorIsNotEqual(transformedCell, formulaInfo, rule);
default:
throw new Error('Unknown operator.');
}
}
}

View File

@@ -0,0 +1,22 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ICellCustomRender, ICellRenderContext } from '@univerjs/core';
export interface IBaseDataValidationWidget extends ICellCustomRender {
calcCellAutoHeight(info: ICellRenderContext): number | undefined;
calcCellAutoWidth(info: ICellRenderContext): number | undefined;
}