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
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:
41
packages/sheets-numfmt-ui/README.md
Normal file
41
packages/sheets-numfmt-ui/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# @univerjs/sheets-numfmt-ui
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-numfmt-ui)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-numfmt-ui)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-numfmt-ui)
|
||||
|
||||
`@univerjs/sheets-numfmt-ui` adds number-format UI for Univer Sheets, including menus, editors, previews, and locale-aware formatting controls.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-numfmt-ui` | `UniverSheetsNumfmtUi` | Yes | Yes | No |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-numfmt-ui
|
||||
# or
|
||||
npm install @univerjs/sheets-numfmt-ui
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import '@univerjs/sheets-numfmt-ui/lib/index.css';
|
||||
import EnUS from '@univerjs/sheets-numfmt-ui/locale/en-US';
|
||||
import { UniverSheetsNumfmtUIPlugin } from '@univerjs/sheets-numfmt-ui';
|
||||
|
||||
univer.registerPlugin(UniverSheetsNumfmtUIPlugin);
|
||||
|
||||
// Merge EnUS into your Univer locale map when this package contributes UI text.
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-numfmt-ui)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
96
packages/sheets-numfmt-ui/package.json
Normal file
96
packages/sheets-numfmt-ui/package.json
Normal file
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-numfmt-ui",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Number format menus, editors, and previews for Univer Sheets.",
|
||||
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/univer"
|
||||
},
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/dream-num/univer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"univer",
|
||||
"sheets",
|
||||
"number-format",
|
||||
"formatting",
|
||||
"ui"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./locale/*": "./src/locale/*.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./locale/*": {
|
||||
"import": "./lib/es/locale/*.js",
|
||||
"require": "./lib/cjs/locale/*.js",
|
||||
"types": "./lib/types/locale/*.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/design": "workspace:*",
|
||||
"@univerjs/engine-formula": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/icons": "1.38.0",
|
||||
"@univerjs/sheets": "workspace:*",
|
||||
"@univerjs/sheets-numfmt": "workspace:*",
|
||||
"@univerjs/sheets-ui": "workspace:*",
|
||||
"@univerjs/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"postcss": "^8.5.28",
|
||||
"react": "18.3.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"tailwindcss": "3.4.18",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
1
packages/sheets-numfmt-ui/postcss.config.mjs
Normal file
1
packages/sheets-numfmt-ui/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@univerjs-infra/shared/postcss';
|
||||
@@ -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 { IAccessor, ICommand } from '@univerjs/core';
|
||||
import type { ISetNumfmtCommandParams } from '@univerjs/sheets-numfmt';
|
||||
import { CommandType, ICommandService, Range } from '@univerjs/core';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { getPatternType, SetNumfmtCommand } from '@univerjs/sheets-numfmt';
|
||||
|
||||
export interface ISetMobileNumfmtCommandParams {
|
||||
value?: string | null;
|
||||
}
|
||||
|
||||
export const SetMobileNumfmtCommand: ICommand<ISetMobileNumfmtCommandParams> = {
|
||||
id: 'sheet.command.mobile.numfmt.set',
|
||||
type: CommandType.COMMAND,
|
||||
handler: async (accessor: IAccessor, params) => {
|
||||
const selections = accessor.get(SheetsSelectionsService).getCurrentSelections();
|
||||
if (!selections?.length) return false;
|
||||
|
||||
const pattern = params?.value || undefined;
|
||||
const type = pattern ? getPatternType(pattern) : undefined;
|
||||
const values: ISetNumfmtCommandParams['values'] = [];
|
||||
selections.forEach((selection) => {
|
||||
Range.foreach(selection.range, (row, col) => values.push({ row, col, pattern, type }));
|
||||
});
|
||||
|
||||
return accessor.get(ICommandService).executeCommand(SetNumfmtCommand.id, { values });
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
/**
|
||||
* 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 { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import { ICommandService, LocaleService } from '@univerjs/core';
|
||||
import { IRenderManagerService, RenderManagerService } from '@univerjs/engine-render';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsNumfmtCellContentController } from '@univerjs/sheets-numfmt';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { ComponentManager, DesktopSidebarService, ISidebarService } from '@univerjs/ui';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createTestBed } from '../../../controllers/__tests__/test.util';
|
||||
import { SheetNumfmtUIController } from '../../../controllers/ui.controller';
|
||||
import { CloseNumfmtPanelOperator } from '../close.numfmt.panel.operation';
|
||||
import { OpenNumfmtPanelOperator } from '../open.numfmt.panel.operation';
|
||||
|
||||
function createSelection(row: number, column: number): ISelectionWithStyle {
|
||||
return {
|
||||
range: { startRow: row, endRow: row, startColumn: column, endColumn: column },
|
||||
primary: {
|
||||
startRow: row,
|
||||
endRow: row,
|
||||
startColumn: column,
|
||||
endColumn: column,
|
||||
actualRow: row,
|
||||
actualColumn: column,
|
||||
isMerged: false,
|
||||
isMergedMainCell: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
describe('number format panel operation', () => {
|
||||
let commandService: ICommandService;
|
||||
let selectionsService: SheetsSelectionsService;
|
||||
let sidebarService: ISidebarService;
|
||||
let unitId: string;
|
||||
let subUnitId: string;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createTestBed(undefined, [
|
||||
[SheetsSelectionsService],
|
||||
[IRenderManagerService, { useClass: RenderManagerService }],
|
||||
[SheetSkeletonManagerService],
|
||||
[ComponentManager],
|
||||
[ISidebarService, { useClass: DesktopSidebarService }],
|
||||
[SheetsNumfmtCellContentController],
|
||||
[SheetNumfmtUIController],
|
||||
]);
|
||||
|
||||
unitId = testBed.unitId;
|
||||
subUnitId = testBed.subUnitId;
|
||||
commandService = testBed.get(ICommandService);
|
||||
selectionsService = testBed.get(SheetsSelectionsService);
|
||||
sidebarService = testBed.get(ISidebarService);
|
||||
testBed.get(LocaleService).load({});
|
||||
testBed.get(SheetNumfmtUIController);
|
||||
});
|
||||
|
||||
it('opens the number format panel with the active cell preview value', async () => {
|
||||
selectionsService.setSelections(unitId, subUnitId, [createSelection(0, 1)]);
|
||||
|
||||
await commandService.executeCommand(OpenNumfmtPanelOperator.id);
|
||||
|
||||
expect(sidebarService.visible).toBe(true);
|
||||
expect(sidebarService.options.header?.title).toBe('sheets-numfmt-ui.title');
|
||||
expect(sidebarService.options.children).toMatchObject({
|
||||
label: 'SHEET_NUMFMT_PANEL',
|
||||
value: {
|
||||
defaultPattern: '',
|
||||
defaultValue: 1,
|
||||
row: 0,
|
||||
col: 1,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the sidebar closed when there is no active selection', async () => {
|
||||
await commandService.executeCommand(OpenNumfmtPanelOperator.id);
|
||||
|
||||
expect(sidebarService.visible).toBe(false);
|
||||
});
|
||||
|
||||
it('publishes a close notification after the number format panel is dismissed', async () => {
|
||||
const closedPanelEvents: string[] = [];
|
||||
commandService.onCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id === CloseNumfmtPanelOperator.id) {
|
||||
closedPanelEvents.push(commandInfo.id);
|
||||
}
|
||||
});
|
||||
selectionsService.setSelections(unitId, subUnitId, [createSelection(0, 1)]);
|
||||
await commandService.executeCommand(OpenNumfmtPanelOperator.id);
|
||||
|
||||
await commandService.executeCommand(CloseNumfmtPanelOperator.id);
|
||||
|
||||
expect(closedPanelEvents).toEqual([CloseNumfmtPanelOperator.id]);
|
||||
});
|
||||
});
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
import type { ICommand } from '@univerjs/core';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
|
||||
export const CloseNumfmtPanelOperator: ICommand = {
|
||||
id: 'sheet.operation.close.numfmt.panel',
|
||||
type: CommandType.OPERATION,
|
||||
handler: () =>
|
||||
// do nothing, just notify panel is closed
|
||||
true,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 { IAccessor, ICommand } from '@univerjs/core';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { SheetNumfmtUIController } from '../../controllers/ui.controller';
|
||||
|
||||
export const OpenNumfmtPanelOperator: ICommand = {
|
||||
id: 'sheet.operation.open.numfmt.panel',
|
||||
type: CommandType.OPERATION,
|
||||
handler: (accessor: IAccessor) => {
|
||||
const numfmtController = accessor.get(SheetNumfmtUIController);
|
||||
numfmtController.openPanel();
|
||||
return true;
|
||||
},
|
||||
};
|
||||
27
packages/sheets-numfmt-ui/src/config/config.ts
Normal file
27
packages/sheets-numfmt-ui/src/config/config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 { MenuConfig } from '@univerjs/ui';
|
||||
|
||||
export const SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY = 'sheets-numfmt-ui.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsNumfmtUIConfig {
|
||||
menu?: MenuConfig;
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsNumfmtUIConfig = {};
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* 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 { Workbook } from '@univerjs/core';
|
||||
import type { ISetNumfmtMutationParams } from '@univerjs/sheets';
|
||||
import { ICommandService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { SetNumfmtMutation } from '@univerjs/sheets';
|
||||
import { SheetsNumfmtCellContentController } from '@univerjs/sheets-numfmt/controllers/numfmt-cell-content.controller.js';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createTestBed } from './test.util';
|
||||
|
||||
describe('test cell-content', () => {
|
||||
let unitId: string = '';
|
||||
let subUnitId: string = '';
|
||||
let univerInstanceService: IUniverInstanceService;
|
||||
let testBed: any;
|
||||
let commandService: ICommandService;
|
||||
beforeEach(() => {
|
||||
testBed = createTestBed(undefined, [[SheetsNumfmtCellContentController]]);
|
||||
unitId = testBed.unitId;
|
||||
subUnitId = testBed.subUnitId;
|
||||
commandService = testBed.get(ICommandService);
|
||||
univerInstanceService = testBed.get(IUniverInstanceService);
|
||||
});
|
||||
|
||||
it('Add a new data format to test whether the rendering layer is working correctly', () => {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: '$#,##0;(#,##0)',
|
||||
},
|
||||
},
|
||||
};
|
||||
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
const worksheet = workbook.getActiveSheet()!;
|
||||
testBed.get(SheetsNumfmtCellContentController);
|
||||
const value = worksheet.getCell(0, 0);
|
||||
expect(value).toEqual({ v: 0, t: 2 });
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const startTimeWithNumfmt = performance.now();
|
||||
const value1 = worksheet.getCell(0, 0);
|
||||
const timeWithNumfmt = performance.now() - startTimeWithNumfmt;
|
||||
expect(value1!.v).toEqual('$0');
|
||||
expect(value1!.t).toEqual(2);
|
||||
|
||||
const startTimeWithNumfmtCache = performance.now();
|
||||
const timeWithNumfmtCache = performance.now() - startTimeWithNumfmtCache;
|
||||
// With caching, it takes less time than without caching
|
||||
// console.log(timeWithNumfmtCache, ' ', timeWithNumfmt);
|
||||
expect(timeWithNumfmt - timeWithNumfmtCache).toBeGreaterThanOrEqual(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,826 @@
|
||||
/**
|
||||
* 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 { ICellDataForSheetInterceptor, Injector, Univer, Workbook, Worksheet } from '@univerjs/core';
|
||||
import type { ISetNumfmtMutationParams, ISheetLocation } from '@univerjs/sheets';
|
||||
import {
|
||||
CellModeEnum,
|
||||
CellValueType,
|
||||
createInterceptorKey,
|
||||
excelDateSerial,
|
||||
ICommandService,
|
||||
InterceptorManager,
|
||||
IUniverInstanceService,
|
||||
LocaleType,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { SetNumfmtMutation, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { SheetsNumfmtCellContentController } from '@univerjs/sheets-numfmt';
|
||||
import { getMatrixPlainText, IEditorBridgeService } from '@univerjs/sheets-ui';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import { NumfmtEditorController } from '../numfmt.editor.controller';
|
||||
import { createTestBed } from './test.util';
|
||||
|
||||
const BEFORE_CELL_EDIT = createInterceptorKey<ICellDataForSheetInterceptor, ISheetLocation>('BEFORE_CELL_EDIT');
|
||||
const AFTER_CELL_EDIT = createInterceptorKey<ICellDataForSheetInterceptor, ISheetLocation>('AFTER_CELL_EDIT');
|
||||
|
||||
class MockEditorBridgeService {
|
||||
interceptor = new InterceptorManager({
|
||||
BEFORE_CELL_EDIT,
|
||||
AFTER_CELL_EDIT,
|
||||
});
|
||||
}
|
||||
|
||||
describe('test editor', () => {
|
||||
let unitId: string = '';
|
||||
let subUnitId: string = '';
|
||||
let workbook: Workbook;
|
||||
let worksheet: Worksheet;
|
||||
let testBed: ReturnType<typeof createTestBed>;
|
||||
let commandService: ICommandService;
|
||||
|
||||
beforeEach(() => {
|
||||
testBed = createTestBed(undefined, [
|
||||
[NumfmtEditorController],
|
||||
[SheetsNumfmtCellContentController],
|
||||
[IEditorBridgeService, { useClass: MockEditorBridgeService }],
|
||||
]);
|
||||
|
||||
unitId = testBed.unitId;
|
||||
subUnitId = testBed.subUnitId;
|
||||
commandService = testBed.get(ICommandService);
|
||||
const univerInstanceService = testBed.get(IUniverInstanceService);
|
||||
testBed.get(NumfmtEditorController);
|
||||
testBed.get(SheetsNumfmtCellContentController);
|
||||
workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
worksheet = workbook.getActiveSheet()!;
|
||||
});
|
||||
|
||||
it('before edit with currency', () => {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: '$#,##0;(#,##0)',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = worksheet.getCell(0, 0);
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
expect(cellData!.v).toEqual('$0');
|
||||
expect(cellData!.t).toEqual(2);
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(BEFORE_CELL_EDIT)(cellData, location);
|
||||
// The currency format needs to be entered in the editor with real values, not with currency symbols
|
||||
expect(result!.v).toEqual(0);
|
||||
expect(result!.t).toEqual(2);
|
||||
});
|
||||
|
||||
it('before edit with a conditional text format uses the raw value', () => {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 0, endRow: 0, startColumn: 1, endColumn: 1 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: '[>0]"A";[<0]"B";"B"',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = worksheet.getCell(0, 1);
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 1,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
expect(cellData!.v).toBe('A');
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(BEFORE_CELL_EDIT)(cellData, location);
|
||||
expect(result!.v).toBe(1);
|
||||
expect(result!.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('before edit with data', () => {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: 'h:mm',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = worksheet.getCell(0, 0);
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(BEFORE_CELL_EDIT)(cellData, location);
|
||||
expect(cellData!.v).toEqual('0:00');
|
||||
expect(result!.v).toEqual('0:00:00');
|
||||
});
|
||||
|
||||
it('before edit with percent', () => {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 10, endRow: 10, startColumn: 0, endColumn: 0 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: '0.00%',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = worksheet.getCell(10, 0);
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 10,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
expect(cellData!.v).toEqual('100.12%');
|
||||
expect(cellData!.t).toEqual(2);
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(BEFORE_CELL_EDIT)(cellData, location);
|
||||
// The currency format needs to be entered in the editor with real values, not with currency symbols
|
||||
expect(result!.v).toEqual('100.1234567%');
|
||||
expect(result!.t).toEqual(2);
|
||||
expect(result!.isPercentFormat).toBe(true);
|
||||
});
|
||||
|
||||
describe('after edit with percent', () => {
|
||||
function setPercentFormat() {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 10, endRow: 10, startColumn: 0, endColumn: 0 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: '0.00%',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
}
|
||||
|
||||
it.each([
|
||||
['35%', 0.35],
|
||||
['0.35%', 0.0035],
|
||||
])('stores edited percent text %s as %s', (content, expected) => {
|
||||
setPercentFormat();
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const result = sheetInterceptorService.writeCellInterceptor
|
||||
.fetchThroughInterceptors(AFTER_CELL_EDIT)({ v: content, t: CellValueType.NUMBER }, {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 10,
|
||||
col: 0,
|
||||
});
|
||||
|
||||
expect(result?.v).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it('after edit with data', () => {
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 0, endRow: 0, startColumn: 2, endColumn: 2 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: 'h:mm',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '12:33:22', t: 2 };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 2,
|
||||
origin: cellData,
|
||||
};
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
// The date-time drop is a numeric value, not a literal string
|
||||
expect(result?.v).toBe(2.523171296296296);
|
||||
});
|
||||
|
||||
it('edit number will throw style in this editing', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = {
|
||||
p: {
|
||||
id: '__INTERNAL_EDITOR__DOCS_NORMAL',
|
||||
documentStyle: {
|
||||
pageSize: {
|
||||
width: 230.41758728027344,
|
||||
},
|
||||
marginTop: 6,
|
||||
marginBottom: 2,
|
||||
marginRight: 2,
|
||||
marginLeft: 2,
|
||||
renderConfig: {
|
||||
verticalAlign: 0,
|
||||
horizontalAlign: 0,
|
||||
wrapStrategy: 0,
|
||||
background: {},
|
||||
centerAngle: 0,
|
||||
vertexAngle: 0,
|
||||
},
|
||||
},
|
||||
body: {
|
||||
dataStream: '2022-11-11\r\n',
|
||||
textRuns: [
|
||||
{
|
||||
st: 0,
|
||||
ed: 2,
|
||||
ts: {
|
||||
ff: 'Arial',
|
||||
fs: 11,
|
||||
},
|
||||
},
|
||||
{
|
||||
st: 2,
|
||||
ed: 10,
|
||||
ts: {
|
||||
ff: 'Arial',
|
||||
fs: 11,
|
||||
bl: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
paragraphs: [
|
||||
{
|
||||
startIndex: 10,
|
||||
paragraphId: 'para_sheets_numfmt_edit_date',
|
||||
paragraphStyle: {
|
||||
horizontalAlign: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
customRanges: [],
|
||||
customDecorations: [],
|
||||
},
|
||||
drawings: {},
|
||||
drawingsOrder: [],
|
||||
settings: {
|
||||
zoomRatio: 1,
|
||||
},
|
||||
},
|
||||
v: null,
|
||||
f: null,
|
||||
si: null,
|
||||
};
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
expect(result?.s).toBeFalsy();
|
||||
expect(result?.p).toBeFalsy();
|
||||
expect(result?.v).toBe(44876);
|
||||
});
|
||||
|
||||
it('edit number with bullet should keep rich text', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const richTextParams = {
|
||||
id: '__INTERNAL_EDITOR__DOCS_NORMAL',
|
||||
documentStyle: {
|
||||
pageSize: {
|
||||
width: 595,
|
||||
},
|
||||
documentFlavor: 0,
|
||||
marginTop: 0,
|
||||
marginBottom: 0,
|
||||
marginRight: 0,
|
||||
marginLeft: 0,
|
||||
renderConfig: {
|
||||
vertexAngle: 0,
|
||||
centerAngle: 0,
|
||||
},
|
||||
},
|
||||
drawings: {},
|
||||
drawingsOrder: [],
|
||||
body: {
|
||||
dataStream: '123123123\r\n',
|
||||
textRuns: [
|
||||
{
|
||||
st: 0,
|
||||
ed: 9,
|
||||
ts: {
|
||||
ff: 'Arial',
|
||||
fs: 11,
|
||||
},
|
||||
},
|
||||
],
|
||||
paragraphs: [
|
||||
{
|
||||
startIndex: 9,
|
||||
paragraphId: 'para_sheets_numfmt_bullet',
|
||||
paragraphStyle: {
|
||||
horizontalAlign: 0,
|
||||
},
|
||||
bullet: {
|
||||
nestingLevel: 0,
|
||||
textStyle: {
|
||||
fs: 20,
|
||||
},
|
||||
listType: 'CHECK_LIST',
|
||||
listId: 'xIECkc',
|
||||
},
|
||||
},
|
||||
],
|
||||
sectionBreaks: [
|
||||
{
|
||||
sectionId: 'section_fixture_1028',
|
||||
startIndex: 10,
|
||||
},
|
||||
],
|
||||
customRanges: [],
|
||||
customDecorations: [],
|
||||
},
|
||||
};
|
||||
const cellData = {
|
||||
p: richTextParams,
|
||||
v: null,
|
||||
f: null,
|
||||
si: null,
|
||||
};
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
expect(result?.p).toEqual(richTextParams);
|
||||
expect(result?.v).toBeFalsy();
|
||||
});
|
||||
|
||||
it('edit number 123456789123456789', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '123456789123456789' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe('123456789123456789');
|
||||
expect(result?.t).toBe(CellValueType.FORCE_STRING);
|
||||
});
|
||||
|
||||
it('edit number 1212121212121212.2345', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '1212121212121212.2345' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe('1212121212121212.2345');
|
||||
expect(result?.t).toBe(CellValueType.FORCE_STRING);
|
||||
});
|
||||
|
||||
it('edit number 1212121212121212', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '1212121212121212' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe(1212121212121212);
|
||||
expect(result?.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('edit number 1e3', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '1e3' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe(1000);
|
||||
expect(result?.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('edit number 001', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '001' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe(1);
|
||||
expect(result?.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('edit number +123.', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '+123.' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe(123);
|
||||
expect(result?.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('edit content 5A', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '5A' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe('5A');
|
||||
});
|
||||
|
||||
it('edit content 5 A', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '5 A' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe(0.2083333333333333);
|
||||
expect(result?.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('edit content 1 23', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '1 23' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe('1 23');
|
||||
});
|
||||
|
||||
it('edit content 2/3', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '2/3' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
expect(result?.v).toBe(excelDateSerial(new Date(Date.UTC(new Date().getUTCFullYear(), 1, 3))));
|
||||
expect(result?.t).toBe(CellValueType.NUMBER);
|
||||
});
|
||||
|
||||
it('edit content 1000,', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '1000,' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe('1000,');
|
||||
});
|
||||
|
||||
it('edit content 1000,1.00', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '1000,1.00' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe('1000,1.00');
|
||||
});
|
||||
|
||||
it('edit content $1000', () => {
|
||||
const sheetInterceptorService = testBed.get(SheetInterceptorService);
|
||||
const cellData = { v: '$1000' };
|
||||
const location = {
|
||||
workbook,
|
||||
worksheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: cellData,
|
||||
};
|
||||
|
||||
const result = sheetInterceptorService.writeCellInterceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)(cellData, location);
|
||||
|
||||
expect(result?.v).toBe(1000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('test get cell text/plain', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let commandService: ICommandService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const testBed = createTestBed({
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
cellData: {
|
||||
0: {
|
||||
0: {
|
||||
v: 44876,
|
||||
t: 2,
|
||||
},
|
||||
1: {
|
||||
p: {
|
||||
id: '__INTERNAL_EDITOR__DOCS_NORMAL',
|
||||
documentStyle: {
|
||||
pageSize: {
|
||||
width: 72.90791320800781,
|
||||
},
|
||||
marginTop: 0,
|
||||
marginBottom: 2,
|
||||
marginRight: 2,
|
||||
marginLeft: 2,
|
||||
renderConfig: {
|
||||
verticalAlign: 0,
|
||||
horizontalAlign: 0,
|
||||
wrapStrategy: 0,
|
||||
background: {},
|
||||
centerAngle: 0,
|
||||
vertexAngle: 0,
|
||||
zeroWidthParagraphBreak: 1,
|
||||
},
|
||||
},
|
||||
body: {
|
||||
dataStream: 'univer\r\n',
|
||||
textRuns: [
|
||||
{
|
||||
st: 0,
|
||||
ed: 1,
|
||||
ts: {
|
||||
ff: 'Arial',
|
||||
fs: 11,
|
||||
},
|
||||
},
|
||||
{
|
||||
st: 1,
|
||||
ed: 4,
|
||||
ts: {
|
||||
ff: 'Arial',
|
||||
fs: 28,
|
||||
},
|
||||
},
|
||||
{
|
||||
st: 4,
|
||||
ed: 6,
|
||||
ts: {
|
||||
ff: 'Arial',
|
||||
fs: 11,
|
||||
},
|
||||
},
|
||||
],
|
||||
paragraphs: [
|
||||
{
|
||||
startIndex: 6,
|
||||
paragraphId: 'para_sheets_numfmt_rich_text',
|
||||
paragraphStyle: {
|
||||
horizontalAlign: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
customRanges: [],
|
||||
customDecorations: [],
|
||||
customBlocks: [],
|
||||
},
|
||||
drawings: {},
|
||||
drawingsOrder: [],
|
||||
settings: {
|
||||
zoomRatio: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
locale: LocaleType.ZH_CN,
|
||||
name: '',
|
||||
sheetOrder: [],
|
||||
styles: {},
|
||||
}, [
|
||||
[NumfmtEditorController],
|
||||
[SheetsNumfmtCellContentController],
|
||||
[IEditorBridgeService, { useClass: MockEditorBridgeService }],
|
||||
]);
|
||||
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
commandService = get(ICommandService);
|
||||
testBed.get(NumfmtEditorController);
|
||||
testBed.get(SheetsNumfmtCellContentController);
|
||||
testBed.get(SheetInterceptorService);
|
||||
});
|
||||
|
||||
it('when the cell has a numfmt should use both to get displayV', () => {
|
||||
const univerInstanceService = get(IUniverInstanceService);
|
||||
|
||||
const workbook = univerInstanceService.getUnit<Workbook>('test');
|
||||
const worksheet = workbook?.getActiveSheet();
|
||||
if (worksheet) {
|
||||
const unitId = 'test';
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
const params: ISetNumfmtMutationParams = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
values: {
|
||||
1: {
|
||||
ranges: [{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }],
|
||||
},
|
||||
},
|
||||
refMap: {
|
||||
1: {
|
||||
pattern: 'yyyy-mm-dd',
|
||||
},
|
||||
},
|
||||
};
|
||||
commandService.syncExecuteCommand(SetNumfmtMutation.id, params);
|
||||
const matrix = worksheet.getMatrixWithMergedCells(0, 0, 0, 1, CellModeEnum.Both);
|
||||
const plain = getMatrixPlainText(matrix);
|
||||
expect(plain).toBe('2022-11-11\tuniver');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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 { ICellData, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import {
|
||||
ICommandService,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
RedoCommand,
|
||||
Tools,
|
||||
UndoCommand,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import {
|
||||
AFTER_CELL_EDIT,
|
||||
INumfmtService,
|
||||
NumfmtService,
|
||||
RemoveNumfmtMutation,
|
||||
SetNumfmtMutation,
|
||||
SetRangeValuesCommand,
|
||||
SetRangeValuesMutation,
|
||||
SetSelectionsOperation,
|
||||
SetWorksheetActiveOperation,
|
||||
SheetInterceptorService,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import { SheetsNumfmtCellContentController } from '@univerjs/sheets-numfmt';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { NumfmtEditorController } from '../numfmt.editor.controller';
|
||||
|
||||
describe('NumfmtEditorController explicit command targets', () => {
|
||||
let univer: Univer;
|
||||
|
||||
beforeEach(() => {
|
||||
univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
injector.add([INumfmtService, { useClass: NumfmtService }]);
|
||||
injector.add([SheetInterceptorService]);
|
||||
injector.add([SheetsSelectionsService]);
|
||||
injector.add([SheetsNumfmtCellContentController]);
|
||||
injector.add([NumfmtEditorController]);
|
||||
const locale = injector.get(LocaleService);
|
||||
locale.load({ [LocaleType.EN_US]: {} });
|
||||
locale.setLocale(LocaleType.EN_US);
|
||||
locale.setDirection('ltr');
|
||||
const commands = injector.get(ICommandService);
|
||||
for (const command of [
|
||||
SetRangeValuesCommand,
|
||||
SetRangeValuesMutation,
|
||||
SetNumfmtMutation,
|
||||
RemoveNumfmtMutation,
|
||||
SetSelectionsOperation,
|
||||
SetWorksheetActiveOperation,
|
||||
]) {
|
||||
commands.registerCommand(command);
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => univer.dispose());
|
||||
|
||||
for (const current of ['none', 'peer', 'other-worksheet'] as const) {
|
||||
for (const formatted of [false, true]) {
|
||||
it(`writes ${formatted ? 'parsed percentage' : 'plain numeric data'} with ${current} current and preserves history`, async () => {
|
||||
const injector = univer.__getInjector();
|
||||
const instances = injector.get(IUniverInstanceService);
|
||||
const peer = instances.createUnit<IWorkbookData, Workbook>(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
workbookData('peer'),
|
||||
{ makeCurrent: false }
|
||||
);
|
||||
const child = instances.createUnit<IWorkbookData, Workbook>(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
workbookData('child'),
|
||||
{ makeCurrent: false }
|
||||
);
|
||||
if (current !== 'none') {
|
||||
instances.setCurrentUnitForType(current === 'peer' ? 'peer' : 'child');
|
||||
}
|
||||
injector.get(NumfmtEditorController);
|
||||
const worksheet = child.getSheetBySheetId('sheet2')!;
|
||||
const before = Tools.deepClone(worksheet.getCellRaw(0, 0));
|
||||
const peerBefore = Tools.deepClone(peer.getSnapshot());
|
||||
const otherSheetBefore = Tools.deepClone(child.getSheetBySheetId('sheet1')!.getSnapshot());
|
||||
let value: ICellData = { v: 42 };
|
||||
if (formatted) {
|
||||
const interceptor = injector.get(SheetInterceptorService).writeCellInterceptor;
|
||||
value = interceptor.fetchThroughInterceptors(AFTER_CELL_EDIT)({ v: '50%' }, {
|
||||
workbook: child,
|
||||
worksheet,
|
||||
unitId: 'child',
|
||||
subUnitId: 'sheet2',
|
||||
row: 0,
|
||||
col: 0,
|
||||
origin: before,
|
||||
})!;
|
||||
}
|
||||
const commands = injector.get(ICommandService);
|
||||
expect(await commands.executeCommand(SetRangeValuesCommand.id, {
|
||||
unitId: 'child',
|
||||
subUnitId: 'sheet2',
|
||||
range: { startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 },
|
||||
value,
|
||||
})).toBe(true);
|
||||
expect(worksheet.getCellRaw(0, 0)?.v).toBe(formatted ? 0.5 : 42);
|
||||
const pattern = injector.get(INumfmtService).getValue('child', 'sheet2', 0, 0)?.pattern;
|
||||
expect(pattern).toBe(formatted ? '0%' : undefined);
|
||||
expect(peer.getSnapshot()).toEqual(peerBefore);
|
||||
expect(child.getSheetBySheetId('sheet1')!.getSnapshot()).toEqual(otherSheetBefore);
|
||||
|
||||
instances.focusUnit('child');
|
||||
expect(await commands.executeCommand(UndoCommand.id)).toBe(true);
|
||||
expect(worksheet.getCellRaw(0, 0)?.v).toBe(before?.v);
|
||||
expect(injector.get(INumfmtService).getValue('child', 'sheet2', 0, 0)?.pattern).toBeUndefined();
|
||||
expect(await commands.executeCommand(RedoCommand.id)).toBe(true);
|
||||
expect(worksheet.getCellRaw(0, 0)?.v).toBe(formatted ? 0.5 : 42);
|
||||
expect(injector.get(INumfmtService).getValue('child', 'sheet2', 0, 0)?.pattern).toBe(pattern);
|
||||
expect(peer.getSnapshot()).toEqual(peerBefore);
|
||||
expect(child.getSheetBySheetId('sheet1')!.getSnapshot()).toEqual(otherSheetBefore);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function workbookData(id: string): IWorkbookData {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
appVersion: 'test',
|
||||
locale: LocaleType.EN_US,
|
||||
styles: {},
|
||||
sheetOrder: ['sheet1', 'sheet2'],
|
||||
sheets: {
|
||||
sheet1: { id: 'sheet1', name: 'Other', cellData: { 0: { 0: { v: 7 } } } },
|
||||
sheet2: { id: 'sheet2', name: 'Target', cellData: { 0: { 0: { v: 11 } } } },
|
||||
},
|
||||
};
|
||||
}
|
||||
109
packages/sheets-numfmt-ui/src/controllers/__tests__/test.util.ts
Normal file
109
packages/sheets-numfmt-ui/src/controllers/__tests__/test.util.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* 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, IWorkbookData } from '@univerjs/core';
|
||||
import { ICommandService, Inject, Injector, IUniverInstanceService, LocaleType, Plugin, Univer, UniverInstanceType } from '@univerjs/core';
|
||||
import { LexerTreeBuilder } from '@univerjs/engine-formula';
|
||||
import {
|
||||
INumfmtService,
|
||||
NumfmtService,
|
||||
RemoveNumfmtMutation,
|
||||
SetNumfmtMutation,
|
||||
SheetInterceptorService,
|
||||
} from '@univerjs/sheets';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
|
||||
const TEST_WORKBOOK_DATA_DEMO: () => IWorkbookData = () => ({
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
cellData: {
|
||||
0: { 0: { v: 0, t: 2 }, 1: { v: 1, t: 2 }, 2: { v: 2, t: 2 }, 3: { v: 3, t: 2 }, 4: { v: 4, t: 2 } },
|
||||
1: { 0: { v: 0, t: 2 }, 1: { v: 1, t: 2 }, 2: { v: 2, t: 2 }, 3: { v: 3, t: 2 }, 4: { v: 4, t: 2 } },
|
||||
2: { 0: { v: 0, t: 2 }, 1: { v: 1, t: 2 }, 2: { v: 2, t: 2 }, 3: { v: 3, t: 2 }, 4: { v: 4, t: 2 } },
|
||||
3: { 0: { v: 0, t: 2 }, 1: { v: 1, t: 2 }, 2: { v: 2, t: 2 }, 3: { v: 3, t: 2 }, 4: { v: 4, t: 2 } },
|
||||
10: {
|
||||
0: {
|
||||
v: 1.001234567,
|
||||
t: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
locale: LocaleType.ZH_CN,
|
||||
name: '',
|
||||
sheetOrder: [],
|
||||
styles: {},
|
||||
});
|
||||
|
||||
export const createTestBed = (defaultWorkbook?: IWorkbookData, dependencies?: Dependency[]) => {
|
||||
const univer = new Univer();
|
||||
const injector = univer.__getInjector();
|
||||
const get = injector.get.bind(injector);
|
||||
|
||||
class TestPlugin extends Plugin {
|
||||
static override pluginName = 'test-plugin';
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
_config: undefined,
|
||||
@Inject(Injector) override readonly _injector: Injector
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
const injector = this._injector;
|
||||
injector.add([INumfmtService, { useClass: NumfmtService }]);
|
||||
injector.add([SheetInterceptorService]);
|
||||
injector.add([SheetSkeletonManagerService]);
|
||||
injector.add([LexerTreeBuilder]);
|
||||
|
||||
dependencies?.forEach((d) => injector.add(d));
|
||||
|
||||
injector.get(SheetInterceptorService);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
this._injector.get(INumfmtService);
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
|
||||
const workbookJson = defaultWorkbook ?? TEST_WORKBOOK_DATA_DEMO();
|
||||
const sheet = univer.createUnit(UniverInstanceType.UNIVER_SHEET, workbookJson);
|
||||
|
||||
const univerInstanceService = injector.get(IUniverInstanceService);
|
||||
const commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(RemoveNumfmtMutation);
|
||||
commandService.registerCommand(SetNumfmtMutation);
|
||||
univerInstanceService.focusUnit('test');
|
||||
const unitId = workbookJson.id;
|
||||
const subUnitId = workbookJson.sheets.sheet1.id!;
|
||||
|
||||
return {
|
||||
univer,
|
||||
get,
|
||||
sheet,
|
||||
unitId,
|
||||
subUnitId,
|
||||
// numfmtService,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 { Disposable, Inject } from '@univerjs/core';
|
||||
import {
|
||||
AddDigitsIcon,
|
||||
DollarIcon,
|
||||
DongIcon,
|
||||
EuroIcon,
|
||||
PercentIcon,
|
||||
ReduceDigitsIcon,
|
||||
RialIcon,
|
||||
RmbIcon,
|
||||
RoubleIcon,
|
||||
RupiahIcon,
|
||||
WonIcon,
|
||||
ZlotyIcon,
|
||||
} from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import { MORE_NUMFMT_TYPE_KEY, MoreNumfmtType, Options, OPTIONS_KEY } from '../views/components/MoreNumfmtType';
|
||||
import { SheetNumfmtPanel } from '../views/SheetNumfmtPanel';
|
||||
|
||||
const SHEET_NUMFMT_PANEL = 'SHEET_NUMFMT_PANEL';
|
||||
|
||||
export class ComponentsController extends Disposable {
|
||||
constructor(
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
@Inject(IconManager) private readonly _iconManager: IconManager
|
||||
) {
|
||||
super();
|
||||
|
||||
this._registerComponents();
|
||||
this._registerIcons();
|
||||
}
|
||||
|
||||
private _registerIcons(): void {
|
||||
this.disposeWithMe(this._iconManager.register({
|
||||
AddDigitsIcon,
|
||||
DollarIcon,
|
||||
DongIcon,
|
||||
EuroIcon,
|
||||
PercentIcon,
|
||||
ReduceDigitsIcon,
|
||||
RialIcon,
|
||||
RmbIcon,
|
||||
RoubleIcon,
|
||||
RupiahIcon,
|
||||
WonIcon,
|
||||
ZlotyIcon,
|
||||
}));
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
this.disposeWithMe(
|
||||
this._componentManager.register(SHEET_NUMFMT_PANEL, SheetNumfmtPanel)
|
||||
);
|
||||
|
||||
([
|
||||
[MORE_NUMFMT_TYPE_KEY, MoreNumfmtType],
|
||||
[OPTIONS_KEY, Options],
|
||||
] as const).forEach(([key, comp]) => {
|
||||
this.disposeWithMe(
|
||||
this._componentManager.register(key, comp)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 { Disposable, Inject } from '@univerjs/core';
|
||||
import { ComponentManager } from '@univerjs/ui';
|
||||
import { MOBILE_CUSTOM_NUMFMT_COMPONENT } from '../../menu/mobile-menu';
|
||||
import { MobileCustomFormat } from '../../views/mobile/MobileCustomFormat';
|
||||
|
||||
export class MobileComponentsController extends Disposable {
|
||||
constructor(@Inject(ComponentManager) componentManager: ComponentManager) {
|
||||
super();
|
||||
|
||||
this.disposeWithMe(componentManager.register(MOBILE_CUSTOM_NUMFMT_COMPONENT, MobileCustomFormat));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 { Workbook } from '@univerjs/core';
|
||||
import type { ISetNumfmtMutationParams } from '@univerjs/sheets';
|
||||
import { Disposable, ICommandService, Inject, IUniverInstanceService, LocaleService, UniverInstanceType } from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { RemoveNumfmtMutation, SetNumfmtMutation } from '@univerjs/sheets';
|
||||
import { SheetsNumfmtCellContentController } from '@univerjs/sheets-numfmt';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { merge, Observable } from 'rxjs';
|
||||
import { debounceTime } from 'rxjs/operators';
|
||||
import { SetMobileNumfmtCommand } from '../../commands/commands/set-mobile-numfmt.command';
|
||||
|
||||
export class SheetNumfmtMobileUIController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
@Inject(SheetsNumfmtCellContentController)
|
||||
private readonly _sheetsNumfmtCellContentController: SheetsNumfmtCellContentController
|
||||
) {
|
||||
super();
|
||||
|
||||
this.disposeWithMe(this._commandService.registerCommand(SetMobileNumfmtCommand));
|
||||
this._initNumfmtLocalChange();
|
||||
this._initCommandExecutedListener();
|
||||
}
|
||||
|
||||
private _initNumfmtLocalChange(): void {
|
||||
this.disposeWithMe(
|
||||
merge(
|
||||
this._sheetsNumfmtCellContentController.locale$,
|
||||
this._localeService.currentLocale$
|
||||
).subscribe(() => this._forceUpdate())
|
||||
);
|
||||
}
|
||||
|
||||
private _initCommandExecutedListener(): void {
|
||||
const commandList = [RemoveNumfmtMutation.id, SetNumfmtMutation.id];
|
||||
this.disposeWithMe(
|
||||
new Observable<string>((subscribe) => {
|
||||
const disposable = this._commandService.onCommandExecuted((command) => {
|
||||
if (commandList.includes(command.id)) {
|
||||
const params = command.params as ISetNumfmtMutationParams;
|
||||
subscribe.next(params.unitId);
|
||||
}
|
||||
});
|
||||
return () => disposable.dispose();
|
||||
}).pipe(debounceTime(16)).subscribe((unitId) => this._forceUpdate(unitId))
|
||||
);
|
||||
}
|
||||
|
||||
private _forceUpdate(unitId?: string): void {
|
||||
const resolvedUnitId = unitId ?? this._univerInstanceService
|
||||
.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)
|
||||
?.getUnitId();
|
||||
if (!resolvedUnitId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderUnit = this._renderManagerService.getRenderUnitById(resolvedUnitId);
|
||||
renderUnit?.with(SheetSkeletonManagerService).reCalculate();
|
||||
renderUnit?.mainComponent?.makeDirty();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* 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, Nullable, Workbook, Worksheet } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule } from '@univerjs/engine-render';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import type { IUniverSheetsNumfmtConfig } from '@univerjs/sheets-numfmt';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { Disposable, getNumfmtParseValueFilter, ICommandService, IConfigService, Inject, isRealNum, isTextFormat, IUniverInstanceService, LocaleService, Tools, UniverInstanceType } from '@univerjs/core';
|
||||
import { INumfmtService, SheetsSelectionsService, TextToNumberCommand } from '@univerjs/sheets';
|
||||
import { SHEETS_NUMFMT_PLUGIN_CONFIG_KEY } from '@univerjs/sheets-numfmt';
|
||||
import { CellAlertManagerService, CellAlertType } from '@univerjs/sheets-ui';
|
||||
|
||||
const ALERT_KEY = 'SHEET_NUMFMT_ALERT';
|
||||
|
||||
export class NumfmtAlertRenderController extends Disposable implements IRenderModule {
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
|
||||
@Inject(CellAlertManagerService) private readonly _cellAlertManagerService: CellAlertManagerService,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService,
|
||||
@Inject(INumfmtService) private _numfmtService: INumfmtService,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
|
||||
) {
|
||||
super();
|
||||
this._init();
|
||||
}
|
||||
|
||||
private _init() {
|
||||
this._initCellAlertPopup();
|
||||
}
|
||||
|
||||
private _initCellAlertPopup() {
|
||||
this.disposeWithMe(this._selectionManagerService.selectionChanged$.subscribe((selections) => this._updateAlert(selections)));
|
||||
this.disposeWithMe(this._context.unit.activeSheet$.subscribe(() => this._updateAlert(this._selectionManagerService.getCurrentSelections())));
|
||||
this.disposeWithMe(
|
||||
this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET).subscribe(() =>
|
||||
this._updateAlert(this._selectionManagerService.getCurrentSelections())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _isCurrentSheet(worksheet: Nullable<Worksheet>): worksheet is Worksheet {
|
||||
const selectionParam = this._selectionManagerService.currentSelectionParam;
|
||||
return Boolean(
|
||||
worksheet &&
|
||||
selectionParam?.unitId === this._context.unit.getUnitId() &&
|
||||
selectionParam.sheetId === worksheet.getSheetId()
|
||||
);
|
||||
}
|
||||
|
||||
private _updateAlert(selections: Nullable<Readonly<ISelectionWithStyle[]>>) {
|
||||
this._hideAlert();
|
||||
const workbook = this._context.unit;
|
||||
const worksheet = workbook.getActiveSheet();
|
||||
if (!this._isCurrentSheet(worksheet)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (selections?.length !== 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._configService.getConfig<IUniverSheetsNumfmtConfig>(SHEETS_NUMFMT_PLUGIN_CONFIG_KEY)?.disableTextFormatAlert) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = selections[0];
|
||||
const primary = selection.primary;
|
||||
if (!primary) {
|
||||
return;
|
||||
}
|
||||
|
||||
const row = primary.actualRow;
|
||||
const col = primary.actualColumn;
|
||||
const unitId = workbook.getUnitId();
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
const cellData = worksheet.getCell(row, col);
|
||||
let numfmtValue;
|
||||
|
||||
if (cellData?.s) {
|
||||
numfmtValue = workbook.getStyles().get(cellData.s)?.n;
|
||||
}
|
||||
if (!numfmtValue) {
|
||||
numfmtValue = this._numfmtService.getValue(unitId, subUnitId, row, col);
|
||||
}
|
||||
|
||||
const cellValue = cellData?.v;
|
||||
const isConvertibleNumber = isRealNum(cellValue as CellValue) ||
|
||||
(typeof cellValue === 'string' && typeof getNumfmtParseValueFilter(cellValue)?.v === 'number');
|
||||
if (!isTextFormat(numfmtValue?.pattern) || !Tools.isDefine(cellValue) || !isConvertibleNumber) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = { ...selection.range };
|
||||
this._cellAlertManagerService.showAlert({
|
||||
type: CellAlertType.WARNING,
|
||||
title: this._localeService.t<LocaleKey>('sheets-numfmt-ui.info.error'),
|
||||
message: this._localeService.t<LocaleKey>('sheets-numfmt-ui.info.forceStringInfo'),
|
||||
location: { unitId, subUnitId, row, col },
|
||||
width: 200,
|
||||
height: 74,
|
||||
key: this._alertKey,
|
||||
menu: [{
|
||||
label: this._localeService.t<LocaleKey>('sheets-numfmt-ui.info.convertToNumber'),
|
||||
onSelect: () => {
|
||||
const result = this._commandService.syncExecuteCommand(TextToNumberCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ranges: [range],
|
||||
});
|
||||
if (result) {
|
||||
this._hideAlert();
|
||||
}
|
||||
},
|
||||
}],
|
||||
});
|
||||
}
|
||||
|
||||
private get _alertKey() {
|
||||
return `${ALERT_KEY}:${this._context.unit.getUnitId()}`;
|
||||
}
|
||||
|
||||
private _hideAlert() {
|
||||
this._cellAlertManagerService.removeAlert(this._alertKey);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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 { ISetNumfmtCommandParams } from '@univerjs/sheets-numfmt';
|
||||
import type { RepeatableCommandHandler } from '@univerjs/sheets-ui';
|
||||
import { Disposable, Optional } from '@univerjs/core';
|
||||
import { SetNumfmtCommand } from '@univerjs/sheets-numfmt';
|
||||
import { IRepeatLastActionService, RepeatLastActionPermission } from '@univerjs/sheets-ui';
|
||||
|
||||
export class NumfmtRepeatLastActionController extends Disposable {
|
||||
constructor(
|
||||
@Optional(IRepeatLastActionService) private readonly _repeatLastActionService?: IRepeatLastActionService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommandRecording();
|
||||
}
|
||||
|
||||
private _initCommandRecording(): void {
|
||||
if (!this._repeatLastActionService) {
|
||||
return;
|
||||
}
|
||||
|
||||
const handler: RepeatableCommandHandler<ISetNumfmtCommandParams> = (selections, params) => {
|
||||
if (!params) return;
|
||||
|
||||
const { values } = params;
|
||||
const numfmtCell = values.find((cell) => cell.pattern);
|
||||
|
||||
if (!numfmtCell) return;
|
||||
|
||||
const { pattern, type } = numfmtCell;
|
||||
const newValues = [];
|
||||
const cache = new Set<string>();
|
||||
|
||||
for (const selection of selections) {
|
||||
const { startRow, startColumn, endRow, endColumn } = selection;
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
for (let col = startColumn; col <= endColumn; col++) {
|
||||
const key = `${row}-${col}`;
|
||||
if (cache.has(key)) {
|
||||
continue;
|
||||
}
|
||||
cache.add(key);
|
||||
newValues.push({ row, col, pattern, type });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...params,
|
||||
values: newValues,
|
||||
};
|
||||
};
|
||||
|
||||
this.disposeWithMe(this._repeatLastActionService.registerRepeatableCommand(SetNumfmtCommand.id, handler, RepeatLastActionPermission.CellStyle));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,394 @@
|
||||
/**
|
||||
* 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 { ICellData, ICellDataForSheetInterceptor, IDocumentBody, IRange, Nullable } from '@univerjs/core';
|
||||
import type {
|
||||
INumfmtItemWithCache,
|
||||
IRemoveNumfmtMutationParams,
|
||||
ISetCellsNumfmt,
|
||||
ISetNumfmtMutationParams,
|
||||
ISetRangeValuesMutationParams,
|
||||
} from '@univerjs/sheets';
|
||||
import {
|
||||
CellValueType,
|
||||
Disposable,
|
||||
getNumfmtParseValueFilter,
|
||||
Inject,
|
||||
Injector,
|
||||
isRealNum,
|
||||
isTextFormat,
|
||||
IUniverInstanceService,
|
||||
Optional,
|
||||
toDisposable,
|
||||
willLoseNumericPrecision,
|
||||
} from '@univerjs/core';
|
||||
import { stripErrorMargin } from '@univerjs/engine-formula';
|
||||
import {
|
||||
AFTER_CELL_EDIT,
|
||||
BEFORE_CELL_EDIT,
|
||||
factoryRemoveNumfmtUndoMutation,
|
||||
factorySetNumfmtUndoMutation,
|
||||
getSheetCommandTarget,
|
||||
INumfmtService,
|
||||
RemoveNumfmtMutation,
|
||||
SetNumfmtMutation,
|
||||
SetRangeValuesCommand,
|
||||
SheetInterceptorService,
|
||||
transformCellsToRange,
|
||||
} from '@univerjs/sheets';
|
||||
import { getPatternType, SheetsNumfmtCellContentController } from '@univerjs/sheets-numfmt';
|
||||
import { IEditorBridgeService } from '@univerjs/sheets-ui';
|
||||
import { parseTemporalEditorValue, serializeTemporalEditorValue } from '../utils/temporal-editor-value';
|
||||
|
||||
const createCollectEffectMutation = () => {
|
||||
interface IConfig {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
row: number;
|
||||
col: number;
|
||||
value: Nullable<INumfmtItemWithCache>;
|
||||
}
|
||||
let list: IConfig[] = [];
|
||||
const add = (unitId: string, subUnitId: string, row: number, col: number, value: Nullable<INumfmtItemWithCache>) =>
|
||||
list.push({ unitId, subUnitId, row, col, value });
|
||||
const getEffects = () => list;
|
||||
const clean = () => {
|
||||
list = [];
|
||||
};
|
||||
return {
|
||||
add,
|
||||
getEffects,
|
||||
clean,
|
||||
};
|
||||
};
|
||||
|
||||
export class NumfmtEditorController extends Disposable {
|
||||
// collect effect mutations when edit end and push this to commands stack in next commands progress
|
||||
private _collectEffectMutation = createCollectEffectMutation();
|
||||
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(INumfmtService) private _numfmtService: INumfmtService,
|
||||
@Inject(IUniverInstanceService) private _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(Injector) private _injector: Injector,
|
||||
@Inject(SheetsNumfmtCellContentController) private _sheetsNumfmtCellContentController: SheetsNumfmtCellContentController,
|
||||
@Optional(IEditorBridgeService) private _editorBridgeService?: IEditorBridgeService
|
||||
) {
|
||||
super();
|
||||
this._initInterceptorEditorStart();
|
||||
this._initInterceptorEditorEnd();
|
||||
this._initInterceptorCommands();
|
||||
}
|
||||
|
||||
private _initInterceptorEditorStart() {
|
||||
if (!this._editorBridgeService) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.disposeWithMe(
|
||||
toDisposable(
|
||||
this._sheetInterceptorService.writeCellInterceptor.intercept(BEFORE_CELL_EDIT, {
|
||||
handler: (value, context, next) => {
|
||||
/**
|
||||
* This value is get by `worksheet.getCell()`, it has been processed by cell content interceptor, and used to display in cell render, so it should be the final value after all the processing of number format.
|
||||
* But the editor has different requirement for different number format type, so we need to get the raw cell value and number format value to determine the final value for editor.
|
||||
*/
|
||||
const row = context.row;
|
||||
const col = context.col;
|
||||
const numfmtCell = this._numfmtService.getValue(
|
||||
context.unitId,
|
||||
context.subUnitId,
|
||||
row,
|
||||
col
|
||||
);
|
||||
if (numfmtCell) {
|
||||
const rawCell = context.worksheet.getCellRaw(row, col);
|
||||
if (!rawCell?.f && rawCell?.t === CellValueType.NUMBER && isRealNum(rawCell.v)) {
|
||||
// Excel completes omitted components in the active date/clock format without changing the stored numfmt.
|
||||
const canonicalValue = serializeTemporalEditorValue({
|
||||
serial: Number(rawCell.v),
|
||||
pattern: numfmtCell.pattern,
|
||||
locale: this._sheetsNumfmtCellContentController.getLocale(context.workbook),
|
||||
dateSystem: context.workbook.getDateSystem(),
|
||||
});
|
||||
if (canonicalValue != null) {
|
||||
return next && next({ ...rawCell, v: canonicalValue });
|
||||
}
|
||||
}
|
||||
|
||||
const type = getPatternType(numfmtCell.pattern);
|
||||
switch (type) {
|
||||
/**
|
||||
* For scientific, currency, grouped and number format, the editor should display the raw number value without format, unlike the cell render which display the formatted value.
|
||||
*/
|
||||
case 'scientific':
|
||||
case 'currency':
|
||||
case 'grouped':
|
||||
case 'number': {
|
||||
const cell: Nullable<ICellData> = { ...context.worksheet.getCellRaw(row, col) };
|
||||
if (cell?.t === CellValueType.NUMBER && isRealNum(cell.v)) {
|
||||
cell.v = stripErrorMargin(Number(cell.v));
|
||||
}
|
||||
return next && next(cell);
|
||||
}
|
||||
/**
|
||||
* For percent format, the editor should display the full percent value, unlike the cell render which display the limited decimal places.
|
||||
* e.g. { v: 1.001234567, t: 2, s: { n: { pattern: '0.00%' } } } should display as '100.12%' in cell render, but when edit this cell, the editor should display '100.1234567%' rather than '100.12%'.
|
||||
* If the editor also display '100.12%', will lose precision when before edit.
|
||||
*/
|
||||
case 'percent': {
|
||||
const cell: Nullable<ICellDataForSheetInterceptor> = { ...context.worksheet.getCellRaw(row, col) };
|
||||
if (cell?.t === CellValueType.NUMBER && isRealNum(cell.v)) {
|
||||
cell.v = `${stripErrorMargin(Number(cell.v) * 100)}%`;
|
||||
cell.isPercentFormat = true;
|
||||
}
|
||||
return next && next(cell);
|
||||
}
|
||||
/**
|
||||
* For date, time and datetime format, the editor should display the formatted value like the cell render.
|
||||
*/
|
||||
case 'date':
|
||||
case 'time':
|
||||
case 'datetime':
|
||||
default: {
|
||||
return next && next(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return next(value);
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the values after edit
|
||||
* @private
|
||||
* @memberof NumfmtService
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _initInterceptorEditorEnd() {
|
||||
this.disposeWithMe(
|
||||
toDisposable(
|
||||
this._sheetInterceptorService.writeCellInterceptor.intercept(AFTER_CELL_EDIT, {
|
||||
// eslint-disable-next-line max-lines-per-function,complexity
|
||||
handler: (value, context, next) => {
|
||||
if (!value?.v && !value?.p) {
|
||||
return next(value);
|
||||
}
|
||||
|
||||
// clear the effect
|
||||
this._collectEffectMutation.clean();
|
||||
const currentNumfmtValue = this._numfmtService.getValue(
|
||||
context.unitId,
|
||||
context.subUnitId,
|
||||
context.row,
|
||||
context.col
|
||||
);
|
||||
|
||||
const originCell = context.worksheet.getCellRaw(context.row, context.col);
|
||||
|
||||
// if the cell is text format or force string, do not convert the value
|
||||
if (isTextFormat(currentNumfmtValue?.pattern) || value.t === CellValueType.FORCE_STRING) {
|
||||
return next(value);
|
||||
}
|
||||
|
||||
const body = value.p?.body;
|
||||
const content = value?.p?.body?.dataStream ? value.p.body.dataStream.replace(/\r\n$/, '') : String(value.v);
|
||||
const locale = this._sheetsNumfmtCellContentController.getLocale(context.workbook);
|
||||
const dateSystem = context.workbook.getDateSystem();
|
||||
const numfmtInfo = getNumfmtParseValueFilter(content, { locale, dateSystem });
|
||||
|
||||
if (body) {
|
||||
if (!canConvertRichTextToNumfmt(body)) {
|
||||
return next(value);
|
||||
} else {
|
||||
const { dataStream } = body;
|
||||
const dataStreamWithoutEnd = dataStream.replace(/\r\n$/, '');
|
||||
const num = Number(dataStreamWithoutEnd);
|
||||
if (Number.isNaN(num) && !numfmtInfo) {
|
||||
return next(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentNumfmtValue?.pattern && originCell?.t === CellValueType.NUMBER && isRealNum(originCell.v)) {
|
||||
const temporalValue = parseTemporalEditorValue({
|
||||
content,
|
||||
originalSerial: Number(originCell.v),
|
||||
pattern: currentNumfmtValue.pattern,
|
||||
locale,
|
||||
dateSystem,
|
||||
});
|
||||
if (temporalValue != null) {
|
||||
return next({ ...value, p: undefined, v: stripErrorMargin(temporalValue, 16), t: CellValueType.NUMBER });
|
||||
}
|
||||
}
|
||||
|
||||
if (numfmtInfo) {
|
||||
// If the content is parsed to a number format or the origin cell has a number format, do not need judge whether it will lose precision.
|
||||
// If the cell type is string or force string, do not need judge whether it will lose precision.
|
||||
// If the numeric string will lose precision when converted to a number, set the cell type to force string
|
||||
// e.g. 123456789123456789
|
||||
// e.g. 1212121212121212.2345
|
||||
if (
|
||||
!numfmtInfo.z &&
|
||||
!currentNumfmtValue?.pattern &&
|
||||
originCell?.t !== CellValueType.STRING &&
|
||||
originCell?.t !== CellValueType.FORCE_STRING &&
|
||||
willLoseNumericPrecision(content)
|
||||
) {
|
||||
return next({
|
||||
...value,
|
||||
p: undefined,
|
||||
v: content,
|
||||
t: CellValueType.FORCE_STRING,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Only when the content has number format pattern but the current cell has no pattern, or the pattern type is different, need to update the number format.
|
||||
* Different currency symbols also should not be updated in Excel.
|
||||
*/
|
||||
if (numfmtInfo.z && (!currentNumfmtValue?.pattern || getPatternType(numfmtInfo.z) !== getPatternType(currentNumfmtValue.pattern))) {
|
||||
this._collectEffectMutation.add(
|
||||
context.unitId,
|
||||
context.subUnitId,
|
||||
context.row,
|
||||
context.col,
|
||||
{
|
||||
pattern: numfmtInfo.z,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const v = stripErrorMargin(Number(numfmtInfo.v), 16);
|
||||
|
||||
return next({ ...value, p: undefined, v, t: CellValueType.NUMBER });
|
||||
}
|
||||
|
||||
return next(value);
|
||||
},
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _initInterceptorCommands() {
|
||||
const self = this;
|
||||
this.disposeWithMe(
|
||||
this._sheetInterceptorService.interceptCommand({
|
||||
getMutations(command) {
|
||||
switch (command.id) {
|
||||
case SetRangeValuesCommand.id: {
|
||||
const effects = self._collectEffectMutation.getEffects();
|
||||
self._collectEffectMutation.clean();
|
||||
if (!effects.length) {
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
const target = getSheetCommandTarget(
|
||||
self._univerInstanceService,
|
||||
command.params as ISetRangeValuesMutationParams
|
||||
);
|
||||
if (!target) {
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
}
|
||||
const { unitId, subUnitId } = target;
|
||||
const list = effects.filter((item) => item.unitId === unitId && item.subUnitId === subUnitId);
|
||||
const cells: ISetCellsNumfmt = list
|
||||
.filter((item) => !!item.value?.pattern)
|
||||
.map((item) => ({
|
||||
row: item.row,
|
||||
col: item.col,
|
||||
pattern: item.value!.pattern,
|
||||
}));
|
||||
const removeCells: IRange[] = list
|
||||
.filter((item) => !item.value?.pattern)
|
||||
.map((item) => ({
|
||||
startRow: item.row,
|
||||
endColumn: item.col,
|
||||
startColumn: item.col,
|
||||
endRow: item.row,
|
||||
}));
|
||||
const redos = [];
|
||||
const undos = [];
|
||||
if (cells.length) {
|
||||
const redo = {
|
||||
id: SetNumfmtMutation.id,
|
||||
params: transformCellsToRange(unitId, subUnitId, cells) as ISetNumfmtMutationParams,
|
||||
};
|
||||
redos.push(redo);
|
||||
undos.push(...factorySetNumfmtUndoMutation(self._injector, redo.params));
|
||||
}
|
||||
if (removeCells.length) {
|
||||
const redo = {
|
||||
id: RemoveNumfmtMutation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
ranges: removeCells,
|
||||
} as IRemoveNumfmtMutationParams,
|
||||
};
|
||||
redos.push(redo);
|
||||
undos.push(...factoryRemoveNumfmtUndoMutation(self._injector, redo.params));
|
||||
}
|
||||
return {
|
||||
redos,
|
||||
undos: undos.reverse(),
|
||||
};
|
||||
}
|
||||
}
|
||||
return {
|
||||
redos: [],
|
||||
undos: [],
|
||||
};
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this._collectEffectMutation.clean();
|
||||
}
|
||||
}
|
||||
|
||||
function canConvertRichTextToNumfmt(body: IDocumentBody): boolean {
|
||||
const { textRuns = [], paragraphs = [], customRanges, customBlocks = [] } = body;
|
||||
|
||||
// Some styles are unique to rich text. When this style appears, we consider the value to be rich text.
|
||||
const richTextStyle = ['va'];
|
||||
|
||||
return !(
|
||||
textRuns.some((textRun) => {
|
||||
const hasRichTextStyle = Boolean(textRun.ts && Object.keys(textRun.ts).some((property) => {
|
||||
return richTextStyle.includes(property);
|
||||
}));
|
||||
return hasRichTextStyle;
|
||||
}) ||
|
||||
paragraphs.some((paragraph) => paragraph.bullet) ||
|
||||
paragraphs.length >= 2 ||
|
||||
Boolean(customRanges?.length) ||
|
||||
customBlocks.length > 0
|
||||
);
|
||||
}
|
||||
322
packages/sheets-numfmt-ui/src/controllers/ui.controller.ts
Normal file
322
packages/sheets-numfmt-ui/src/controllers/ui.controller.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 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 { IDisposable, IRange, Workbook } from '@univerjs/core';
|
||||
import type { ISetNumfmtMutationParams } from '@univerjs/sheets';
|
||||
import type { ISetNumfmtCommandParams } from '@univerjs/sheets-numfmt';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import type { ISheetNumfmtPanelProps } from '../views/SheetNumfmtPanel';
|
||||
import {
|
||||
CellValueType,
|
||||
Disposable,
|
||||
DisposableCollection,
|
||||
ICommandService,
|
||||
Inject,
|
||||
InterceptorEffectEnum,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
Range,
|
||||
ThemeService,
|
||||
toDisposable,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import {
|
||||
INTERCEPTOR_POINT,
|
||||
INumfmtService,
|
||||
RemoveNumfmtMutation,
|
||||
SetNumfmtMutation,
|
||||
SheetInterceptorService,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import {
|
||||
getPatternPreviewIgnoreGeneral,
|
||||
getPatternType,
|
||||
SetNumfmtCommand,
|
||||
SheetsNumfmtCellContentController,
|
||||
} from '@univerjs/sheets-numfmt';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { ISidebarService } from '@univerjs/ui';
|
||||
import { combineLatest, merge, Observable } from 'rxjs';
|
||||
import { debounceTime, map, switchMap, tap } from 'rxjs/operators';
|
||||
import { CloseNumfmtPanelOperator } from '../commands/operations/close.numfmt.panel.operation';
|
||||
import { OpenNumfmtPanelOperator } from '../commands/operations/open.numfmt.panel.operation';
|
||||
|
||||
const SHEET_NUMFMT_PANEL = 'SHEET_NUMFMT_PANEL';
|
||||
|
||||
export class SheetNumfmtUIController extends Disposable {
|
||||
/**
|
||||
* If _previewPattern is null ,the realTimeRenderingInterceptor will skip and if it is '',realTimeRenderingInterceptor will clear numfmt.
|
||||
* @private
|
||||
* @type {(string | null)}
|
||||
* @memberof NumfmtController
|
||||
*/
|
||||
private _previewPattern: string | null = '';
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(ThemeService) private _themeService: ThemeService,
|
||||
@IUniverInstanceService private _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
@Inject(SheetsSelectionsService) private _selectionManagerService: SheetsSelectionsService,
|
||||
@IRenderManagerService private _renderManagerService: IRenderManagerService,
|
||||
@INumfmtService private _numfmtService: INumfmtService,
|
||||
@ISidebarService private _sidebarService: ISidebarService,
|
||||
@Inject(LocaleService) private _localeService: LocaleService,
|
||||
@Inject(SheetsNumfmtCellContentController) private _sheetsNumfmtCellContentController: SheetsNumfmtCellContentController
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initRealTimeRenderingInterceptor();
|
||||
this._initCommands();
|
||||
this._initCloseListener();
|
||||
this._commandExecutedListener();
|
||||
this._initNumfmtLocalChange();
|
||||
}
|
||||
|
||||
private _initNumfmtLocalChange() {
|
||||
this.disposeWithMe(merge(this._sheetsNumfmtCellContentController.locale$, this._localeService.currentLocale$).subscribe(() => {
|
||||
this._forceUpdate();
|
||||
}));
|
||||
}
|
||||
|
||||
openPanel(): boolean {
|
||||
const sidebarService = this._sidebarService;
|
||||
const selectionManagerService = this._selectionManagerService;
|
||||
const commandService = this._commandService;
|
||||
const univerInstanceService = this._univerInstanceService;
|
||||
const numfmtService = this._numfmtService;
|
||||
const localeService = this._localeService;
|
||||
|
||||
const selections = selectionManagerService.getCurrentSelections()?.map((s) => s.range) || [];
|
||||
const range = selections[0];
|
||||
|
||||
if (!range) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
const sheet = workbook.getActiveSheet();
|
||||
if (!sheet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const cellValue = sheet.getCellRaw(range.startRow, range.startColumn);
|
||||
const numfmtValue = numfmtService.getValue(
|
||||
workbook.getUnitId(),
|
||||
sheet.getSheetId(),
|
||||
range.startRow,
|
||||
range.startColumn
|
||||
);
|
||||
let pattern = '';
|
||||
if (numfmtValue) {
|
||||
pattern = numfmtValue.pattern;
|
||||
}
|
||||
|
||||
const defaultValue = (cellValue?.t === CellValueType.NUMBER ? cellValue.v : 12345678) as number;
|
||||
|
||||
const props: ISheetNumfmtPanelProps = {
|
||||
onChange: (config) => {
|
||||
if (config.type === 'change') {
|
||||
this._previewPattern = config.value;
|
||||
this._forceUpdate();
|
||||
} else if (config.type === 'confirm') {
|
||||
const selections = selectionManagerService.getCurrentSelections()?.map((s) => s.range) || [];
|
||||
const params: ISetNumfmtCommandParams = { values: [] };
|
||||
const patternType = getPatternType(config.value);
|
||||
selections.forEach((rangeInfo) => {
|
||||
Range.foreach(rangeInfo, (row, col) => {
|
||||
params.values.push({
|
||||
row,
|
||||
col,
|
||||
pattern: config.value,
|
||||
type: patternType,
|
||||
});
|
||||
});
|
||||
});
|
||||
commandService.executeCommand(SetNumfmtCommand.id, params);
|
||||
sidebarService.close();
|
||||
} else if (config.type === 'cancel') {
|
||||
sidebarService.close();
|
||||
}
|
||||
},
|
||||
value: { defaultPattern: pattern, defaultValue, row: range.startRow, col: range.startColumn },
|
||||
};
|
||||
|
||||
this._sidebarDisposable = sidebarService.open({
|
||||
header: {
|
||||
title: localeService.t<LocaleKey>('sheets-numfmt-ui.title'),
|
||||
},
|
||||
children: {
|
||||
label: SHEET_NUMFMT_PANEL,
|
||||
...(props as any), // need passthrough to react props.
|
||||
},
|
||||
onClose: () => {
|
||||
this._forceUpdate();
|
||||
commandService.executeCommand(CloseNumfmtPanelOperator.id);
|
||||
},
|
||||
});
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
private _forceUpdate(unitId?: string): void {
|
||||
const resolvedUnitId = unitId ?? this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)?.getUnitId();
|
||||
if (!resolvedUnitId) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderUnit = this._renderManagerService.getRenderUnitById(resolvedUnitId);
|
||||
|
||||
renderUnit?.with(SheetSkeletonManagerService).reCalculate();
|
||||
renderUnit?.mainComponent?.makeDirty();
|
||||
}
|
||||
|
||||
private _initCommands() {
|
||||
[
|
||||
OpenNumfmtPanelOperator,
|
||||
CloseNumfmtPanelOperator,
|
||||
].forEach((config) => {
|
||||
this.disposeWithMe(this._commandService.registerCommand(config));
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line max-lines-per-function
|
||||
private _initRealTimeRenderingInterceptor() {
|
||||
const isPanelOpenObserver = new Observable<boolean>((subscriber) => {
|
||||
this._commandService.onCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id === OpenNumfmtPanelOperator.id) {
|
||||
subscriber.next(true);
|
||||
}
|
||||
if (commandInfo.id === CloseNumfmtPanelOperator.id) {
|
||||
subscriber.next(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
const combineOpenAndSelection$ = combineLatest([
|
||||
isPanelOpenObserver,
|
||||
this._selectionManagerService.selectionMoveEnd$.pipe(
|
||||
map((selectionInfos) => {
|
||||
if (!selectionInfos) {
|
||||
return [];
|
||||
}
|
||||
return selectionInfos.map((selectionInfo) => selectionInfo.range);
|
||||
})
|
||||
),
|
||||
]);
|
||||
this.disposeWithMe(
|
||||
toDisposable(
|
||||
combineOpenAndSelection$
|
||||
.pipe(
|
||||
switchMap(
|
||||
([isOpen, selectionRanges]) =>
|
||||
new Observable<{
|
||||
disposableCollection: DisposableCollection;
|
||||
selectionRanges: IRange[];
|
||||
}>((subscribe) => {
|
||||
const disposableCollection = new DisposableCollection();
|
||||
isOpen &&
|
||||
selectionRanges.length &&
|
||||
subscribe.next({ selectionRanges, disposableCollection });
|
||||
return () => {
|
||||
disposableCollection.dispose();
|
||||
};
|
||||
})
|
||||
),
|
||||
tap(() => {
|
||||
this._previewPattern = null;
|
||||
})
|
||||
)
|
||||
.subscribe(({ disposableCollection, selectionRanges }) => {
|
||||
const workbook = this._univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
this.openPanel();
|
||||
disposableCollection.add(
|
||||
this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, {
|
||||
priority: 99,
|
||||
effect: InterceptorEffectEnum.Value | InterceptorEffectEnum.Style,
|
||||
handler: (cell, location, next) => {
|
||||
const { row, col } = location;
|
||||
const defaultValue = next(cell) || {};
|
||||
if (
|
||||
selectionRanges.find(
|
||||
(range) =>
|
||||
range.startColumn <= col &&
|
||||
range.endColumn >= col &&
|
||||
range.startRow <= row &&
|
||||
range.endRow >= row
|
||||
)
|
||||
) {
|
||||
const rawValue = location.worksheet.getCellRaw(row, col);
|
||||
const value = rawValue?.v;
|
||||
const type = rawValue?.t;
|
||||
if (
|
||||
value === undefined ||
|
||||
value === null ||
|
||||
type !== CellValueType.NUMBER ||
|
||||
this._previewPattern === null
|
||||
) {
|
||||
return defaultValue;
|
||||
}
|
||||
const info = getPatternPreviewIgnoreGeneral(this._previewPattern, value as number, this._sheetsNumfmtCellContentController.locale);
|
||||
if (info.color) {
|
||||
const color = this._themeService.getColorFromTheme(`${info.color}.500`) ?? info.color;
|
||||
return {
|
||||
...defaultValue,
|
||||
v: info.result,
|
||||
t: CellValueType.STRING,
|
||||
s: { cl: { rgb: color } },
|
||||
};
|
||||
}
|
||||
return {
|
||||
...defaultValue,
|
||||
v: info.result,
|
||||
t: CellValueType.STRING,
|
||||
};
|
||||
}
|
||||
return defaultValue;
|
||||
},
|
||||
})
|
||||
);
|
||||
this._renderManagerService.getRenderUnitById(workbook.getUnitId())?.mainComponent?.makeDirty();
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _commandExecutedListener() {
|
||||
const commandList = [RemoveNumfmtMutation.id, SetNumfmtMutation.id];
|
||||
this.disposeWithMe(new Observable<string>((subscribe) => {
|
||||
const disposable = this._commandService.onCommandExecuted((command) => {
|
||||
if (commandList.includes(command.id)) {
|
||||
const params = command.params as ISetNumfmtMutationParams;
|
||||
subscribe.next(params.unitId);
|
||||
}
|
||||
});
|
||||
return () => disposable.dispose();
|
||||
}).pipe(debounceTime(16))
|
||||
.subscribe((unitId) => this._forceUpdate(unitId))
|
||||
);
|
||||
}
|
||||
|
||||
private _sidebarDisposable: IDisposable | null = null;
|
||||
private _initCloseListener(): void {
|
||||
this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET).subscribe((unit) => {
|
||||
if (!unit) {
|
||||
this._sidebarDisposable?.dispose();
|
||||
this._sidebarDisposable = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* 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 { ILocalStorageService, Inject } from '@univerjs/core';
|
||||
import { createContext } from 'react';
|
||||
|
||||
type HabitValue = string | number;
|
||||
interface IUserHabitController {
|
||||
addHabit(habit: string, initValue: HabitValue[]): Promise<void>;
|
||||
markHabit(habit: string, value: HabitValue): void;
|
||||
deleteHabit(habit: string): void;
|
||||
getHabit(habit: string, sortList?: HabitValue[]): Promise<HabitValue[]>;
|
||||
}
|
||||
|
||||
export const UserHabitCurrencyContext = createContext<string[]>([]);
|
||||
|
||||
export class UserHabitController implements IUserHabitController {
|
||||
constructor(@Inject(ILocalStorageService) private _localStorageService: ILocalStorageService) {
|
||||
// super
|
||||
}
|
||||
|
||||
private _getKey(habit: string) {
|
||||
return `userHabitController_${habit}`;
|
||||
}
|
||||
|
||||
async addHabit<T = unknown[]>(habit: string, initValue: T) {
|
||||
const key = this._getKey(habit);
|
||||
return this._localStorageService.getItem<T>(key).then((item) => {
|
||||
if (!item) {
|
||||
this._localStorageService.setItem(key, initValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
markHabit(habit: string, value: HabitValue) {
|
||||
const key = this._getKey(habit);
|
||||
this._localStorageService.getItem<HabitValue[]>(key).then((list) => {
|
||||
if (list) {
|
||||
const index = list.findIndex((item) => item === value);
|
||||
index > -1 && list.splice(index, 1);
|
||||
list.unshift(value);
|
||||
this._localStorageService.setItem(key, list);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getHabit(habit: string, sortList: HabitValue[]) {
|
||||
const key = this._getKey(habit);
|
||||
const result = await this._localStorageService.getItem<HabitValue[]>(key);
|
||||
if (sortList && result) {
|
||||
const priority = result.map((item, index, arr) => {
|
||||
const length = arr.length;
|
||||
return {
|
||||
value: item,
|
||||
priority: length - index,
|
||||
};
|
||||
});
|
||||
return sortList.sort((a, b) => {
|
||||
const ap = priority.find((item) => item.value === a)?.priority || -1;
|
||||
const bp = priority.find((item) => item.value === b)?.priority || -1;
|
||||
return bp - ap;
|
||||
});
|
||||
}
|
||||
return result || [];
|
||||
}
|
||||
|
||||
deleteHabit(habit: string) {
|
||||
this._localStorageService.removeItem(habit);
|
||||
}
|
||||
}
|
||||
2
packages/sheets-numfmt-ui/src/global.css
Normal file
2
packages/sheets-numfmt-ui/src/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
22
packages/sheets-numfmt-ui/src/index.ts
Normal file
22
packages/sheets-numfmt-ui/src/index.ts
Normal 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 './global.css';
|
||||
|
||||
export type { IUniverSheetsNumfmtUIConfig } from './config/config';
|
||||
export { menuSchema as SheetsNumfmtUIMenuSchema } from './menu/schema';
|
||||
export { UniverSheetsNumfmtMobileUIPlugin } from './mobile-plugin';
|
||||
export { UniverSheetsNumfmtUIPlugin } from './plugin';
|
||||
61
packages/sheets-numfmt-ui/src/locale/ar-SA.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/ar-SA.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'تنسيق الأرقام',
|
||||
numfmtType: 'أنواع التنسيق',
|
||||
cancel: 'إلغاء',
|
||||
confirm: 'تأكيد',
|
||||
general: 'عام',
|
||||
accounting: 'محاسبة',
|
||||
text: 'نص',
|
||||
number: 'رقم',
|
||||
percent: 'نسبة مئوية',
|
||||
scientific: 'علمي',
|
||||
currency: 'عملة',
|
||||
date: 'تاريخ',
|
||||
time: 'وقت',
|
||||
thousandthPercentile: 'فاصل الألوف',
|
||||
dateTime: 'التاريخ والوقت',
|
||||
decimalLength: 'المنازل العشرية',
|
||||
currencyType: 'رمز العملة',
|
||||
moreFmt: 'التنسيقات',
|
||||
financialValue: 'قيمة مالية',
|
||||
roundingCurrency: 'تقريب العملة',
|
||||
timeDuration: 'المدة الزمنية',
|
||||
currencyDes: 'يُستخدم تنسيق العملة لتمثيل قيم العملة العامة. يقوم تنسيق المحاسبة بمحاذاة عمود من القيم مع النقاط العشرية',
|
||||
accountingDes: 'يقوم تنسيق الأرقام المحاسبي بمحاذاة عمود من القيم مع رموز العملة والنقاط العشرية',
|
||||
dateType: 'نوع التاريخ',
|
||||
dateDes: 'يعرض تنسيق التاريخ قيم سلسلة التاريخ والوقت كقيم تاريخ.',
|
||||
negType: 'نوع العدد السالب',
|
||||
generalDes: 'لا يحتوي التنسيق العام على أي تنسيق رقم محدد.',
|
||||
thousandthPercentileDes: 'يُستخدم تنسيق النسبة المئوية لتمثيل الأرقام العادية. توفر تنسيقات العملة والمحاسبة تنسيقًا متخصصًا لحسابات قيم العملة.',
|
||||
addDecimal: 'زيادة المنازل العشرية',
|
||||
subtractDecimal: 'تقليل المنازل العشرية',
|
||||
customFormat: 'تنسيق مخصص',
|
||||
customFormatDes: 'إنشاء تنسيقات أرقام مخصصة استنادًا إلى التنسيقات الموجودة.',
|
||||
info: {
|
||||
error: 'خطأ',
|
||||
forceStringInfo: 'رقم مخزن كنص',
|
||||
convertToNumber: 'تحويل إلى رقم',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/ca-ES.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/ca-ES.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Format de nombre',
|
||||
numfmtType: 'Tipus de format',
|
||||
cancel: 'Cancel·lar',
|
||||
confirm: 'Confirmar',
|
||||
general: 'General',
|
||||
accounting: 'Comptabilitat',
|
||||
text: 'Text',
|
||||
number: 'Nombre',
|
||||
percent: 'Percentatge',
|
||||
scientific: 'Científic',
|
||||
currency: 'Moneda',
|
||||
date: 'Data',
|
||||
time: 'Hora',
|
||||
thousandthPercentile: 'Separador de milers',
|
||||
dateTime: 'Data i hora',
|
||||
decimalLength: 'Decimals',
|
||||
currencyType: 'Símbol de moneda',
|
||||
moreFmt: 'Formats',
|
||||
financialValue: 'Valor financer',
|
||||
roundingCurrency: 'Arrodonir la moneda',
|
||||
timeDuration: 'Durada',
|
||||
currencyDes: 'El format de moneda s\'utilitza per representar valors monetaris generals. El format de comptabilitat alinea una columna de valors amb els punts decimals.',
|
||||
accountingDes: 'El format de nombre de comptabilitat alinea una columna de valors amb símbols de moneda i punts decimals.',
|
||||
dateType: 'Tipus de data',
|
||||
dateDes: 'El format de data presenta valors de sèries de data i hora com a valors de data.',
|
||||
negType: 'Tipus de nombre negatiu',
|
||||
generalDes: 'El format regular no conté cap format de nombre específic.',
|
||||
thousandthPercentileDes: 'El format de separador de milers s\'utilitza per a la representació de nombres ordinaris. Els formats monetaris i de comptabilitat proporcionen un format especialitzat per a càlculs de valors monetaris.',
|
||||
addDecimal: 'Augmentar decimals',
|
||||
subtractDecimal: 'Disminuir decimals',
|
||||
customFormat: 'Format personalitzat',
|
||||
customFormatDes: 'Genera formats de nombre personalitzats basats en els existents.',
|
||||
info: {
|
||||
error: 'Error',
|
||||
forceStringInfo: 'Número emmagatzemat com a text',
|
||||
convertToNumber: 'Converteix en número',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/de-DE.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/de-DE.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Zahlenformat',
|
||||
numfmtType: 'Formattypen',
|
||||
cancel: 'Abbrechen',
|
||||
confirm: 'Bestätigen',
|
||||
general: 'Allgemein',
|
||||
accounting: 'Buchhaltung',
|
||||
text: 'Text',
|
||||
number: 'Zahl',
|
||||
percent: 'Prozent',
|
||||
scientific: 'Wissenschaftlich',
|
||||
currency: 'Währung',
|
||||
date: 'Datum',
|
||||
time: 'Zeit',
|
||||
thousandthPercentile: 'Tausendertrennzeichen',
|
||||
dateTime: 'Datum und Uhrzeit',
|
||||
decimalLength: 'Dezimalstellen',
|
||||
currencyType: 'Währungssymbol',
|
||||
moreFmt: 'Formate',
|
||||
financialValue: 'Finanzwert',
|
||||
roundingCurrency: 'Währung aufrunden',
|
||||
timeDuration: 'Dauer',
|
||||
currencyDes: 'Das Währungsformat wird verwendet, um allgemeine Währungswerte darzustellen. Das Buchhaltungsformat richtet eine Spalte von Werten an Dezimalpunkten aus',
|
||||
accountingDes: 'Das Buchhaltungszahlenformat richtet eine Spalte von Werten an Währungssymbolen und Dezimalpunkten aus',
|
||||
dateType: 'Datumstyp',
|
||||
dateDes: 'Das Datumsformat stellt Datums- und Zeitserienwerte als Datumswerte dar.',
|
||||
negType: 'Negativer Zahlentyp',
|
||||
generalDes: 'Das allgemeine Format enthält kein spezifisches Zahlenformat.',
|
||||
thousandthPercentileDes: 'Das Tausendertrennzeichenformat wird für die Darstellung gewöhnlicher Zahlen verwendet. Währungs- und Buchhaltungsformate bieten ein spezialisiertes Format für Währungswertberechnungen.',
|
||||
addDecimal: 'Dezimalstellen erhöhen',
|
||||
subtractDecimal: 'Dezimalstellen verringern',
|
||||
customFormat: 'Benutzerdefiniertes Format',
|
||||
customFormatDes: 'Benutzerdefinierte Zahlenformate basierend auf vorhandenen Formaten erstellen.',
|
||||
info: {
|
||||
error: 'Fehler',
|
||||
forceStringInfo: 'Zahl als Text gespeichert',
|
||||
convertToNumber: 'In Zahl konvertieren',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
59
packages/sheets-numfmt-ui/src/locale/en-US.ts
Normal file
59
packages/sheets-numfmt-ui/src/locale/en-US.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Number format',
|
||||
numfmtType: 'Format types',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
general: 'General',
|
||||
accounting: 'Accounting',
|
||||
text: 'Text',
|
||||
number: 'Number',
|
||||
percent: 'Percentage',
|
||||
scientific: 'Scientific',
|
||||
currency: 'Currency',
|
||||
date: 'Date',
|
||||
time: 'Time',
|
||||
thousandthPercentile: 'Thousands separator',
|
||||
dateTime: 'Date and time',
|
||||
decimalLength: 'Decimal places',
|
||||
currencyType: 'Currency Symbol',
|
||||
moreFmt: 'Formats',
|
||||
financialValue: 'Financial value',
|
||||
roundingCurrency: 'Rounding up the currency',
|
||||
timeDuration: 'Duration Time',
|
||||
currencyDes: 'The currency format is used to represent general currency values. The accounting format aligns a column of values with decimal points',
|
||||
accountingDes: 'The accounting number format aligns a column of values with currency symbols and decimal points',
|
||||
dateType: 'Date Type',
|
||||
dateDes: 'The date format presents date and time series values as date values.',
|
||||
negType: 'A negative number type',
|
||||
generalDes: 'The regular format does not contain any specific number format.',
|
||||
thousandthPercentileDes: 'The percentile format is used for the representation of ordinary numbers. Monetary and accounting formats provide a specialized format for monetary value calculations.',
|
||||
addDecimal: 'Increase decimal places',
|
||||
subtractDecimal: 'Decreasing decimal places',
|
||||
customFormat: 'Custom Format',
|
||||
customFormatDes: 'Generate custom number formats based on existing formats.',
|
||||
info: {
|
||||
error: 'Error',
|
||||
forceStringInfo: 'Number stored as text',
|
||||
convertToNumber: 'Convert to Number',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/es-ES.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/es-ES.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Formato de número',
|
||||
numfmtType: 'Tipos de formato',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
general: 'General',
|
||||
accounting: 'Contabilidad',
|
||||
text: 'Texto',
|
||||
number: 'Número',
|
||||
percent: 'Porcentaje',
|
||||
scientific: 'Científico',
|
||||
currency: 'Moneda',
|
||||
date: 'Fecha',
|
||||
time: 'Hora',
|
||||
thousandthPercentile: 'Separador de miles',
|
||||
dateTime: 'Fecha y hora',
|
||||
decimalLength: 'Decimales',
|
||||
currencyType: 'Símbolo de moneda',
|
||||
moreFmt: 'Formatos',
|
||||
financialValue: 'Valor financiero',
|
||||
roundingCurrency: 'Redondear la moneda',
|
||||
timeDuration: 'Duración',
|
||||
currencyDes: 'El formato de moneda se utiliza para representar valores monetarios generales. El formato de contabilidad alinea una columna de valores con los puntos decimales.',
|
||||
accountingDes: 'El formato de número de contabilidad alinea una columna de valores con símbolos de moneda y puntos decimales.',
|
||||
dateType: 'Tipo de fecha',
|
||||
dateDes: 'El formato de fecha presenta valores de series de fecha y hora como valores de fecha.',
|
||||
negType: 'Tipo de número negativo',
|
||||
generalDes: 'El formato regular no contiene ningún formato de número específico.',
|
||||
thousandthPercentileDes: 'El formato de separador de miles se utiliza para la representación de números ordinarios. Los formatos monetarios y de contabilidad proporcionan un formato especializado para cálculos de valores monetarios.',
|
||||
addDecimal: 'Aumentar decimales',
|
||||
subtractDecimal: 'Disminuir decimales',
|
||||
customFormat: 'Formato personalizado',
|
||||
customFormatDes: 'Generar formatos de número personalizados basados en los existentes.',
|
||||
info: {
|
||||
error: 'Error',
|
||||
forceStringInfo: 'Número almacenado como texto',
|
||||
convertToNumber: 'Convertir en número',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/fa-IR.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/fa-IR.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'قالببندی عدد',
|
||||
numfmtType: 'انواع قالببندی',
|
||||
cancel: 'انصراف',
|
||||
confirm: 'تایید',
|
||||
general: 'عمومی',
|
||||
accounting: 'حسابداری',
|
||||
text: 'متن',
|
||||
number: 'عدد',
|
||||
percent: 'درصد',
|
||||
scientific: 'علمی',
|
||||
currency: 'ارز',
|
||||
date: 'تاریخ',
|
||||
time: 'زمان',
|
||||
thousandthPercentile: 'جداساز هزارتایی',
|
||||
dateTime: 'تاریخ و زمان',
|
||||
decimalLength: 'اعشار',
|
||||
currencyType: 'نماد ارز',
|
||||
moreFmt: 'قالبها',
|
||||
financialValue: 'مقدار مالی',
|
||||
roundingCurrency: 'گرد کردن ارز',
|
||||
timeDuration: 'مدت زمان',
|
||||
currencyDes: 'قالببندی ارز برای نمایش مقادیر عمومی ارز استفاده میشود. قالببندی حسابداری ستونی از مقادیر را با اعشار تراز میکند.',
|
||||
accountingDes: 'قالببندی عددی حسابداری ستونی از مقادیر را با نمادهای ارز و اعشار تراز میکند.',
|
||||
dateType: 'نوع تاریخ',
|
||||
dateDes: 'قالببندی تاریخ مقادیر سری تاریخ و زمان را به عنوان مقادیر تاریخ ارائه میدهد.',
|
||||
negType: 'نوع عدد منفی',
|
||||
generalDes: 'قالببندی معمولی حاوی هیچ قالببندی عدد خاصی نیست.',
|
||||
thousandthPercentileDes: 'قالببندی درصدی برای نمایش اعداد معمولی استفاده میشود. قالببندیهای پولی و حسابداری قالب تخصصی برای محاسبات مقدار پولی ارائه میدهند.',
|
||||
addDecimal: 'افزایش اعشار',
|
||||
subtractDecimal: 'کاهش اعشار',
|
||||
customFormat: 'قالببندی سفارشی',
|
||||
customFormatDes: 'ایجاد قالببندیهای عددی سفارشی بر اساس قالببندیهای موجود.',
|
||||
info: {
|
||||
error: 'خطا',
|
||||
forceStringInfo: 'عدد به عنوان متن ذخیره شده',
|
||||
convertToNumber: 'تبدیل به عدد',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/fr-FR.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/fr-FR.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Format de nombre',
|
||||
numfmtType: 'Types de format',
|
||||
cancel: 'Annuler',
|
||||
confirm: 'Confirmer',
|
||||
general: 'Général',
|
||||
accounting: 'Comptabilité',
|
||||
text: 'Texte',
|
||||
number: 'Nombre',
|
||||
percent: 'Pourcentage',
|
||||
scientific: 'Scientifique',
|
||||
currency: 'Devise',
|
||||
date: 'Date',
|
||||
time: 'Heure',
|
||||
thousandthPercentile: 'Séparateur de milliers',
|
||||
dateTime: 'Date et heure',
|
||||
decimalLength: 'Nombre de décimales',
|
||||
currencyType: 'Symbole de devise',
|
||||
moreFmt: 'Formats',
|
||||
financialValue: 'Valeur financière',
|
||||
roundingCurrency: 'Arrondir la devise',
|
||||
timeDuration: 'Durée',
|
||||
currencyDes: 'Le format de devise est utilisé pour représenter des valeurs monétaires générales. Le format de comptabilité aligne une colonne de valeurs avec des points décimaux',
|
||||
accountingDes: 'Le format de nombre de comptabilité aligne une colonne de valeurs avec des symboles de devise et des points décimaux',
|
||||
dateType: 'Type de date',
|
||||
dateDes: 'Le format de date présente les valeurs de la série date et heure sous forme de valeurs de date.',
|
||||
negType: 'Type de nombre négatif',
|
||||
generalDes: 'Le format général ne contient aucun format de nombre spécifique.',
|
||||
thousandthPercentileDes: 'Le format de percentile est utilisé pour la représentation des nombres ordinaires. Les formats monétaires et comptables fournissent un format spécialisé pour les calculs de valeurs monétaires.',
|
||||
addDecimal: 'Augmenter les décimales',
|
||||
subtractDecimal: 'Diminuer les décimales',
|
||||
customFormat: 'Format personnalisé',
|
||||
customFormatDes: 'Générer des formats de nombre personnalisés basés sur des formats existants.',
|
||||
info: {
|
||||
error: 'Erreur',
|
||||
forceStringInfo: 'Nombre stocké en tant que texte',
|
||||
convertToNumber: 'Convertir en nombre',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/id-ID.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/id-ID.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Format angka',
|
||||
numfmtType: 'Jenis format',
|
||||
cancel: 'Batal',
|
||||
confirm: 'Konfirmasi',
|
||||
general: 'Umum',
|
||||
accounting: 'Akuntansi',
|
||||
text: 'Teks',
|
||||
number: 'Angka',
|
||||
percent: 'Persentase',
|
||||
scientific: 'Ilmiah',
|
||||
currency: 'Mata Uang',
|
||||
date: 'Tanggal',
|
||||
time: 'Waktu',
|
||||
thousandthPercentile: 'Pemisah ribuan',
|
||||
dateTime: 'Tanggal dan waktu',
|
||||
decimalLength: 'Tempat desimal',
|
||||
currencyType: 'Simbol Mata Uang',
|
||||
moreFmt: 'Format',
|
||||
financialValue: 'Nilai keuangan',
|
||||
roundingCurrency: 'Pembulatan mata uang',
|
||||
timeDuration: 'Durasi Waktu',
|
||||
currencyDes: 'Format mata uang digunakan untuk mewakili nilai mata uang umum. Format akuntansi menyelaraskan kolom nilai dengan titik desimal',
|
||||
accountingDes: 'Format angka akuntansi menyelaraskan kolom nilai dengan simbol mata uang dan titik desimal',
|
||||
dateType: 'Jenis Tanggal',
|
||||
dateDes: 'Format tanggal menampilkan nilai seri tanggal dan waktu sebagai nilai tanggal.',
|
||||
negType: 'Jenis angka negatif',
|
||||
generalDes: 'Format reguler tidak berisi format angka tertentu.',
|
||||
thousandthPercentileDes: 'Format persentil digunakan untuk representasi angka biasa. Format moneter dan akuntansi menyediakan format khusus untuk perhitungan nilai moneter.',
|
||||
addDecimal: 'Tambah tempat desimal',
|
||||
subtractDecimal: 'Kurangi tempat desimal',
|
||||
customFormat: 'Format Kustom',
|
||||
customFormatDes: 'Hasilkan format angka kustom berdasarkan format yang ada.',
|
||||
info: {
|
||||
error: 'Kesalahan',
|
||||
forceStringInfo: 'Angka disimpan sebagai teks',
|
||||
convertToNumber: 'Konversi ke angka',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/it-IT.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/it-IT.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Formato numero',
|
||||
numfmtType: 'Tipi di formato',
|
||||
cancel: 'Annulla',
|
||||
confirm: 'Conferma',
|
||||
general: 'Generale',
|
||||
accounting: 'Contabilità',
|
||||
text: 'Testo',
|
||||
number: 'Numero',
|
||||
percent: 'Percentuale',
|
||||
scientific: 'Scientifico',
|
||||
currency: 'Valuta',
|
||||
date: 'Data',
|
||||
time: 'Ora',
|
||||
thousandthPercentile: 'Separatore delle migliaia',
|
||||
dateTime: 'Data e ora',
|
||||
decimalLength: 'Cifre decimali',
|
||||
currencyType: 'Simbolo valuta',
|
||||
moreFmt: 'Formati',
|
||||
financialValue: 'Valore finanziario',
|
||||
roundingCurrency: 'Arrotondamento valuta',
|
||||
timeDuration: 'Durata',
|
||||
currencyDes: 'Il formato valuta è utilizzato per rappresentare valori monetari generali. Il formato contabilità allinea una colonna di valori con i punti decimali',
|
||||
accountingDes: 'Il formato numerico contabilità allinea una colonna di valori con i simboli di valuta e i punti decimali',
|
||||
dateType: 'Tipo data',
|
||||
dateDes: 'Il formato data presenta i valori di serie data e ora come valori di data.',
|
||||
negType: 'Tipo numero negativo',
|
||||
generalDes: 'Il formato generale non contiene alcun formato numerico specifico.',
|
||||
thousandthPercentileDes: 'Il formato percentuale è utilizzato per la rappresentazione di numeri ordinari. I formati monetari e contabilità forniscono un formato specializzato per i calcoli di valore monetario.',
|
||||
addDecimal: 'Aumenta cifre decimali',
|
||||
subtractDecimal: 'Diminuisci cifre decimali',
|
||||
customFormat: 'Formato personalizzato',
|
||||
customFormatDes: 'Genera formati numerici personalizzati basati sui formati esistenti.',
|
||||
info: {
|
||||
error: 'Errore',
|
||||
forceStringInfo: 'Numero memorizzato come testo',
|
||||
convertToNumber: 'Converti in numero',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/ja-JP.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/ja-JP.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: '表示形式',
|
||||
numfmtType: '形式の種類',
|
||||
cancel: 'キャンセル',
|
||||
confirm: '確認',
|
||||
general: '標準',
|
||||
accounting: '会計',
|
||||
text: '文字列',
|
||||
number: '数値',
|
||||
percent: 'パーセンテージ',
|
||||
scientific: '指数表示',
|
||||
currency: '通貨',
|
||||
date: '日付',
|
||||
time: '時刻',
|
||||
thousandthPercentile: '桁区切り',
|
||||
dateTime: '日付と時刻',
|
||||
decimalLength: '小数点以下の桁数',
|
||||
currencyType: '通貨記号',
|
||||
moreFmt: 'その他の表示形式',
|
||||
financialValue: '財務値',
|
||||
roundingCurrency: '通貨の丸め',
|
||||
timeDuration: '時間の長さ',
|
||||
currencyDes: '通貨表示形式は一般的な通貨の値を表示するために使用します。会計表示形式は小数点で値を揃えます。',
|
||||
accountingDes: '会計表示形式は通貨記号と小数点で値を揃えます。',
|
||||
dateType: '日付の種類',
|
||||
dateDes: '日付表示形式は日付や時刻の値を所定の日付書式で表示します。',
|
||||
negType: '負数の表示形式',
|
||||
generalDes: '標準表示形式は特定の数値表示形式を適用しません。',
|
||||
thousandthPercentileDes: 'パーセンテージ表示形式は通常の数値をパーセンテージとして表示します。通貨や会計は金額表示に特化しています。',
|
||||
addDecimal: '小数点以下の桁数を増やす',
|
||||
subtractDecimal: '小数点以下の桁数を減らす',
|
||||
customFormat: 'ユーザー定義表示形式',
|
||||
customFormatDes: '既存の表示形式を基にユーザー定義の数値表示形式を作成します。',
|
||||
info: {
|
||||
error: 'エラー',
|
||||
forceStringInfo: '数値がテキストとして保存されました',
|
||||
convertToNumber: '数値に変換',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/ko-KR.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/ko-KR.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: '숫자 서식',
|
||||
numfmtType: '서식 종류',
|
||||
cancel: '취소',
|
||||
confirm: '확인',
|
||||
general: '일반',
|
||||
accounting: '회계',
|
||||
text: '텍스트',
|
||||
number: '숫자',
|
||||
percent: '백분율',
|
||||
scientific: '과학적 표기',
|
||||
currency: '통화',
|
||||
date: '날짜',
|
||||
time: '시간',
|
||||
thousandthPercentile: '천 단위 구분기호',
|
||||
dateTime: '날짜 및 시간',
|
||||
decimalLength: '소수점 자리수',
|
||||
currencyType: '통화 기호',
|
||||
moreFmt: '기타 서식',
|
||||
financialValue: '재무 값',
|
||||
roundingCurrency: '통화 반올림',
|
||||
timeDuration: '시간 길이',
|
||||
currencyDes: '통화 서식은 일반적인 통화 값을 표시하는 데 사용됩니다. 회계 서식은 소수점에 맞춰 값을 정렬합니다.',
|
||||
accountingDes: '회계 숫자 서식은 통화 기호와 소수점에 맞춰 값을 정렬합니다.',
|
||||
dateType: '날짜 유형',
|
||||
dateDes: '날짜 서식은 날짜 및 시간 값을 날짜 형식으로 표시합니다.',
|
||||
negType: '음수 표시 형식',
|
||||
generalDes: '일반 서식은 특정 숫자 서식이 적용되지 않습니다.',
|
||||
thousandthPercentileDes: '백분율 서식은 일반 숫자를 표시할 때 사용됩니다. 통화 및 회계 서식은 금액 계산에 특화되어 있습니다.',
|
||||
addDecimal: '소수점 자리수 늘리기',
|
||||
subtractDecimal: '소수점 자리수 줄이기',
|
||||
customFormat: '사용자 지정 서식',
|
||||
customFormatDes: '기존 서식을 기반으로 사용자 지정 숫자 서식을 만듭니다.',
|
||||
info: {
|
||||
error: '오류',
|
||||
forceStringInfo: '숫자가 텍스트로 저장되었습니다',
|
||||
convertToNumber: '숫자로 변환',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/pl-PL.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/pl-PL.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Format liczb',
|
||||
numfmtType: 'Typy formatów',
|
||||
cancel: 'Anuluj',
|
||||
confirm: 'Potwierdź',
|
||||
general: 'Ogólny',
|
||||
accounting: 'Księgowy',
|
||||
text: 'Tekstowy',
|
||||
number: 'Liczbowy',
|
||||
percent: 'Procentowy',
|
||||
scientific: 'Naukowy',
|
||||
currency: 'Walutowy',
|
||||
date: 'Data',
|
||||
time: 'Czas',
|
||||
thousandthPercentile: 'Separator tysięcy',
|
||||
dateTime: 'Data i czas',
|
||||
decimalLength: 'Miejsca dziesiętne',
|
||||
currencyType: 'Symbol waluty',
|
||||
moreFmt: 'Formaty',
|
||||
financialValue: 'Wartość finansowa',
|
||||
roundingCurrency: 'Zaokrąglenie waluty w górę',
|
||||
timeDuration: 'Czas trwania',
|
||||
currencyDes: 'Format walutowy służy do przedstawiania ogólnych wartości walutowych. Format księgowy wyrównuje kolumnę wartości względem miejsc dziesiętnych.',
|
||||
accountingDes: 'Format liczbowy księgowy wyrównuje kolumnę wartości względem symboli walut i miejsc dziesiętnych.',
|
||||
dateType: 'Typ daty',
|
||||
dateDes: 'Format daty przedstawia wartości szeregów dat i czasu jako wartości dat.',
|
||||
negType: 'Typ liczby ujemnej',
|
||||
generalDes: 'Format ogólny nie zawiera żadnego określonego formatu liczbowego.',
|
||||
thousandthPercentileDes: 'Format percentylowy służy do przedstawiania zwykłych liczb. Formaty walutowe i księgowe zapewniają specjalistyczny format do obliczeń wartości pieniężnych.',
|
||||
addDecimal: 'Zwiększ liczbę miejsc dziesiętnych',
|
||||
subtractDecimal: 'Zmniejsz liczbę miejsc dziesiętnych',
|
||||
customFormat: 'Format niestandardowy',
|
||||
customFormatDes: 'Generuj niestandardowe formaty liczb na podstawie istniejących formatów.',
|
||||
info: {
|
||||
error: 'Błąd',
|
||||
forceStringInfo: 'Liczba przechowywana jako tekst',
|
||||
convertToNumber: 'Konwertuj na liczbę',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/pt-BR.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/pt-BR.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Formato de número',
|
||||
numfmtType: 'Tipos de formato',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
general: 'Geral',
|
||||
accounting: 'Contábil',
|
||||
text: 'Texto',
|
||||
number: 'Número',
|
||||
percent: 'Porcentagem',
|
||||
scientific: 'Científico',
|
||||
currency: 'Moeda',
|
||||
date: 'Data',
|
||||
time: 'Hora',
|
||||
thousandthPercentile: 'Separador de milhares',
|
||||
dateTime: 'Data e hora',
|
||||
decimalLength: 'Casas decimais',
|
||||
currencyType: 'Símbolo da moeda',
|
||||
moreFmt: 'Formatos',
|
||||
financialValue: 'Valor financeiro',
|
||||
roundingCurrency: 'Arredondamento da moeda',
|
||||
timeDuration: 'Duração do tempo',
|
||||
currencyDes: 'O formato de moeda é usado para representar valores monetários gerais. O formato contábil alinha uma coluna de valores com pontos decimais',
|
||||
accountingDes: 'O formato de número contábil alinha uma coluna de valores com símbolos de moeda e pontos decimais',
|
||||
dateType: 'Tipo de data',
|
||||
dateDes: 'O formato de data apresenta valores de séries de data e hora como valores de data.',
|
||||
negType: 'Um tipo de número negativo',
|
||||
generalDes: 'O formato regular não contém nenhum formato de número específico.',
|
||||
thousandthPercentileDes: 'O formato percentil é usado para a representação de números comuns. Os formatos monetário e contábil fornecem um formato especializado para cálculos de valores monetários.',
|
||||
addDecimal: 'Aumentar casas decimais',
|
||||
subtractDecimal: 'Diminuir casas decimais',
|
||||
customFormat: 'Formato Personalizado',
|
||||
customFormatDes: 'Gere formatos de número personalizados com base em formatos existentes.',
|
||||
info: {
|
||||
error: 'Erro',
|
||||
forceStringInfo: 'Número armazenado como texto',
|
||||
convertToNumber: 'Converter em número',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/ru-RU.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/ru-RU.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Формат числа',
|
||||
numfmtType: 'Типы форматов',
|
||||
cancel: 'Отмена',
|
||||
confirm: 'Подтвердить',
|
||||
general: 'Общий',
|
||||
accounting: 'Бухгалтерский',
|
||||
text: 'Текст',
|
||||
number: 'Число',
|
||||
percent: 'Процент',
|
||||
scientific: 'Экспоненциальный',
|
||||
currency: 'Валюта',
|
||||
date: 'Дата',
|
||||
time: 'Время',
|
||||
thousandthPercentile: 'Разделитель тысяч',
|
||||
dateTime: 'Дата и время',
|
||||
decimalLength: 'Десятичные знаки',
|
||||
currencyType: 'Символ валюты',
|
||||
moreFmt: 'Другие форматы',
|
||||
financialValue: 'Финансовое значение',
|
||||
roundingCurrency: 'Округление валюты',
|
||||
timeDuration: 'Продолжительность времени',
|
||||
currencyDes: 'Формат валюты используется для представления общих значений валюты. Формат бухгалтерского учета выравнивает столбец значений по десятичным точкам.',
|
||||
accountingDes: 'Формат бухгалтерских чисел выравнивает столбец значений по символам валюты и десятичным точкам.',
|
||||
dateType: 'Тип даты',
|
||||
dateDes: 'Формат даты представляет значения времени и даты как значения даты.',
|
||||
negType: 'Тип отрицательного числа',
|
||||
generalDes: 'Обычный формат не содержит никакого специфического формата числа.',
|
||||
thousandthPercentileDes: 'Формат процента используется для представления обычных чисел. Монетарные и бухгалтерские форматы предоставляют специальный формат для вычислений монетарных значений.',
|
||||
addDecimal: 'Увеличить количество десятичных знаков',
|
||||
subtractDecimal: 'Уменьшить количество десятичных знаков',
|
||||
customFormat: 'Пользовательский формат',
|
||||
customFormatDes: 'Генерация пользовательских числовых форматов на основе существующих форматов.',
|
||||
info: {
|
||||
error: 'Ошибка',
|
||||
forceStringInfo: 'Число хранится как текст',
|
||||
convertToNumber: 'Преобразовать в число',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/sk-SK.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/sk-SK.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Formát čísla',
|
||||
numfmtType: 'Typy formátov',
|
||||
cancel: 'Zrušiť',
|
||||
confirm: 'Potvrdiť',
|
||||
general: 'Všeobecný',
|
||||
accounting: 'Účtovnícky',
|
||||
text: 'Text',
|
||||
number: 'Číslo',
|
||||
percent: 'Percento',
|
||||
scientific: 'Vedecký',
|
||||
currency: 'Mena',
|
||||
date: 'Dátum',
|
||||
time: 'Čas',
|
||||
thousandthPercentile: 'Oddeľovač tisícov',
|
||||
dateTime: 'Dátum a čas',
|
||||
decimalLength: 'Počet desatinných miest',
|
||||
currencyType: 'Symbol meny',
|
||||
moreFmt: 'Formáty',
|
||||
financialValue: 'Finančná hodnota',
|
||||
roundingCurrency: 'Zaokrúhľovanie meny',
|
||||
timeDuration: 'Trvanie',
|
||||
currencyDes: 'Formát meny sa používa na zobrazenie bežných menových hodnôt. Účtovnícky formát zarovná stĺpec hodnôt podľa desatinných miest.',
|
||||
accountingDes: 'Účtovnícky formát čísiel zarovná stĺpec hodnôt so symbolmi meny a desatinnými miestami.',
|
||||
dateType: 'Typ dátumu',
|
||||
dateDes: 'Formát dátumu zobrazuje hodnoty dátumu a času ako dátumy.',
|
||||
negType: 'Typ záporného čísla',
|
||||
generalDes: 'Všeobecný formát neobsahuje žiadny špecifický formát čísla.',
|
||||
thousandthPercentileDes: 'Percentilný formát sa používa na reprezentáciu bežných čísel. Menové a účtovnícke formáty poskytujú špecializovaný formát pre výpočty menových hodnôt.',
|
||||
addDecimal: 'Zvýšiť počet desatinných miest',
|
||||
subtractDecimal: 'Znížiť počet desatinných miest',
|
||||
customFormat: 'Vlastný formát',
|
||||
customFormatDes: 'Vytvorte vlastné formáty čísiel na základe existujúcich formátov.',
|
||||
info: {
|
||||
error: 'Chyba',
|
||||
forceStringInfo: 'Číslo uložené ako text',
|
||||
convertToNumber: 'Konvertovať na číslo',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
20
packages/sheets-numfmt-ui/src/locale/types.ts
Normal file
20
packages/sheets-numfmt-ui/src/locale/types.ts
Normal 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>;
|
||||
61
packages/sheets-numfmt-ui/src/locale/vi-VN.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/vi-VN.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: 'Định dạng số',
|
||||
numfmtType: 'Loại định dạng',
|
||||
cancel: 'Hủy bỏ',
|
||||
confirm: 'Xác nhận',
|
||||
general: 'Chung',
|
||||
accounting: 'Kế toán',
|
||||
text: 'Văn bản',
|
||||
number: 'Số',
|
||||
percent: 'Phần trăm',
|
||||
scientific: 'Chính xác',
|
||||
currency: 'Tiền tệ',
|
||||
date: 'Ngày',
|
||||
time: 'Thời gian',
|
||||
thousandthPercentile: 'Phân vị phần nghìn',
|
||||
dateTime: 'Ngày giờ',
|
||||
decimalLength: 'Số chữ số thập phân',
|
||||
currencyType: 'Loại tiền tệ',
|
||||
moreFmt: 'Nhiều định dạng hơn',
|
||||
financialValue: 'Giá trị tài chính',
|
||||
roundingCurrency: 'Tiền tệ làm tròn',
|
||||
timeDuration: 'Thời lượng',
|
||||
currencyDes: 'Định dạng tiền tệ được sử dụng để biểu thị các giá trị tiền tệ thông thường. Định dạng kế toán có thể căn chỉnh các giá trị trong một cột với dấu thập phân.',
|
||||
accountingDes: 'Định dạng số kế toán có thể căn chỉnh các ký hiệu tiền tệ và dấu thập phân trong một cột các giá trị.',
|
||||
dateType: 'Loại ngày',
|
||||
dateDes: 'Định dạng ngày biểu thị các giá trị chuỗi ngày và thời gian dưới dạng giá trị ngày.',
|
||||
negType: 'Loại số âm',
|
||||
generalDes: 'Định dạng chung không chứa bất kỳ định dạng số cụ thể nào.',
|
||||
thousandthPercentileDes: 'Định dạng phân vị phần nghìn được sử dụng để biểu thị các số thông thường. Các định dạng tiền tệ và kế toán cung cấp các định dạng chuyên dụng để tính toán giá trị tiền tệ.',
|
||||
addDecimal: 'Thêm chữ số thập phân',
|
||||
subtractDecimal: 'Giảm chữ số thập phân',
|
||||
customFormat: 'Định dạng tùy chỉnh',
|
||||
customFormatDes: 'Tạo định dạng số tùy chỉnh dựa trên các định dạng hiện có.',
|
||||
info: {
|
||||
error: 'Lỗi',
|
||||
forceStringInfo: 'Số được lưu trữ dưới dạng văn bản',
|
||||
convertToNumber: 'Chuyển đổi thành số',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/zh-CN.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/zh-CN.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: '数字格式',
|
||||
numfmtType: '格式类型',
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
general: '常规',
|
||||
accounting: '会计',
|
||||
text: '文本',
|
||||
number: '数值',
|
||||
percent: '百分比',
|
||||
scientific: '科学记数',
|
||||
currency: '货币',
|
||||
date: '日期',
|
||||
time: '时间',
|
||||
thousandthPercentile: '千分位符',
|
||||
dateTime: '日期时间',
|
||||
decimalLength: '小数位数',
|
||||
currencyType: '货币类型',
|
||||
moreFmt: '更多格式',
|
||||
financialValue: '财务数值',
|
||||
roundingCurrency: '货币取整',
|
||||
timeDuration: '持续时间',
|
||||
currencyDes: '货币格式用于表示一般货币数值。会计格式可以对一列数值进行小数点对齐',
|
||||
accountingDes: '会计数字格式可对一列数值进行货币符号和小数点对齐',
|
||||
dateType: '日期类型',
|
||||
dateDes: '日期格式将日期和时间系列数值品示为日期值。',
|
||||
negType: '负数类型',
|
||||
generalDes: '常规格式不包含任何特定的数字格式。',
|
||||
thousandthPercentileDes: '千分位符格式用于一般数字的表示。货币和会计格式则提供货币值计算的专用格式。',
|
||||
addDecimal: '增加小数位',
|
||||
subtractDecimal: '减少小数位',
|
||||
customFormat: '自定义格式',
|
||||
customFormatDes: '根据现有格式生成自定义数字格式。',
|
||||
info: {
|
||||
error: '错误',
|
||||
forceStringInfo: '以文本形式存储的数字',
|
||||
convertToNumber: '转换为数字',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/zh-HK.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/zh-HK.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: '數字格式',
|
||||
numfmtType: '格式類型',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
general: '常規',
|
||||
accounting: '會計',
|
||||
text: '文字',
|
||||
number: '數值',
|
||||
percent: '百分比',
|
||||
scientific: '科學記號',
|
||||
currency: '貨幣',
|
||||
date: '日期',
|
||||
time: '時間',
|
||||
thousandthPercentile: '千分位符',
|
||||
dateTime: '日期時間',
|
||||
decimalLength: '小數位數',
|
||||
currencyType: '貨幣類型',
|
||||
moreFmt: '更多格式',
|
||||
financialValue: '財務數值',
|
||||
roundingCurrency: '貨幣取整',
|
||||
timeDuration: '持續時間',
|
||||
currencyDes: '貨幣格式用來表示一般貨幣數值。會計格式可以對一列數值進行小數點對齊',
|
||||
accountingDes: '會計數字格式可對一列數值進行貨幣符號和小數點對齊',
|
||||
dateType: '日期類型',
|
||||
dateDes: '日期格式將日期和時間系列數值品顯示為日期值。',
|
||||
negType: '負數型別',
|
||||
generalDes: '常規格式不包含任何特定的數字格式。',
|
||||
thousandthPercentileDes: '千分位符號格式用於一般數字的表示。貨幣和會計格式則提供貨幣值計算的專用格式。',
|
||||
addDecimal: '增加小數位',
|
||||
subtractDecimal: '減少小數位',
|
||||
customFormat: '自訂格式',
|
||||
customFormatDes: '基於現有格式產生自訂數字格式。',
|
||||
info: {
|
||||
error: '錯誤',
|
||||
forceStringInfo: '以文字形式儲存的數字',
|
||||
convertToNumber: '轉換為數字',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
61
packages/sheets-numfmt-ui/src/locale/zh-TW.ts
Normal file
61
packages/sheets-numfmt-ui/src/locale/zh-TW.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
/**
|
||||
* 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 = {
|
||||
'sheets-numfmt-ui': {
|
||||
title: '數字格式',
|
||||
numfmtType: '格式類型',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
general: '常規',
|
||||
accounting: '會計',
|
||||
text: '文字',
|
||||
number: '數值',
|
||||
percent: '百分比',
|
||||
scientific: '科學記號',
|
||||
currency: '貨幣',
|
||||
date: '日期',
|
||||
time: '時間',
|
||||
thousandthPercentile: '千分位符',
|
||||
dateTime: '日期時間',
|
||||
decimalLength: '小數位數',
|
||||
currencyType: '貨幣類型',
|
||||
moreFmt: '更多格式',
|
||||
financialValue: '財務數值',
|
||||
roundingCurrency: '貨幣取整',
|
||||
timeDuration: '持續時間',
|
||||
currencyDes: '貨幣格式用來表示一般貨幣數值。會計格式可以對一列數值進行小數點對齊',
|
||||
accountingDes: '會計數字格式可對一列數值進行貨幣符號和小數點對齊',
|
||||
dateType: '日期類型',
|
||||
dateDes: '日期格式將日期和時間系列數值品顯示為日期值。',
|
||||
negType: '負數型別',
|
||||
generalDes: '常規格式不包含任何特定的數字格式。',
|
||||
thousandthPercentileDes: '千分位符號格式用於一般數字的表示。貨幣和會計格式則提供貨幣值計算的專用格式。',
|
||||
addDecimal: '增加小數位',
|
||||
subtractDecimal: '減少小數位',
|
||||
customFormat: '自訂格式',
|
||||
customFormatDes: '基於現有格式產生自訂數字格式。',
|
||||
info: {
|
||||
error: '錯誤',
|
||||
forceStringInfo: '以文字形式儲存的數字',
|
||||
convertToNumber: '轉換為數字',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
195
packages/sheets-numfmt-ui/src/menu/menu.ts
Normal file
195
packages/sheets-numfmt-ui/src/menu/menu.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* 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 { IAccessor } from '@univerjs/core';
|
||||
import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import {
|
||||
fromCallback,
|
||||
ICommandService,
|
||||
isDefaultFormat,
|
||||
isPatternEqualWithoutDecimal,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
RegionService,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import {
|
||||
RangeProtectionPermissionEditPoint,
|
||||
RemoveNumfmtMutation,
|
||||
SetNumfmtMutation,
|
||||
SheetsSelectionsService,
|
||||
WorkbookEditablePermission,
|
||||
WorksheetEditPermission,
|
||||
WorksheetSetCellStylePermission,
|
||||
} from '@univerjs/sheets';
|
||||
import {
|
||||
AddDecimalCommand,
|
||||
getCurrencySymbolByLocale,
|
||||
getCurrencySymbolIconByLocale,
|
||||
SetCurrencyCommand,
|
||||
SetPercentCommand,
|
||||
SubtractDecimalCommand,
|
||||
} from '@univerjs/sheets-numfmt';
|
||||
import { deriveStateFromActiveSheet$, getCurrentRangeDisable$ } from '@univerjs/sheets-ui';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
import { filter, map, merge, Observable } from 'rxjs';
|
||||
import { OpenNumfmtPanelOperator } from '../commands/operations/open.numfmt.panel.operation';
|
||||
import { MORE_NUMFMT_TYPE_KEY, OPTIONS_KEY } from '../views/components/MoreNumfmtType';
|
||||
import { MENU_OPTIONS } from './number-format-options';
|
||||
|
||||
export function CurrencySymbolIconMenuItem(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
const regionService = accessor.get(RegionService);
|
||||
|
||||
return {
|
||||
icon: regionService.currentRegion$.pipe(map((region) => getCurrencySymbolIconByLocale(region).icon)),
|
||||
id: SetCurrencyCommand.id,
|
||||
title: 'sheets-numfmt-ui.currency',
|
||||
tooltip: 'sheets-numfmt-ui.currency',
|
||||
type: MenuItemType.BUTTON,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export function AddDecimalMenuItem(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
return {
|
||||
icon: 'AddDigitsIcon',
|
||||
id: AddDecimalCommand.id,
|
||||
title: 'sheets-numfmt-ui.addDecimal',
|
||||
tooltip: 'sheets-numfmt-ui.addDecimal',
|
||||
type: MenuItemType.BUTTON,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export function SubtractDecimalMenuItem(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
return {
|
||||
icon: 'ReduceDigitsIcon',
|
||||
id: SubtractDecimalCommand.id,
|
||||
title: 'sheets-numfmt-ui.subtractDecimal',
|
||||
tooltip: 'sheets-numfmt-ui.subtractDecimal',
|
||||
type: MenuItemType.BUTTON,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
export function PercentMenuItem(accessor: IAccessor): IMenuButtonItem<LocaleKey> {
|
||||
return {
|
||||
icon: 'PercentIcon',
|
||||
id: SetPercentCommand.id,
|
||||
title: 'sheets-numfmt-ui.percent',
|
||||
tooltip: 'sheets-numfmt-ui.percent',
|
||||
type: MenuItemType.BUTTON,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetEditPermission, WorksheetSetCellStylePermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
}),
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
export function FactoryOtherMenuItem(accessor: IAccessor): IMenuSelectorItem<LocaleKey, string> {
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const localeService = accessor.get(LocaleService);
|
||||
const regionService = accessor.get(RegionService);
|
||||
|
||||
const selectionManagerService = accessor.get(SheetsSelectionsService);
|
||||
const commandList = [RemoveNumfmtMutation.id, SetNumfmtMutation.id];
|
||||
const value$ = deriveStateFromActiveSheet$(
|
||||
univerInstanceService,
|
||||
'',
|
||||
({ workbook, worksheet }) => new Observable((subscribe) =>
|
||||
merge(
|
||||
selectionManagerService.selectionMoveEnd$,
|
||||
regionService.currentRegion$,
|
||||
fromCallback(commandService.onCommandExecuted.bind(commandService)).pipe(
|
||||
filter(([commandInfo]) => commandList.includes(commandInfo.id))
|
||||
)
|
||||
).subscribe(() => {
|
||||
const selections = selectionManagerService.getCurrentSelections();
|
||||
if (selections && selections[0]) {
|
||||
const range = selections[0].range;
|
||||
const row = range.startRow;
|
||||
const col = range.startColumn;
|
||||
const numfmtValue = workbook.getStyles().get(worksheet.getCell(row, col)?.s)?.n;
|
||||
const pattern = numfmtValue?.pattern;
|
||||
const currencySymbol = getCurrencySymbolByLocale(regionService.getCurrentRegion());
|
||||
|
||||
// Adapts the 'General' obtained during import, or the 'General' set manually
|
||||
let value: string = localeService.t<LocaleKey>('sheets-numfmt-ui.general');
|
||||
|
||||
if (isDefaultFormat(pattern)) {
|
||||
subscribe.next(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pattern) {
|
||||
const item = MENU_OPTIONS(currencySymbol).filter((item) => typeof item === 'object' && item.pattern).find(
|
||||
(item) => isPatternEqualWithoutDecimal(pattern, (item as { pattern: string }).pattern)
|
||||
);
|
||||
if (item && typeof item === 'object' && item.pattern) {
|
||||
value = localeService.t<LocaleKey>(item.label);
|
||||
} else {
|
||||
value = localeService.t<LocaleKey>('sheets-numfmt-ui.moreFmt');
|
||||
}
|
||||
}
|
||||
|
||||
subscribe.next(value);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
label: MORE_NUMFMT_TYPE_KEY,
|
||||
id: OpenNumfmtPanelOperator.id,
|
||||
tooltip: 'sheets-numfmt-ui.title',
|
||||
type: MenuItemType.SELECTOR,
|
||||
slot: true,
|
||||
selections: [{
|
||||
label: {
|
||||
name: OPTIONS_KEY,
|
||||
hoverable: false,
|
||||
selectable: false,
|
||||
},
|
||||
}],
|
||||
value$,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
}),
|
||||
};
|
||||
};
|
||||
192
packages/sheets-numfmt-ui/src/menu/mobile-menu.ts
Normal file
192
packages/sheets-numfmt-ui/src/menu/mobile-menu.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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 { IAccessor } from '@univerjs/core';
|
||||
import type { IMenuSelectorItem } from '@univerjs/ui';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import {
|
||||
DEFAULT_TEXT_FORMAT_EXCEL,
|
||||
fromCallback,
|
||||
ICommandService,
|
||||
isDefaultFormat,
|
||||
isPatternEqualWithoutDecimal,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
RegionService,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import {
|
||||
RangeProtectionPermissionEditPoint,
|
||||
RemoveNumfmtMutation,
|
||||
SetNumfmtMutation,
|
||||
SheetsSelectionsService,
|
||||
WorkbookEditablePermission,
|
||||
WorksheetEditPermission,
|
||||
WorksheetSetCellStylePermission,
|
||||
} from '@univerjs/sheets';
|
||||
import {
|
||||
AddDecimalCommand,
|
||||
CURRENCYFORMAT,
|
||||
DATEFMTLISG,
|
||||
getCurrencySymbolByLocale,
|
||||
NUMBERFORMAT,
|
||||
SetCurrencyCommand,
|
||||
SetPercentCommand,
|
||||
SubtractDecimalCommand,
|
||||
} from '@univerjs/sheets-numfmt';
|
||||
import { deriveStateFromActiveSheet$, getCurrentRangeDisable$ } from '@univerjs/sheets-ui';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
import { filter, merge, Observable } from 'rxjs';
|
||||
import { SetMobileNumfmtCommand } from '../commands/commands/set-mobile-numfmt.command';
|
||||
import { MENU_OPTIONS } from './number-format-options';
|
||||
|
||||
export const MOBILE_NUMFMT_MENU_ID = 'sheet.menu.mobile.numfmt';
|
||||
export const MOBILE_CUSTOM_NUMFMT_COMPONENT = 'sheets-numfmt-ui.mobile.custom-format';
|
||||
|
||||
export interface IMobileNumberFormatMenuConfig {
|
||||
kind: 'number-format';
|
||||
title: LocaleKey;
|
||||
commandId: string;
|
||||
detailTitle: LocaleKey;
|
||||
customTitle: LocaleKey;
|
||||
customComponent: string;
|
||||
quickOptions: Array<{
|
||||
label: LocaleKey;
|
||||
commandId: string;
|
||||
value?: string;
|
||||
}>;
|
||||
decimalOptions: Array<{
|
||||
label: LocaleKey;
|
||||
commandId: string;
|
||||
}>;
|
||||
detailOptions: Array<{
|
||||
label?: LocaleKey;
|
||||
value?: string | null;
|
||||
divider?: boolean;
|
||||
custom?: boolean;
|
||||
}>;
|
||||
customPatterns: string[];
|
||||
}
|
||||
|
||||
export function createMobileNumberFormatMenuConfig(currencySymbol: string): IMobileNumberFormatMenuConfig {
|
||||
const detailOptions: IMobileNumberFormatMenuConfig['detailOptions'] = MENU_OPTIONS(currencySymbol)
|
||||
.slice(0, -1)
|
||||
.map((item) => item === '|'
|
||||
? { divider: true }
|
||||
: { label: item.label, value: item.pattern });
|
||||
detailOptions.push({ label: 'sheets-numfmt-ui.customFormat', custom: true });
|
||||
|
||||
return {
|
||||
kind: 'number-format',
|
||||
title: 'sheets-numfmt-ui.title',
|
||||
commandId: SetMobileNumfmtCommand.id,
|
||||
detailTitle: 'sheets-numfmt-ui.moreFmt',
|
||||
customTitle: 'sheets-numfmt-ui.customFormat',
|
||||
customComponent: MOBILE_CUSTOM_NUMFMT_COMPONENT,
|
||||
quickOptions: [
|
||||
{ label: 'sheets-numfmt-ui.percent', commandId: SetPercentCommand.id },
|
||||
{ label: 'sheets-numfmt-ui.currency', commandId: SetCurrencyCommand.id },
|
||||
{ label: 'sheets-numfmt-ui.date', commandId: SetMobileNumfmtCommand.id, value: 'yyyy-mm-dd;@' },
|
||||
{
|
||||
label: 'sheets-numfmt-ui.text',
|
||||
commandId: SetMobileNumfmtCommand.id,
|
||||
value: DEFAULT_TEXT_FORMAT_EXCEL,
|
||||
},
|
||||
],
|
||||
decimalOptions: [
|
||||
{ label: 'sheets-numfmt-ui.subtractDecimal', commandId: SubtractDecimalCommand.id },
|
||||
{ label: 'sheets-numfmt-ui.addDecimal', commandId: AddDecimalCommand.id },
|
||||
],
|
||||
detailOptions,
|
||||
customPatterns: [...new Set([
|
||||
...CURRENCYFORMAT.map((item) => item.suffix(currencySymbol)),
|
||||
...DATEFMTLISG.map((item) => item.suffix),
|
||||
...NUMBERFORMAT.map((item) => item.suffix),
|
||||
])],
|
||||
};
|
||||
}
|
||||
|
||||
export function FactoryMobileOtherMenuItem(
|
||||
accessor: IAccessor
|
||||
): IMenuSelectorItem<LocaleKey, string> & { mobileNumberFormat: IMobileNumberFormatMenuConfig } {
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const localeService = accessor.get(LocaleService);
|
||||
const regionService = accessor.get(RegionService);
|
||||
const selectionManagerService = accessor.get(SheetsSelectionsService);
|
||||
const commandList = [RemoveNumfmtMutation.id, SetNumfmtMutation.id];
|
||||
const value$ = deriveStateFromActiveSheet$(
|
||||
univerInstanceService,
|
||||
'',
|
||||
({ workbook, worksheet }) => new Observable((subscribe) =>
|
||||
merge(
|
||||
selectionManagerService.selectionMoveEnd$,
|
||||
regionService.currentRegion$,
|
||||
fromCallback(commandService.onCommandExecuted.bind(commandService)).pipe(
|
||||
filter(([commandInfo]) => commandList.includes(commandInfo.id))
|
||||
)
|
||||
).subscribe(() => {
|
||||
const selection = selectionManagerService.getCurrentSelections()?.[0];
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { startRow, startColumn } = selection.range;
|
||||
const numfmtValue = workbook.getStyles().get(worksheet.getCell(startRow, startColumn)?.s)?.n;
|
||||
const pattern = numfmtValue?.pattern;
|
||||
const currencySymbol = getCurrencySymbolByLocale(regionService.getCurrentRegion());
|
||||
let value = localeService.t<LocaleKey>('sheets-numfmt-ui.general');
|
||||
|
||||
if (isDefaultFormat(pattern)) {
|
||||
subscribe.next(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pattern) {
|
||||
const item = MENU_OPTIONS(currencySymbol)
|
||||
.filter((option) => typeof option === 'object' && option.pattern)
|
||||
.find((option) => isPatternEqualWithoutDecimal(
|
||||
pattern,
|
||||
(option as { pattern: string }).pattern
|
||||
));
|
||||
value = item && typeof item === 'object' && item.pattern
|
||||
? localeService.t<LocaleKey>(item.label)
|
||||
: localeService.t<LocaleKey>('sheets-numfmt-ui.moreFmt');
|
||||
}
|
||||
|
||||
subscribe.next(value);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
id: MOBILE_NUMFMT_MENU_ID,
|
||||
title: 'sheets-numfmt-ui.title',
|
||||
tooltip: 'sheets-numfmt-ui.title',
|
||||
type: MenuItemType.SELECTOR,
|
||||
selections: [],
|
||||
value$,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {
|
||||
workbookTypes: [WorkbookEditablePermission],
|
||||
worksheetTypes: [WorksheetSetCellStylePermission, WorksheetEditPermission],
|
||||
rangeTypes: [RangeProtectionPermissionEditPoint],
|
||||
}),
|
||||
mobileNumberFormat: createMobileNumberFormatMenuConfig(
|
||||
getCurrencySymbolByLocale(regionService.getCurrentRegion())
|
||||
),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* 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 { Disposable } from '@univerjs/core';
|
||||
import { IMenuManagerService } from '@univerjs/ui';
|
||||
import { mobileMenuSchema } from './mobile-schema';
|
||||
|
||||
export class MobileNumfmtMenuController extends Disposable {
|
||||
constructor(
|
||||
@IMenuManagerService menuManagerService: IMenuManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
menuManagerService.mergeMenu(mobileMenuSchema);
|
||||
}
|
||||
}
|
||||
28
packages/sheets-numfmt-ui/src/menu/mobile-schema.ts
Normal file
28
packages/sheets-numfmt-ui/src/menu/mobile-schema.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 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 { MenuSchemaType } from '@univerjs/ui';
|
||||
import { RibbonStartGroup } from '@univerjs/ui';
|
||||
import { FactoryMobileOtherMenuItem, MOBILE_NUMFMT_MENU_ID } from './mobile-menu';
|
||||
|
||||
export const mobileMenuSchema: MenuSchemaType = {
|
||||
[RibbonStartGroup.NUMBER]: {
|
||||
[MOBILE_NUMFMT_MENU_ID]: {
|
||||
order: 9,
|
||||
menuItemFactory: FactoryMobileOtherMenuItem,
|
||||
},
|
||||
},
|
||||
};
|
||||
86
packages/sheets-numfmt-ui/src/menu/number-format-options.ts
Normal file
86
packages/sheets-numfmt-ui/src/menu/number-format-options.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../locale/types';
|
||||
import { DEFAULT_TEXT_FORMAT_EXCEL } from '@univerjs/core';
|
||||
|
||||
export const MENU_OPTIONS = (currencySymbol: string): Array<{
|
||||
label: LocaleKey;
|
||||
pattern: string | null;
|
||||
} | '|'> => {
|
||||
return [
|
||||
{
|
||||
label: 'sheets-numfmt-ui.general',
|
||||
pattern: null,
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.text',
|
||||
pattern: DEFAULT_TEXT_FORMAT_EXCEL,
|
||||
},
|
||||
'|',
|
||||
{
|
||||
label: 'sheets-numfmt-ui.number',
|
||||
pattern: '0',
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.percent',
|
||||
pattern: '0.00%',
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.scientific',
|
||||
pattern: '0.00E+00',
|
||||
},
|
||||
'|',
|
||||
{
|
||||
label: 'sheets-numfmt-ui.accounting',
|
||||
pattern: `"${currencySymbol}" #,##0.00_);[Red]("${currencySymbol}"#,##0.00)`,
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.financialValue',
|
||||
pattern: '#,##0.00;[Red]#,##0.00',
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.currency',
|
||||
pattern: `"${currencySymbol}"#,##0.00_);[Red]("${currencySymbol}"#,##0.00)`,
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.roundingCurrency',
|
||||
pattern: `"${currencySymbol}"#,##0;[Red]"${currencySymbol}"#,##0`,
|
||||
},
|
||||
'|',
|
||||
{
|
||||
label: 'sheets-numfmt-ui.date',
|
||||
pattern: 'yyyy-mm-dd;@',
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.time',
|
||||
pattern: 'am/pm h":"mm":"ss',
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.dateTime',
|
||||
pattern: 'yyyy-m-d am/pm h:mm',
|
||||
},
|
||||
{
|
||||
label: 'sheets-numfmt-ui.timeDuration',
|
||||
pattern: '[h]:mm:ss',
|
||||
},
|
||||
'|',
|
||||
{
|
||||
label: 'sheets-numfmt-ui.moreFmt',
|
||||
pattern: '',
|
||||
},
|
||||
];
|
||||
};
|
||||
33
packages/sheets-numfmt-ui/src/menu/numfmt.menu.controller.ts
Normal file
33
packages/sheets-numfmt-ui/src/menu/numfmt.menu.controller.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 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 { Disposable } from '@univerjs/core';
|
||||
import { IMenuManagerService } from '@univerjs/ui';
|
||||
import { menuSchema } from './schema';
|
||||
|
||||
export class NumfmtMenuController extends Disposable {
|
||||
constructor(
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initMenu();
|
||||
}
|
||||
|
||||
private _initMenu() {
|
||||
this._menuManagerService.mergeMenu(menuSchema);
|
||||
}
|
||||
}
|
||||
51
packages/sheets-numfmt-ui/src/menu/schema.ts
Normal file
51
packages/sheets-numfmt-ui/src/menu/schema.ts
Normal 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.
|
||||
*/
|
||||
|
||||
import type { MenuSchemaType } from '@univerjs/ui';
|
||||
import { AddDecimalCommand, SetCurrencyCommand, SetPercentCommand, SubtractDecimalCommand } from '@univerjs/sheets-numfmt';
|
||||
import { RibbonStartGroup } from '@univerjs/ui';
|
||||
import { OpenNumfmtPanelOperator } from '../commands/operations/open.numfmt.panel.operation';
|
||||
import { AddDecimalMenuItem, CurrencySymbolIconMenuItem, FactoryOtherMenuItem, PercentMenuItem, SubtractDecimalMenuItem } from './menu';
|
||||
|
||||
export const menuSchema: MenuSchemaType = {
|
||||
[RibbonStartGroup.NUMBER]: {
|
||||
[OpenNumfmtPanelOperator.id]: {
|
||||
order: 9,
|
||||
gridLayout: { row: 1, column: 1, columnSpan: 4, width: 210 },
|
||||
menuItemFactory: FactoryOtherMenuItem,
|
||||
},
|
||||
[SetPercentCommand.id]: {
|
||||
order: 9.1,
|
||||
gridLayout: { row: 2, column: 1 },
|
||||
menuItemFactory: PercentMenuItem,
|
||||
},
|
||||
[SetCurrencyCommand.id]: {
|
||||
order: 9.2,
|
||||
gridLayout: { row: 2, column: 2 },
|
||||
menuItemFactory: CurrencySymbolIconMenuItem,
|
||||
},
|
||||
[AddDecimalCommand.id]: {
|
||||
order: 9.3,
|
||||
gridLayout: { row: 2, column: 3 },
|
||||
menuItemFactory: AddDecimalMenuItem,
|
||||
},
|
||||
[SubtractDecimalCommand.id]: {
|
||||
order: 9.4,
|
||||
gridLayout: { row: 2, column: 4 },
|
||||
menuItemFactory: SubtractDecimalMenuItem,
|
||||
},
|
||||
},
|
||||
};
|
||||
102
packages/sheets-numfmt-ui/src/mobile-plugin.ts
Normal file
102
packages/sheets-numfmt-ui/src/mobile-plugin.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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 { IUniverSheetsNumfmtUIConfig } from './config/config';
|
||||
import {
|
||||
DependentOn,
|
||||
IConfigService,
|
||||
Inject,
|
||||
Injector,
|
||||
merge,
|
||||
Plugin,
|
||||
registerDependencies,
|
||||
touchDependencies,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt';
|
||||
import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui';
|
||||
import { UniverMobileUIPlugin } from '@univerjs/ui';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { MobileComponentsController } from './controllers/mobile/components.controller';
|
||||
import { SheetNumfmtMobileUIController } from './controllers/mobile/ui.controller';
|
||||
import { NumfmtAlertRenderController } from './controllers/numfmt-alert-render.controller';
|
||||
import { NumfmtRepeatLastActionController } from './controllers/numfmt-repeat-last-action.controller';
|
||||
import { NumfmtEditorController } from './controllers/numfmt.editor.controller';
|
||||
import { MobileNumfmtMenuController } from './menu/mobile-numfmt.menu.controller';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverSheetsNumfmtPlugin,
|
||||
UniverSheetsMobileUIPlugin,
|
||||
UniverMobileUIPlugin
|
||||
)
|
||||
export class UniverSheetsNumfmtMobileUIPlugin extends Plugin {
|
||||
static override pluginName = 'SHEET_NUMFMT_UI_PLUGIN';
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsNumfmtUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) override readonly _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { menu, ...rest } = merge({}, defaultPluginConfig, this._config);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
this._configService.setConfig(SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
registerDependencies(this._injector, [
|
||||
[MobileComponentsController],
|
||||
[SheetNumfmtMobileUIController],
|
||||
[NumfmtEditorController],
|
||||
[MobileNumfmtMenuController],
|
||||
[NumfmtRepeatLastActionController],
|
||||
]);
|
||||
touchDependencies(this._injector, [
|
||||
[MobileComponentsController],
|
||||
[SheetNumfmtMobileUIController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
const modules: Dependency[] = [
|
||||
[NumfmtAlertRenderController],
|
||||
];
|
||||
modules.forEach((module) => {
|
||||
this.disposeWithMe(
|
||||
this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, module)
|
||||
);
|
||||
});
|
||||
|
||||
touchDependencies(this._injector, [
|
||||
[NumfmtEditorController],
|
||||
[MobileNumfmtMenuController],
|
||||
[NumfmtRepeatLastActionController],
|
||||
]);
|
||||
}
|
||||
}
|
||||
108
packages/sheets-numfmt-ui/src/plugin.ts
Normal file
108
packages/sheets-numfmt-ui/src/plugin.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* 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 { IUniverSheetsNumfmtUIConfig } from './config/config';
|
||||
import {
|
||||
DependentOn,
|
||||
IConfigService,
|
||||
Inject,
|
||||
Injector,
|
||||
merge,
|
||||
Plugin,
|
||||
registerDependencies,
|
||||
touchDependencies,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { NumfmtAlertRenderController } from './controllers/numfmt-alert-render.controller';
|
||||
import { NumfmtRepeatLastActionController } from './controllers/numfmt-repeat-last-action.controller';
|
||||
import { NumfmtEditorController } from './controllers/numfmt.editor.controller';
|
||||
import { SheetNumfmtUIController } from './controllers/ui.controller';
|
||||
import { UserHabitController } from './controllers/user-habit.controller';
|
||||
import { NumfmtMenuController } from './menu/numfmt.menu.controller';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverSheetsNumfmtPlugin,
|
||||
UniverSheetsUIPlugin
|
||||
)
|
||||
export class UniverSheetsNumfmtUIPlugin extends Plugin {
|
||||
static override pluginName = 'SHEET_NUMFMT_UI_PLUGIN';
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsNumfmtUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) override readonly _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Manage the plugin configuration.
|
||||
const { menu, ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
|
||||
this._configService.setConfig(SHEETS_NUMFMT_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.add([ComponentsController]);
|
||||
this._injector.get(ComponentsController);
|
||||
registerDependencies(this._injector, [
|
||||
[SheetNumfmtUIController],
|
||||
[NumfmtEditorController],
|
||||
[UserHabitController],
|
||||
[NumfmtMenuController],
|
||||
[NumfmtRepeatLastActionController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
this._registerRenderModules();
|
||||
touchDependencies(this._injector, [
|
||||
[SheetNumfmtUIController],
|
||||
[NumfmtEditorController],
|
||||
[NumfmtMenuController],
|
||||
[NumfmtRepeatLastActionController],
|
||||
]);
|
||||
}
|
||||
|
||||
private _registerRenderModules(): void {
|
||||
const modules: Dependency[] = [
|
||||
[NumfmtAlertRenderController],
|
||||
];
|
||||
|
||||
modules.forEach((m) => {
|
||||
this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 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 { DateSystem, excelDateTimePartsToSerial } from '@univerjs/core';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { parseTemporalEditorValue, serializeTemporalEditorValue } from '../temporal-editor-value';
|
||||
|
||||
const dateSystem = DateSystem.Date1900;
|
||||
|
||||
function serial(year: number, month: number, day: number, hours = 0, minutes = 0, seconds = 0, fractionalSecond = 0) {
|
||||
return excelDateTimePartsToSerial({ year, month, day, hours, minutes, seconds, fractionalSecond }, { dateSystem })!;
|
||||
}
|
||||
|
||||
describe('temporal editor values', () => {
|
||||
it('serializes the complete value for the active temporal category', () => {
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 1), pattern: 'mm-dd', locale: 'en', dateSystem })).toBe('2025-07-01');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 1), pattern: 'dd/mm', locale: 'en', dateSystem })).toBe('01/07/2025');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 1), pattern: 'yyyy-m-d', locale: 'zh-CN', dateSystem })).toBe('2025-7-1');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 1), pattern: 'mm/dd', locale: 'en', dateSystem })).toBe('2025/07/01');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 15, 12, 34, 56), pattern: 'yyyy-mm', locale: 'en', dateSystem })).toBe('2025-07-15');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 15, 12, 34, 56), pattern: 'mm-dd h:mm', locale: 'en', dateSystem })).toBe('2025-07-15 12:34:56');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 15, 12, 34, 56), pattern: 'mm/dd h:mm', locale: 'en', dateSystem })).toBe('2025/07/15 12:34:56');
|
||||
expect(serializeTemporalEditorValue({ serial: serial(2025, 7, 15, 12, 34, 56), pattern: 'h:mm', locale: 'en', dateSystem })).toBe('12:34:56');
|
||||
expect(serializeTemporalEditorValue({ serial: 0.5242683912037037, pattern: 'h:mm', locale: 'en', dateSystem })).toBe('12:34:57');
|
||||
expect(serializeTemporalEditorValue({ serial: 1.25, pattern: '[h]:mm', locale: 'en', dateSystem })).toBe('30:00:00');
|
||||
expect(serializeTemporalEditorValue({ serial: 0.5, pattern: 'h:mm AM/PM', locale: 'en', dateSystem })).toBe('12:00:00 PM');
|
||||
});
|
||||
|
||||
it('preserves Excel date-system edge values', () => {
|
||||
expect(serializeTemporalEditorValue({ serial: 0, pattern: 'mm/dd', locale: 'en', dateSystem })).toBe('1900/01/00');
|
||||
expect(parseTemporalEditorValue({ content: '1900/01/00', originalSerial: 0, pattern: 'mm/dd', locale: 'en', dateSystem })).toBe(0);
|
||||
expect(serializeTemporalEditorValue({ serial: 60, pattern: 'mm/dd', locale: 'en', dateSystem })).toBe('1900/02/29');
|
||||
expect(serializeTemporalEditorValue({ serial: 0, pattern: 'mm/dd', locale: 'en', dateSystem: DateSystem.Date1904 })).toBe('1904/01/01');
|
||||
});
|
||||
|
||||
it('edits negative elapsed durations without treating them as calendar dates', () => {
|
||||
expect(serializeTemporalEditorValue({ serial: -1.5, pattern: '[h]:mm', locale: 'en', dateSystem })).toBe('-36:00:00');
|
||||
expect(parseTemporalEditorValue({ content: '-36:00:00', originalSerial: -1.5, pattern: '[h]:mm', locale: 'en', dateSystem })).toBe(-1.5);
|
||||
});
|
||||
|
||||
it('merges real edits with hidden original components', () => {
|
||||
const original = serial(2025, 7, 15, 12, 34, 56, 0.789);
|
||||
|
||||
expect(parseTemporalEditorValue({ content: '08/20/2026', originalSerial: original, pattern: 'mm/dd', locale: 'en', dateSystem }))
|
||||
.toBeCloseTo(serial(2026, 8, 20, 12, 34, 56, 0.789), 10);
|
||||
expect(parseTemporalEditorValue({ content: '01:02:03.456', originalSerial: original, pattern: 'h:mm', locale: 'en', dateSystem }))
|
||||
.toBeCloseTo(serial(2025, 7, 15, 1, 2, 3, 0.456), 10);
|
||||
expect(parseTemporalEditorValue({ content: '24:00:00', originalSerial: original, pattern: 'h:mm', locale: 'en', dateSystem }))
|
||||
.toBe(serial(2025, 7, 16));
|
||||
expect(parseTemporalEditorValue({ content: '31:15:30.5', originalSerial: 1.25, pattern: '[h]:mm', locale: 'en', dateSystem }))
|
||||
.toBeCloseTo((31 * 3600 + 15 * 60 + 30.5) / 86400, 12);
|
||||
});
|
||||
});
|
||||
141
packages/sheets-numfmt-ui/src/utils/temporal-editor-value.ts
Normal file
141
packages/sheets-numfmt-ui/src/utils/temporal-editor-value.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* 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 { DateSystem, INumfmtLocaleTag } from '@univerjs/core';
|
||||
import { excelSerialToDateTimeParts, numfmt } from '@univerjs/core';
|
||||
|
||||
interface ITemporalEditorValueOptions {
|
||||
serial: number;
|
||||
pattern: string;
|
||||
locale: INumfmtLocaleTag;
|
||||
dateSystem: DateSystem;
|
||||
}
|
||||
|
||||
interface IParseTemporalEditorValueOptions {
|
||||
content: string;
|
||||
originalSerial: number;
|
||||
pattern: string;
|
||||
locale: INumfmtLocaleTag;
|
||||
dateSystem: DateSystem;
|
||||
}
|
||||
|
||||
type TemporalType = 'date' | 'datetime' | 'duration' | 'time';
|
||||
|
||||
function getTemporalType(pattern: string): TemporalType | null {
|
||||
const dateInfo = numfmt.getFormatDateInfo(pattern);
|
||||
if (dateInfo.isDuration) {
|
||||
// Elapsed-time formats can exceed 24 hours and therefore cannot use calendar-time parsing.
|
||||
return 'duration';
|
||||
}
|
||||
|
||||
const type = numfmt.getFormatInfo(pattern).type;
|
||||
return type === 'date' || type === 'datetime' || type === 'time' ? type : null;
|
||||
}
|
||||
|
||||
function trimFraction(value: string, locale: INumfmtLocaleTag): string {
|
||||
const decimal = numfmt.getLocale(locale)?.decimal ?? '.';
|
||||
const decimalIndex = value.lastIndexOf(decimal);
|
||||
if (decimalIndex < 0) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const suffixStart = value.indexOf(' ', decimalIndex);
|
||||
const end = suffixStart < 0 ? value.length : suffixStart;
|
||||
let fractionEnd = end;
|
||||
while (fractionEnd > decimalIndex + 1 && value[fractionEnd - 1] === '0') {
|
||||
fractionEnd--;
|
||||
}
|
||||
|
||||
return value.slice(0, fractionEnd) + value.slice(end);
|
||||
}
|
||||
|
||||
function serializeDuration(serial: number, locale: INumfmtLocaleTag): string {
|
||||
// Round once at the editor's millisecond precision so carries stay consistent across every component.
|
||||
const totalMilliseconds = Math.round(Math.abs(serial) * 86400000);
|
||||
const hours = Math.floor(totalMilliseconds / 3600000);
|
||||
const minutes = Math.floor(totalMilliseconds / 60000) % 60;
|
||||
const seconds = Math.floor(totalMilliseconds / 1000) % 60;
|
||||
const milliseconds = totalMilliseconds % 1000;
|
||||
const fraction = milliseconds
|
||||
? `${numfmt.getLocale(locale)?.decimal ?? '.'}${String(milliseconds).padStart(3, '0').replace(/0+$/, '')}`
|
||||
: '';
|
||||
return `${serial < 0 ? '-' : ''}${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}${fraction}`;
|
||||
}
|
||||
|
||||
export function serializeTemporalEditorValue(options: ITemporalEditorValueOptions): string | null {
|
||||
const { serial, pattern, locale, dateSystem } = options;
|
||||
const type = getTemporalType(pattern);
|
||||
if (!type || !Number.isFinite(serial) || (serial < 0 && type !== 'duration')) {
|
||||
return null;
|
||||
}
|
||||
if (type === 'duration') return serializeDuration(serial, locale);
|
||||
|
||||
const parts = excelSerialToDateTimeParts(serial, { dateSystem });
|
||||
if (!parts) return null;
|
||||
const hasFraction = Math.round(parts.fractionalSecond * 1000) > 0;
|
||||
// This complete pattern is only for the editable text; the cell's persisted numfmt remains unchanged.
|
||||
const canonicalPattern = numfmt.getDateTimeEditPattern(pattern, serial);
|
||||
if (!canonicalPattern) return null;
|
||||
|
||||
const value = numfmt.format(canonicalPattern, serial, { locale, dateSystem, throws: false });
|
||||
return hasFraction ? trimFraction(value, locale) : value;
|
||||
}
|
||||
|
||||
function parseDuration(content: string, locale: INumfmtLocaleTag): number | null {
|
||||
const decimal = numfmt.getLocale(locale)?.decimal ?? '.';
|
||||
const normalized = decimal === '.' ? content : content.replace(decimal, '.');
|
||||
const match = /^\s*([+-])?(\d+):([0-5]?\d):([0-5]?\d)(\.\d{1,10})?\s*$/.exec(normalized);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const sign = match[1] === '-' ? -1 : 1;
|
||||
const hours = Number(match[2]);
|
||||
const minutes = Number(match[3]);
|
||||
const seconds = Number(match[4]);
|
||||
const fractionalSecond = Number(match[5] ?? 0);
|
||||
return sign * (hours * 3600 + minutes * 60 + seconds + fractionalSecond) / 86400;
|
||||
}
|
||||
|
||||
export function parseTemporalEditorValue(options: IParseTemporalEditorValueOptions): number | null {
|
||||
const { content, originalSerial, pattern, locale, dateSystem } = options;
|
||||
const type = getTemporalType(pattern);
|
||||
if (!type || !Number.isFinite(originalSerial) || (originalSerial < 0 && type !== 'duration')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === 'duration') {
|
||||
return parseDuration(content, locale);
|
||||
}
|
||||
|
||||
if (type === 'time') {
|
||||
const parsed = numfmt.parseTime(content, { locale, dateSystem });
|
||||
// A time-only edit must not discard a date component hidden by the cell format.
|
||||
return parsed ? Math.floor(originalSerial) + Number(parsed.v) : null;
|
||||
}
|
||||
|
||||
const parsed = numfmt.parseDate(content, { locale, dateSystem });
|
||||
if (!parsed || typeof parsed.v !== 'number') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (type === 'date') {
|
||||
// A date-only edit must not discard time and subsecond components hidden by the cell format.
|
||||
return Math.floor(parsed.v) + (originalSerial - Math.floor(originalSerial));
|
||||
}
|
||||
|
||||
return parsed.v;
|
||||
}
|
||||
158
packages/sheets-numfmt-ui/src/views/SheetNumfmtPanel.tsx
Normal file
158
packages/sheets-numfmt-ui/src/views/SheetNumfmtPanel.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* 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 { ISelectProps } from '@univerjs/design';
|
||||
import type { ComponentType } from 'react';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import type { IBusinessComponentProps } from './components/interface';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { Button, clsx, scrollbarClassName, Select } from '@univerjs/design';
|
||||
import { getCurrencyType } from '@univerjs/sheets-numfmt';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { UserHabitCurrencyContext } from '../controllers/user-habit.controller';
|
||||
import { AccountingPanel, isAccountingPanel } from './components/Accounting';
|
||||
import { CurrencyPanel, isCurrencyPanel } from './components/Currency';
|
||||
import { CustomFormat } from './components/CustomFormat';
|
||||
import { DatePanel, isDatePanel } from './components/Date';
|
||||
import { GeneralPanel, isGeneralPanel } from './components/General';
|
||||
import { isThousandthPercentilePanel, ThousandthPercentilePanel } from './components/ThousandthPercentile';
|
||||
import { useCurrencyOptions } from './hooks/use-currency-options';
|
||||
import { useNextTick } from './hooks/use-next-tick';
|
||||
|
||||
export interface ISheetNumfmtPanelProps {
|
||||
value: { defaultValue: number; defaultPattern: string; row: number; col: number };
|
||||
onChange: (config: { type: 'change' | 'cancel' | 'confirm'; value: string }) => void;
|
||||
}
|
||||
|
||||
export function SheetNumfmtPanel(props: ISheetNumfmtPanelProps) {
|
||||
const { defaultValue, defaultPattern, row, col } = props.value;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const currentPatternRef = useRef<() => string | null>(() => '');
|
||||
const nextTick = useNextTick();
|
||||
|
||||
const typeOptions = useMemo(
|
||||
() => {
|
||||
const typeOptions: Array<{ label: LocaleKey; component: ComponentType<IBusinessComponentProps> }> = [
|
||||
{ label: 'sheets-numfmt-ui.general', component: GeneralPanel },
|
||||
{ label: 'sheets-numfmt-ui.accounting', component: AccountingPanel },
|
||||
{ label: 'sheets-numfmt-ui.currency', component: CurrencyPanel },
|
||||
{ label: 'sheets-numfmt-ui.date', component: DatePanel },
|
||||
{ label: 'sheets-numfmt-ui.thousandthPercentile', component: ThousandthPercentilePanel },
|
||||
{ label: 'sheets-numfmt-ui.customFormat', component: CustomFormat },
|
||||
];
|
||||
|
||||
return typeOptions.map((item) => ({ ...item, label: localeService.t<LocaleKey>(item.label) }));
|
||||
},
|
||||
[localeService]
|
||||
);
|
||||
const [type, setType] = useState(findDefaultType);
|
||||
const [key, setKey] = useState(() => `${row}_${col}_${defaultPattern}`);
|
||||
const { mark, userHabitCurrency } = useCurrencyOptions(() => setKey(`${row}_${col}_${defaultPattern}_userCurrency`));
|
||||
|
||||
const BusinessComponent = typeOptions.find((item) => item.label === type)?.component;
|
||||
|
||||
function findDefaultType() {
|
||||
const list = [isGeneralPanel, isAccountingPanel, isCurrencyPanel, isDatePanel, isThousandthPercentilePanel];
|
||||
return (
|
||||
list.reduce((pre, curFn, index) => pre || (curFn(defaultPattern) ? typeOptions[index].label : ''), '') ||
|
||||
typeOptions[0].label
|
||||
);
|
||||
}
|
||||
|
||||
const selectOptions: ISelectProps['options'] = typeOptions.map((option) => ({
|
||||
label: option.label,
|
||||
value: option.label,
|
||||
}));
|
||||
|
||||
const handleSelect: ISelectProps['onChange'] = (value) => {
|
||||
setType(value);
|
||||
// after the BusinessComponent render.
|
||||
nextTick(() => props.onChange({ type: 'change', value: currentPatternRef.current() || '' }));
|
||||
};
|
||||
|
||||
const handleChange = useCallback((v: string) => {
|
||||
props.onChange({ type: 'change', value: v });
|
||||
}, []);
|
||||
|
||||
const handleActionChange = useCallback((action: () => string | null) => {
|
||||
currentPatternRef.current = action;
|
||||
}, []);
|
||||
|
||||
const handleConfirm = () => {
|
||||
const pattern = currentPatternRef.current() || '';
|
||||
const currency = getCurrencyType(pattern);
|
||||
if (currency) {
|
||||
mark(currency);
|
||||
}
|
||||
props.onChange({ type: 'confirm', value: pattern });
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
props.onChange({ type: 'cancel', value: '' });
|
||||
};
|
||||
|
||||
const subProps: IBusinessComponentProps = {
|
||||
onChange: handleChange,
|
||||
onActionChange: handleActionChange,
|
||||
defaultValue,
|
||||
defaultPattern,
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setType(findDefaultType());
|
||||
setKey(`${row}_${col}_${defaultPattern}`);
|
||||
}, [row, col, defaultPattern]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'univer-flex univer-h-full univer-flex-col univer-justify-between univer-overflow-y-auto univer-pb-5',
|
||||
scrollbarClassName
|
||||
)}
|
||||
>
|
||||
<div>
|
||||
<div className="univer-mt-3.5 univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.numfmtType')}
|
||||
</div>
|
||||
<div className="univer-mt-2">
|
||||
<Select className="univer-w-full" value={type} options={selectOptions} onChange={handleSelect} />
|
||||
</div>
|
||||
<div>
|
||||
{BusinessComponent && (
|
||||
<UserHabitCurrencyContext.Provider value={userHabitCurrency}>
|
||||
<BusinessComponent {...subProps} key={key} />
|
||||
</UserHabitCurrencyContext.Provider>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="
|
||||
univer-mb-5 univer-mt-3.5 univer-flex univer-justify-end univer-gap-2
|
||||
rtl:univer-flex-row-reverse
|
||||
"
|
||||
>
|
||||
<Button onClick={handleCancel}>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.cancel')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={handleConfirm}>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.confirm')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -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 { LocaleKey } from '../../locale/types';
|
||||
import type { IBusinessComponentProps } from './interface';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { InputNumber, Select } from '@univerjs/design';
|
||||
import { getCurrencyType, getDecimalFromPattern, setPatternDecimal } from '@univerjs/sheets-numfmt';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { UserHabitCurrencyContext } from '../../controllers/user-habit.controller';
|
||||
|
||||
export const isAccountingPanel = (pattern: string) => {
|
||||
const type = getCurrencyType(pattern);
|
||||
return !!type && pattern.startsWith('_(');
|
||||
};
|
||||
|
||||
export function AccountingPanel(props: IBusinessComponentProps) {
|
||||
const { defaultPattern, onActionChange, onChange } = props;
|
||||
|
||||
const [decimal, setDecimal] = useState(() => getDecimalFromPattern(defaultPattern || '', 2));
|
||||
const userHabitCurrency = useContext(UserHabitCurrencyContext);
|
||||
const [suffix, setSuffix] = useState(() => getCurrencyType(defaultPattern) || userHabitCurrency[0]);
|
||||
const options = useMemo(() => userHabitCurrency.map((key) => ({ label: key, value: key })), []);
|
||||
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onActionChange(() => setPatternDecimal(`_("${suffix}"* #,##0${decimal > 0 ? '.0' : ''}_)`, decimal));
|
||||
}, [decimal, onActionChange, suffix]);
|
||||
|
||||
const handleSelect = (v: string) => {
|
||||
setSuffix(v);
|
||||
onChange(setPatternDecimal(`_("${v}"* #,##0${decimal > 0 ? '.0' : ''}_)`, decimal));
|
||||
};
|
||||
|
||||
const handleDecimalChange = (v: number | null) => {
|
||||
const decimal = v || 0;
|
||||
setDecimal(decimal);
|
||||
onChange(setPatternDecimal(`_("${suffix}"* #,##0${decimal > 0 ? '.0' : ''}_)`, decimal));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="univer-mt-4 univer-flex univer-justify-between">
|
||||
<div className="option">
|
||||
<div className="univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.decimalLength')}
|
||||
</div>
|
||||
|
||||
<div className="univer-mt-2 univer-w-32">
|
||||
<InputNumber
|
||||
value={decimal}
|
||||
step={1}
|
||||
precision={0}
|
||||
max={20}
|
||||
min={0}
|
||||
onChange={handleDecimalChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="option">
|
||||
<div className="univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.currencyType')}
|
||||
</div>
|
||||
|
||||
<div className="univer-mt-2 univer-w-36">
|
||||
<Select
|
||||
options={options}
|
||||
value={suffix}
|
||||
onChange={handleSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="univer-mt-4 univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.accountingDes')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
122
packages/sheets-numfmt-ui/src/views/components/Currency.tsx
Normal file
122
packages/sheets-numfmt-ui/src/views/components/Currency.tsx
Normal file
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../../locale/types';
|
||||
import type { IBusinessComponentProps } from './interface';
|
||||
import { isPatternEqualWithoutDecimal, LocaleService } from '@univerjs/core';
|
||||
import { InputNumber, Select, SelectList } from '@univerjs/design';
|
||||
import {
|
||||
getCurrencyFormatOptions,
|
||||
getCurrencyType,
|
||||
getDecimalFromPattern,
|
||||
setPatternDecimal,
|
||||
} from '@univerjs/sheets-numfmt';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useContext, useLayoutEffect, useMemo, useState } from 'react';
|
||||
import { UserHabitCurrencyContext } from '../../controllers/user-habit.controller';
|
||||
|
||||
export const isCurrencyPanel = (pattern: string) => {
|
||||
const type = getCurrencyType(pattern);
|
||||
return !!type && !pattern.startsWith('_(');
|
||||
};
|
||||
|
||||
export function CurrencyPanel(props: IBusinessComponentProps) {
|
||||
const { onActionChange, onChange: onValueChange } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const userHabitCurrency = useContext(UserHabitCurrencyContext);
|
||||
const [suffix, setSuffix] = useState(() => getCurrencyType(props.defaultPattern) || userHabitCurrency[0]);
|
||||
const [decimal, setDecimal] = useState(() => getDecimalFromPattern(props.defaultPattern || '', 2));
|
||||
const [pattern, setPattern] = useState(() => {
|
||||
const negativeOptions = getCurrencyFormatOptions(suffix);
|
||||
const pattern =
|
||||
negativeOptions.find((item) => isPatternEqualWithoutDecimal(item.value, props.defaultPattern))?.value ||
|
||||
negativeOptions[0].value;
|
||||
return pattern;
|
||||
});
|
||||
|
||||
const negativeOptions = useMemo(() => getCurrencyFormatOptions(suffix), [suffix]);
|
||||
const options = useMemo(() => userHabitCurrency.map((key) => ({ label: key, value: key })), [userHabitCurrency]);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onActionChange(() => setPatternDecimal(pattern, decimal));
|
||||
}, [decimal, onActionChange, pattern]);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
setSuffix(value);
|
||||
const pattern = getCurrencyFormatOptions(value)[0].value;
|
||||
setPattern(pattern);
|
||||
onValueChange(setPatternDecimal(pattern, decimal));
|
||||
};
|
||||
|
||||
const onChange = (value: any) => {
|
||||
if (value === undefined) {
|
||||
return;
|
||||
}
|
||||
setPattern(value);
|
||||
onValueChange(setPatternDecimal(value, decimal));
|
||||
};
|
||||
|
||||
const onDecimalChange = (v: number | null) => {
|
||||
setDecimal(v || 0);
|
||||
onValueChange(setPatternDecimal(pattern, v || 0));
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="univer-mt-4 univer-flex univer-justify-between">
|
||||
<div className="option">
|
||||
<div className="univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.decimalLength')}
|
||||
</div>
|
||||
<div className="univer-mt-2 univer-w-32">
|
||||
<InputNumber
|
||||
value={decimal}
|
||||
max={20}
|
||||
min={0}
|
||||
onChange={onDecimalChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="option">
|
||||
<div className="univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.currencyType')}
|
||||
</div>
|
||||
<div className="univer-mt-2 univer-w-36">
|
||||
<Select
|
||||
value={suffix}
|
||||
options={options}
|
||||
onChange={onSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="label univer-mt-4">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.negType')}
|
||||
</div>
|
||||
|
||||
<div className="univer-mt-2">
|
||||
<SelectList value={pattern} options={negativeOptions} onChange={onChange} />
|
||||
</div>
|
||||
|
||||
<div className="univer-mt-4 univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.currencyDes')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
126
packages/sheets-numfmt-ui/src/views/components/CustomFormat.tsx
Normal file
126
packages/sheets-numfmt-ui/src/views/components/CustomFormat.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../../locale/types';
|
||||
import type { IBusinessComponentProps } from './interface';
|
||||
import { ILocalStorageService, LocaleService } from '@univerjs/core';
|
||||
import { borderClassName, clsx, Input } from '@univerjs/design';
|
||||
import { CheckMarkIcon } from '@univerjs/icons';
|
||||
import { CURRENCYFORMAT, DATEFMTLISG, NUMBERFORMAT } from '@univerjs/sheets-numfmt';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useEffect, useLayoutEffect, useState } from 'react';
|
||||
import { UserHabitController } from '../../controllers/user-habit.controller';
|
||||
|
||||
const key = 'customFormat';
|
||||
const historyPatternKey = 'numfmt_custom_pattern';
|
||||
|
||||
export function CustomFormat(props: IBusinessComponentProps) {
|
||||
const { defaultPattern, onActionChange, onChange } = props;
|
||||
const userHabitController = useDependency(UserHabitController);
|
||||
const localStorageService = useDependency(ILocalStorageService);
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
const [pattern, setPattern] = useState(defaultPattern);
|
||||
useLayoutEffect(() => {
|
||||
onActionChange(() => {
|
||||
userHabitController.markHabit(key, pattern);
|
||||
localStorageService.getItem<string[]>(historyPatternKey).then((list = []) => {
|
||||
const _list = [...new Set([pattern, ...(list || [])])].splice(0, 10).filter((e) => !!e);
|
||||
localStorageService.setItem(historyPatternKey, _list);
|
||||
});
|
||||
return pattern;
|
||||
});
|
||||
}, [localStorageService, onActionChange, pattern, userHabitController]);
|
||||
const [options, setOptions] = useState<(string | number)[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
void localStorageService.getItem<string[]>(historyPatternKey).then((historyList) => {
|
||||
const list = [
|
||||
...CURRENCYFORMAT.map((item) => item.suffix('$')),
|
||||
...DATEFMTLISG.map((item) => item.suffix),
|
||||
...NUMBERFORMAT.map((item) => item.suffix),
|
||||
...(historyList ?? []),
|
||||
];
|
||||
return userHabitController.addHabit(key, []).then(() => userHabitController.getHabit(key, list));
|
||||
}).then((list) => {
|
||||
if (!cancelled) {
|
||||
setOptions([...new Set(list)]);
|
||||
}
|
||||
});
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [localStorageService, userHabitController]);
|
||||
|
||||
const handleClick = (p: string) => {
|
||||
setPattern(p);
|
||||
onChange(p);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
onChange(pattern);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="univer-mt-4 univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.customFormat')}
|
||||
</div>
|
||||
<Input
|
||||
placeholder={localeService.t<LocaleKey>('sheets-numfmt-ui.customFormat')}
|
||||
onBlur={handleBlur}
|
||||
value={pattern}
|
||||
onChange={setPattern}
|
||||
className="univer-mt-2 univer-w-full"
|
||||
/>
|
||||
<div
|
||||
className={clsx(
|
||||
'univer-mt-2 univer-max-h-[400px] univer-overflow-auto univer-rounded-lg univer-p-2',
|
||||
borderClassName
|
||||
)}
|
||||
>
|
||||
{options.map((p) => (
|
||||
<div
|
||||
key={p}
|
||||
onClick={() => handleClick(p as string)}
|
||||
className={`
|
||||
univer-flex univer-cursor-pointer univer-items-center univer-gap-1.5 univer-py-1.5
|
||||
univer-text-sm
|
||||
`}
|
||||
>
|
||||
<div className="univer-flex univer-w-4 univer-items-center univer-text-primary-600">
|
||||
{pattern === p && <CheckMarkIcon />}
|
||||
</div>
|
||||
<div>
|
||||
{p}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
univer-mt-3 univer-text-sm univer-text-gray-600
|
||||
dark:!univer-text-gray-200
|
||||
`}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.customFormatDes')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
80
packages/sheets-numfmt-ui/src/views/components/Date.tsx
Normal file
80
packages/sheets-numfmt-ui/src/views/components/Date.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../../locale/types';
|
||||
import type { IBusinessComponentProps } from './interface';
|
||||
import { LocaleService, numfmt } from '@univerjs/core';
|
||||
import { SelectList } from '@univerjs/design';
|
||||
import { getDateFormatOptions } from '@univerjs/sheets-numfmt';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
export const isDatePanel = (pattern: string) => {
|
||||
const info = numfmt.getFormatInfo(pattern);
|
||||
return (
|
||||
getDateFormatOptions()
|
||||
.map((item) => item.value)
|
||||
.includes(pattern) || ['date', 'datetime', 'time'].includes(info.type)
|
||||
);
|
||||
};
|
||||
|
||||
export function DatePanel(props: IBusinessComponentProps) {
|
||||
const { onActionChange, onChange, defaultPattern } = props;
|
||||
|
||||
const options = useMemo(getDateFormatOptions, []);
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
const [suffix, setSuffix] = useState(() => {
|
||||
if (defaultPattern) {
|
||||
const item = options.find((item) => item.value === defaultPattern);
|
||||
if (item) {
|
||||
return item.value;
|
||||
}
|
||||
}
|
||||
return options[0].value;
|
||||
});
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onActionChange(() => suffix);
|
||||
}, [onActionChange, suffix]);
|
||||
|
||||
const handleChange = (v: any) => {
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
setSuffix(v);
|
||||
onChange(v);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="univer-mt-4 univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.dateType')}
|
||||
</div>
|
||||
<div className="univer-mt-2">
|
||||
<SelectList value={suffix} options={options} onChange={handleChange} />
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
univer-mt-3.5 univer-text-sm/5 univer-text-gray-600
|
||||
dark:!univer-text-gray-200
|
||||
`}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.dateDes')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
45
packages/sheets-numfmt-ui/src/views/components/General.tsx
Normal file
45
packages/sheets-numfmt-ui/src/views/components/General.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../../locale/types';
|
||||
import type { IBusinessComponentProps } from './interface';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useLayoutEffect } from 'react';
|
||||
|
||||
export const isGeneralPanel = (pattern: string) => !pattern;
|
||||
|
||||
export function GeneralPanel(props: IBusinessComponentProps) {
|
||||
const localeService = useDependency(LocaleService);
|
||||
const { onActionChange } = props;
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onActionChange(() => '');
|
||||
}, [onActionChange]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={`
|
||||
univer-mt-3.5 univer-text-sm/5 univer-text-gray-600
|
||||
dark:!univer-text-gray-200
|
||||
`}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.generalDes')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* 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 { FormatType } from '@univerjs/sheets';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import { ICommandService, LocaleService, Range, RegionService } from '@univerjs/core';
|
||||
import { Separator } from '@univerjs/design';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import {
|
||||
getCurrencySymbolByLocale,
|
||||
getPatternPreview,
|
||||
getPatternType,
|
||||
SetNumfmtCommand,
|
||||
SheetsNumfmtCellContentController,
|
||||
} from '@univerjs/sheets-numfmt';
|
||||
import { ILayoutService, useDependency, useObservable } from '@univerjs/ui';
|
||||
import { useMemo } from 'react';
|
||||
import { OpenNumfmtPanelOperator } from '../../commands/operations/open.numfmt.panel.operation';
|
||||
import { MENU_OPTIONS } from '../../menu/number-format-options';
|
||||
|
||||
export const MORE_NUMFMT_TYPE_KEY = 'sheets-numfmt-ui.moreNumfmtType';
|
||||
export const OPTIONS_KEY = 'sheets-numfmt-ui.moreNumfmtType.options';
|
||||
|
||||
export function MoreNumfmtType(props: { value?: string }) {
|
||||
const { value } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const text = value ?? localeService.t<LocaleKey>('sheets-numfmt-ui.general');
|
||||
|
||||
return <span className="univer-text-sm">{text}</span>;
|
||||
};
|
||||
|
||||
export function Options() {
|
||||
const commandService = useDependency(ICommandService);
|
||||
const localeService = useDependency(LocaleService);
|
||||
const regionService = useDependency(RegionService);
|
||||
const layoutService = useDependency(ILayoutService);
|
||||
const sheetsNumfmtCellContentController = useDependency(SheetsNumfmtCellContentController);
|
||||
const direction = useObservable(localeService.direction$, localeService.getDirection());
|
||||
const region = useObservable(regionService.currentRegion$, regionService.getCurrentRegion());
|
||||
|
||||
const selectionManagerService = useDependency(SheetsSelectionsService);
|
||||
const setNumfmt = (pattern: string | null) => {
|
||||
const selection = selectionManagerService.getCurrentLastSelection();
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const range = selection.range;
|
||||
const values: Array<{ row: number; col: number; pattern?: string; type?: FormatType }> = [];
|
||||
Range.foreach(range, (row, col) => {
|
||||
if (pattern) {
|
||||
values.push({ row, col, pattern, type: getPatternType(pattern) });
|
||||
} else {
|
||||
values.push({ row, col });
|
||||
}
|
||||
});
|
||||
commandService.executeCommand(SetNumfmtCommand.id, { values });
|
||||
|
||||
layoutService.focus();
|
||||
};
|
||||
|
||||
const menuOptions = useMemo(() => {
|
||||
const currencySymbol = getCurrencySymbolByLocale(region);
|
||||
return MENU_OPTIONS(currencySymbol);
|
||||
}, [region]);
|
||||
|
||||
const handleClick = (index: number) => {
|
||||
if (index === 0) {
|
||||
setNumfmt(null);
|
||||
} else if (index === menuOptions.length - 1) {
|
||||
// CATUION: This is a command, not a menu item
|
||||
commandService.executeCommand(OpenNumfmtPanelOperator.id);
|
||||
layoutService.focus();
|
||||
} else {
|
||||
const item = menuOptions[index] as { pattern: string };
|
||||
item.pattern && setNumfmt(item.pattern);
|
||||
}
|
||||
};
|
||||
|
||||
const defaultValue = 1220;
|
||||
|
||||
return (
|
||||
<div dir={direction} className="univer-grid univer-gap-1 univer-p-1.5">
|
||||
{menuOptions.map((item, index) => {
|
||||
if (item === '|') {
|
||||
return <Separator key={index} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`
|
||||
univer-flex univer-h-7 univer-cursor-default univer-items-center univer-justify-between
|
||||
univer-gap-6 univer-rounded univer-px-2 univer-text-sm
|
||||
hover:univer-bg-gray-100
|
||||
dark:hover:!univer-bg-gray-700
|
||||
`}
|
||||
onClick={() => handleClick(index)}
|
||||
>
|
||||
<span>{localeService.t<LocaleKey>(item.label)}</span>
|
||||
|
||||
<span
|
||||
className={`
|
||||
univer-text-xs univer-text-gray-500
|
||||
dark:!univer-text-gray-400
|
||||
`}
|
||||
>
|
||||
{item.pattern ? getPatternPreview(item.pattern || '', defaultValue, sheetsNumfmtCellContentController.locale).result.trim() : ''}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../../locale/types';
|
||||
import type { IBusinessComponentProps } from './interface';
|
||||
import { isPatternEqualWithoutDecimal, LocaleService } from '@univerjs/core';
|
||||
import { InputNumber, SelectList } from '@univerjs/design';
|
||||
import {
|
||||
getDecimalFromPattern,
|
||||
getNumberFormatOptions,
|
||||
isPatternHasDecimal,
|
||||
setPatternDecimal,
|
||||
} from '@univerjs/sheets-numfmt';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useLayoutEffect, useMemo, useState } from 'react';
|
||||
|
||||
export const isThousandthPercentilePanel = (pattern: string) =>
|
||||
getNumberFormatOptions().some((item) => isPatternEqualWithoutDecimal(item.value, pattern));
|
||||
|
||||
export function ThousandthPercentilePanel(props: IBusinessComponentProps) {
|
||||
const { onActionChange, onChange } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
const options = useMemo(getNumberFormatOptions, []);
|
||||
const [decimal, setDecimal] = useState(() => getDecimalFromPattern(props.defaultPattern || '', 0));
|
||||
|
||||
const [suffix, setSuffix] = useState(() => {
|
||||
const item = options.find((item) => isPatternEqualWithoutDecimal(item.value, props.defaultPattern || ''));
|
||||
return item?.value || options[0].value;
|
||||
});
|
||||
|
||||
const pattern = useMemo(() => setPatternDecimal(suffix, Number(decimal || 0)), [suffix, decimal]);
|
||||
|
||||
const isInputDisable = !isPatternHasDecimal(suffix);
|
||||
|
||||
const handleDecimalChange = (decimal: number | null) => {
|
||||
setDecimal(decimal || 0);
|
||||
onChange(setPatternDecimal(suffix, Number(decimal || 0)));
|
||||
};
|
||||
const handleClick = (v: any) => {
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
setDecimal(getDecimalFromPattern(v, 0));
|
||||
setSuffix(v);
|
||||
onChange(v);
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
onActionChange(() => pattern);
|
||||
}, [onActionChange, pattern]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="univer-mt-4 univer-text-sm univer-text-gray-400">
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.decimalLength')}
|
||||
</div>
|
||||
<div className="univer-mt-2">
|
||||
<InputNumber
|
||||
disabled={isInputDisable}
|
||||
value={decimal}
|
||||
max={20}
|
||||
min={0}
|
||||
onChange={handleDecimalChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="univer-mt-4 univer-text-sm univer-text-gray-400">
|
||||
{' '}
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.negType')}
|
||||
</div>
|
||||
<div className="univer-mt-2">
|
||||
<SelectList onChange={handleClick} options={options} value={suffix} />
|
||||
</div>
|
||||
<div
|
||||
className={`
|
||||
univer-mt-3.5 univer-text-sm/5 univer-text-gray-600
|
||||
dark:!univer-text-gray-200
|
||||
`}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.thousandthPercentileDes')}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
22
packages/sheets-numfmt-ui/src/views/components/interface.ts
Normal file
22
packages/sheets-numfmt-ui/src/views/components/interface.ts
Normal 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 interface IBusinessComponentProps {
|
||||
onChange: (result: string) => void;
|
||||
onActionChange: (action: () => string | null) => void;
|
||||
defaultValue: number;
|
||||
defaultPattern: string;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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 { Meta, StoryObj } from '@storybook/react';
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
import type { ISheetNumfmtPanelProps } from '../../SheetNumfmtPanel';
|
||||
import { LocaleService, LocaleType } from '@univerjs/core';
|
||||
import { RediContext } from '@univerjs/ui';
|
||||
import { useContext, useState } from 'react';
|
||||
import { UserHabitController } from '../../../controllers/user-habit.controller';
|
||||
import enUS from '../../../locale/en-US';
|
||||
import ruRU from '../../../locale/ru-RU';
|
||||
import zhCN from '../../../locale/zh-CN';
|
||||
import { SheetNumfmtPanel } from '../../SheetNumfmtPanel';
|
||||
|
||||
const meta: Meta = {
|
||||
title: 'numfmt',
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
},
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
export const NumfmtPanel: StoryObj<ISheetNumfmtPanelProps> = {
|
||||
render() {
|
||||
const { injector } = useContext(RediContext);
|
||||
|
||||
const [inject] = useState(() => {
|
||||
const deps: Dependency[] = [
|
||||
[UserHabitController],
|
||||
];
|
||||
|
||||
injector?.get(LocaleService).load({
|
||||
[LocaleType.EN_US]: enUS,
|
||||
[LocaleType.ZH_CN]: zhCN,
|
||||
[LocaleType.RU_RU]: ruRU,
|
||||
});
|
||||
|
||||
deps.forEach((dependency) => injector?.add(dependency));
|
||||
|
||||
return injector;
|
||||
});
|
||||
|
||||
return (
|
||||
<RediContext.Provider value={{ injector: inject }}>
|
||||
<div className="univer-w-[340px]">
|
||||
<SheetNumfmtPanel
|
||||
value={{ defaultPattern: '', defaultValue: 123123, row: 2, col: 3 }}
|
||||
onChange={(_pattern) => {
|
||||
// empty
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</RediContext.Provider>
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* 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 { currencySymbols } from '@univerjs/core';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { UserHabitController } from '../../controllers/user-habit.controller';
|
||||
|
||||
const key = 'numfmtCurrency';
|
||||
export const useCurrencyOptions = (onOptionChange?: (options: string[]) => void) => {
|
||||
const userHabitController = useDependency(UserHabitController);
|
||||
const [options, setOptions] = useState(currencySymbols);
|
||||
|
||||
useEffect(() => {
|
||||
userHabitController.addHabit('numfmtCurrency', []).then(() => {
|
||||
userHabitController.getHabit(key, [...currencySymbols]).then((list) => {
|
||||
setOptions(list as string[]);
|
||||
onOptionChange && onOptionChange(list as string[]);
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const mark = (v: string) => {
|
||||
userHabitController.markHabit(key, v);
|
||||
};
|
||||
|
||||
return { userHabitCurrency: options, mark };
|
||||
};
|
||||
35
packages/sheets-numfmt-ui/src/views/hooks/use-next-tick.ts
Normal file
35
packages/sheets-numfmt-ui/src/views/hooks/use-next-tick.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 { useEffect, useRef, useState } from 'react';
|
||||
|
||||
export const useNextTick = () => {
|
||||
const effectList = useRef<Array<() => void>>([]);
|
||||
const [value, dispatch] = useState<Record<string, string>>({});
|
||||
|
||||
useEffect(() => {
|
||||
effectList.current.forEach((fn) => {
|
||||
fn();
|
||||
});
|
||||
effectList.current = [];
|
||||
}, [value]);
|
||||
|
||||
const nextTick = (fn: () => void) => {
|
||||
effectList.current.push(fn);
|
||||
dispatch({});
|
||||
};
|
||||
return nextTick;
|
||||
};
|
||||
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* 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 { LocaleKey } from '../../locale/types';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { Button, clsx, Input, MobileActionRow, MobileActionRowGroup, scrollbarClassName } from '@univerjs/design';
|
||||
import { CheckMarkIcon } from '@univerjs/icons';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface IMobileCustomFormatProps {
|
||||
patterns: string[];
|
||||
onConfirm: (pattern: string) => void;
|
||||
}
|
||||
|
||||
export function MobileCustomFormat(props: IMobileCustomFormatProps) {
|
||||
const { patterns, onConfirm } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const [pattern, setPattern] = useState('');
|
||||
const title = localeService.t<LocaleKey>('sheets-numfmt-ui.customFormat');
|
||||
|
||||
return (
|
||||
<div
|
||||
data-u-comp="mobile-custom-format"
|
||||
className="univer-flex univer-h-full univer-min-h-0 univer-flex-col univer-gap-4"
|
||||
>
|
||||
<Input
|
||||
className="univer-shrink-0"
|
||||
size="large"
|
||||
aria-label={title}
|
||||
value={pattern}
|
||||
placeholder={title}
|
||||
onChange={setPattern}
|
||||
/>
|
||||
<div
|
||||
data-u-comp="mobile-custom-format-scroller"
|
||||
className={clsx('univer-min-h-0 univer-flex-1 univer-overflow-y-auto', scrollbarClassName)}
|
||||
>
|
||||
<div
|
||||
className="
|
||||
univer-overflow-hidden univer-rounded-xl univer-bg-gray-0
|
||||
dark:!univer-bg-gray-800
|
||||
"
|
||||
>
|
||||
{patterns.map((item, index) => (
|
||||
<MobileActionRow
|
||||
key={item}
|
||||
aria-label={item}
|
||||
aria-pressed={pattern === item}
|
||||
title={<span className="univer-break-all univer-font-mono univer-text-sm">{item}</span>}
|
||||
trailing={pattern === item
|
||||
? <CheckMarkIcon className="univer-shrink-0 univer-text-primary-600" />
|
||||
: undefined}
|
||||
bordered={index !== patterns.length - 1}
|
||||
className="!univer-rounded-none !univer-font-normal"
|
||||
onClick={() => setPattern(item)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="
|
||||
univer-mt-4 univer-px-1 univer-text-sm univer-text-gray-600
|
||||
dark:!univer-text-gray-200
|
||||
"
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.customFormatDes')}
|
||||
</div>
|
||||
</div>
|
||||
<MobileActionRowGroup className="univer-shrink-0">
|
||||
<Button variant="primary" disabled={!pattern.trim()} onClick={() => onConfirm(pattern.trim())}>
|
||||
{localeService.t<LocaleKey>('sheets-numfmt-ui.confirm')}
|
||||
</Button>
|
||||
</MobileActionRowGroup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
27
packages/sheets-numfmt-ui/tailwind.config.ts
Normal file
27
packages/sheets-numfmt-ui/tailwind.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* 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 { Config } from 'tailwindcss';
|
||||
import preset from '@univerjs-infra/shared/tailwind';
|
||||
|
||||
const config: Config = {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./src/**/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
8
packages/sheets-numfmt-ui/tsconfig.json
Normal file
8
packages/sheets-numfmt-ui/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/sheets-numfmt-ui/tsconfig.node.json
Normal file
18
packages/sheets-numfmt-ui/tsconfig.node.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/node",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
3
packages/sheets-numfmt-ui/vitest.config.ts
Normal file
3
packages/sheets-numfmt-ui/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
Reference in New Issue
Block a user