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

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

View File

@@ -0,0 +1,41 @@
# @univerjs/sheets-sort-ui
[![npm version](https://img.shields.io/npm/v/@univerjs/sheets-sort-ui?style=flat-square)](https://npmjs.com/package/@univerjs/sheets-sort-ui)
[![license](https://img.shields.io/npm/l/@univerjs/sheets-sort-ui?style=flat-square)](https://npmjs.com/package/@univerjs/sheets-sort-ui)
[![downloads](https://img.shields.io/npm/dm/@univerjs/sheets-sort-ui?style=flat-square)](https://npmjs.com/package/@univerjs/sheets-sort-ui)
`@univerjs/sheets-sort-ui` adds sorting menus and panels for Univer Sheets.
## Package Overview
| Package | UMD global | CSS | Locales | Facade entry |
| --- | --- | :---: | :---: | :---: |
| `@univerjs/sheets-sort-ui` | `UniverSheetsSortUi` | Yes | Yes | No |
## Installation
```sh
pnpm add @univerjs/sheets-sort-ui
# or
npm install @univerjs/sheets-sort-ui
```
Keep all `@univerjs/*` packages on the same version.
## Usage
```ts
import '@univerjs/sheets-sort-ui/lib/index.css';
import EnUS from '@univerjs/sheets-sort-ui/locale/en-US';
import { UniverSheetsSortUIPlugin } from '@univerjs/sheets-sort-ui';
univer.registerPlugin(UniverSheetsSortUIPlugin);
// 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-sort-ui)
- [GitHub repository](https://github.com/dream-num/univer)

View File

@@ -0,0 +1,96 @@
{
"name": "@univerjs/sheets-sort-ui",
"version": "1.0.0-rc.0",
"private": false,
"description": "Sorting menus and panels 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",
"sort",
"ui",
"plugin"
],
"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/icons": "1.38.0",
"@univerjs/sheets": "workspace:*",
"@univerjs/sheets-sort": "workspace:*",
"@univerjs/sheets-ui": "workspace:*",
"@univerjs/ui": "workspace:*"
},
"devDependencies": {
"@univerjs-infra/shared": "workspace:*",
"@univerjs/sheets-formula-ui": "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"
}
}

View File

@@ -0,0 +1 @@
export { default } from '@univerjs-infra/shared/postcss';

View File

@@ -0,0 +1,187 @@
/**
* 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, Workbook } from '@univerjs/core';
import { ILogService, Inject, Injector, IUniverInstanceService, LocaleService, LocaleType, LogLevel, Plugin, Tools, Univer, UniverInstanceType } from '@univerjs/core';
import { FormulaCurrentConfigService, FormulaDataModel, FormulaRuntimeService, HyperlinkEngineFormulaService, IFormulaCurrentConfigService, IFormulaRuntimeService, IHyperlinkEngineFormulaService, LexerTreeBuilder } from '@univerjs/engine-formula';
import { SheetInterceptorService, SheetsSelectionsService } from '@univerjs/sheets';
import { FormulaReorderController } from '@univerjs/sheets-formula-ui';
import { SheetsSortService } from '@univerjs/sheets-sort';
import { SheetsSortController } from '@univerjs/sheets-sort/controllers/sheets-sort.controller.js';
import zhCN from '../../../locale/zh-CN';
import { SheetsSortUIService } from '../../../services/sheets-sort-ui.service';
const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = {
id: 'test',
appVersion: '3.0.0-alpha',
sheets: {
sheet1: {
id: 'sheet1',
name: 'sheet1',
cellData: {
0: {
9: { // J1
v: 1,
},
10: { // K1
v: 2,
},
11: { // L1
f: '=J1/K1',
},
},
1: {
9: { // J2
v: 2,
},
10: { // K2
v: 2,
},
11: { // L2
f: '=J2/K2',
},
},
2: {
9: { // J3
v: 3,
},
10: { // K3
v: 2,
},
11: { // L3
f: '=J3/K3',
},
},
3: {
9: { // J4
v: 4,
},
10: { // K4
v: 2,
},
11: { // L4
f: '=J4/K4',
},
},
4: {
9: { // J5
v: 5,
},
10: { // K5
v: 2,
},
11: { // L5
f: '=J5/K5',
},
},
5: {
9: { // J6
v: 6,
},
10: { // K6
v: 2,
},
11: { // L6
f: '=J6/K6',
},
},
6: {
9: { // J7
v: 7,
},
10: { // K7
v: 2,
},
11: { // L7
f: '=SUM(J7:K8)',
},
},
7: {
9: { // J8
v: 1,
},
},
},
},
},
locale: LocaleType.ZH_CN,
name: '',
sheetOrder: [],
styles: {},
};
export interface ITestBed {
univer: Univer;
get: Injector['get'];
sheet: Workbook;
}
export function createCommandTestBed(workbookData?: IWorkbookData, dependencies?: Dependency[]): ITestBed {
const univer = new Univer();
const injector = univer.__getInjector();
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([SheetsSelectionsService]);
injector.add([SheetInterceptorService]);
injector.add([FormulaDataModel]);
injector.add([LexerTreeBuilder]);
injector.add([IHyperlinkEngineFormulaService, { useClass: HyperlinkEngineFormulaService }]);
injector.add([IFormulaRuntimeService, { useClass: FormulaRuntimeService }]);
injector.add([IFormulaCurrentConfigService, { useClass: FormulaCurrentConfigService }]);
injector.add([SheetsSortController]);
injector.add([SheetsSortService]);
injector.add([SheetsSortUIService]);
injector.add([FormulaReorderController]);
dependencies?.forEach((d) => injector.add(d));
}
override onReady(): void {
this._injector.get(SheetsSortController);
this._injector.get(FormulaReorderController);
}
}
univer.registerPlugin(TestPlugin);
const sheet = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, Tools.deepClone(workbookData ?? TEST_WORKBOOK_DATA_DEMO));
const univerInstanceService = injector.get(IUniverInstanceService);
univerInstanceService.focusUnit('test');
const logService = injector.get(ILogService);
logService.setLogLevel(LogLevel.SILENT); // change this to `LogLevel.VERBOSE` to debug tests via logs
const localeService = injector.get(LocaleService);
localeService.load({ zhCN });
return {
univer,
get: injector.get.bind(injector),
sheet,
};
}

View File

@@ -0,0 +1,115 @@
/**
* 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, Injector, Nullable, Univer, Workbook } from '@univerjs/core';
import { ICommandService, IConfirmService, IUniverInstanceService, RANGE_TYPE, TestConfirmService, UniverInstanceType } from '@univerjs/core';
import { ReorderRangeCommand, ReorderRangeMutation, SetRangeValuesMutation, SheetsSelectionsService } from '@univerjs/sheets';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { SheetsSortUIService } from '../../../services/sheets-sort-ui.service';
import { createCommandTestBed } from './create-command-test-bed';
describe('Test "Sort Range Commands"', () => {
let univer: Univer;
let get: Injector['get'];
let commandService: ICommandService;
let sortService: SheetsSortUIService;
let getValues: (
startRow: number,
startColumn: number,
endRow: number,
endColumn: number
) => Array<Array<Nullable<ICellData>>> | undefined;
beforeEach(() => {
const testBed = createCommandTestBed(undefined, [
[IConfirmService, { useClass: TestConfirmService }],
]);
univer = testBed.univer;
get = testBed.get;
commandService = get(ICommandService);
commandService.registerCommand(ReorderRangeCommand);
commandService.registerCommand(SetRangeValuesMutation);
commandService.registerCommand(ReorderRangeMutation);
sortService = get(SheetsSortUIService);
getValues = (
startRow: number,
startColumn: number,
endRow: number,
endColumn: number
): Array<Array<Nullable<ICellData>>> | undefined =>
get(IUniverInstanceService)
.getUnit<Workbook>('test', UniverInstanceType.UNIVER_SHEET)
?.getSheetBySheetId('sheet1')
?.getRange(startRow, startColumn, endRow, endColumn)
.getValues();
});
afterEach(() => univer.dispose());
describe('sort range has formula', () => {
it('desc case: ', async () => {
// J1
let J1Cell = getValues(0, 9, 0, 9)?.[0]?.[0];
expect(J1Cell?.v).toStrictEqual(1);
// L1
let L1Cell = getValues(0, 11, 0, 11)?.[0]?.[0];
expect(L1Cell?.f).toStrictEqual('=J1/K1');
// L7
let L7Cell = getValues(6, 11, 6, 11)?.[0]?.[0];
expect(L7Cell?.f).toStrictEqual('=SUM(J7:K8)');
const selectionManager = get(SheetsSelectionsService);
selectionManager.addSelections([
{
range: {
startRow: 0,
startColumn: 9,
endRow: 6,
endColumn: 11,
rangeType: RANGE_TYPE.NORMAL,
},
primary: {
actualRow: 0,
actualColumn: 9,
startRow: 0,
startColumn: 9,
endRow: 6,
endColumn: 11,
isMerged: false,
isMergedMainCell: false,
},
style: null,
},
]);
const result = await sortService.triggerSortDirectly(false, false);
expect(result).toBeTruthy();
// J1
J1Cell = getValues(0, 9, 0, 9)?.[0]?.[0];
expect(J1Cell?.v).toStrictEqual(7);
// L1
L1Cell = getValues(0, 11, 0, 11)?.[0]?.[0];
expect(L1Cell?.f).toStrictEqual('=SUM(J1:K2)');
// L7
L7Cell = getValues(6, 11, 6, 11)?.[0]?.[0];
expect(L7Cell?.f).toStrictEqual('=J7/K7');
});
});
});

View File

@@ -0,0 +1,235 @@
/**
* 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, Injector, Nullable, Univer } from '@univerjs/core';
import { ICommandService, IConfirmService, RANGE_TYPE, TestConfirmService } from '@univerjs/core';
import { ReorderRangeCommand, ReorderRangeMutation, SetRangeValuesMutation, SetSelectionsOperation, SheetsSelectionsService } from '@univerjs/sheets';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { SheetsSortUIService } from '../../../services/sheets-sort-ui.service';
import {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from '../sheets-sort.command';
import { createCommandTestBed } from './create-command-test-bed';
describe('Sheets sort commands integration', () => {
let univer: Univer;
let get: Injector['get'];
let commandService: ICommandService;
let getValues: (
startRow: number,
startColumn: number,
endRow: number,
endColumn: number
) => Array<Array<Nullable<ICellData>>> | undefined;
beforeEach(() => {
const testBed = createCommandTestBed(undefined, [
[IConfirmService, { useClass: TestConfirmService }],
]);
univer = testBed.univer;
get = testBed.get;
commandService = get(ICommandService);
[
ReorderRangeCommand,
ReorderRangeMutation,
SetRangeValuesMutation,
SetSelectionsOperation,
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscInCtxMenuCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescInCtxMenuCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeCustomInCtxMenuCommand,
].forEach((command) => commandService.registerCommand(command));
getValues = (startRow, startColumn, endRow, endColumn) =>
testBed.sheet
.getSheetBySheetId('sheet1')
?.getRange(startRow, startColumn, endRow, endColumn)
.getValues();
});
afterEach(() => {
univer.dispose();
});
function selectSortRange() {
get(SheetsSelectionsService).addSelections([{
range: {
startRow: 0,
startColumn: 9,
endRow: 6,
endColumn: 11,
rangeType: RANGE_TYPE.NORMAL,
},
primary: {
actualRow: 0,
actualColumn: 9,
startRow: 0,
startColumn: 9,
endRow: 6,
endColumn: 11,
isMerged: false,
isMergedMainCell: false,
},
style: null,
}]);
}
function expectAscendingSortResult() {
expect(getValues(0, 9, 0, 9)?.[0]?.[0]?.v).toBe(1);
expect(getValues(6, 9, 6, 9)?.[0]?.[0]?.v).toBe(7);
expect(getValues(0, 11, 0, 11)?.[0]?.[0]?.f).toBe('=J1/K1');
expect(getValues(6, 11, 6, 11)?.[0]?.[0]?.f).toBe('=SUM(J7:K8)');
}
function expectDescendingSortResult() {
expect(getValues(0, 9, 0, 9)?.[0]?.[0]?.v).toBe(7);
expect(getValues(6, 9, 6, 9)?.[0]?.[0]?.v).toBe(1);
expect(getValues(0, 11, 0, 11)?.[0]?.[0]?.f).toBe('=SUM(J1:K2)');
expect(getValues(6, 11, 6, 11)?.[0]?.[0]?.f).toBe('=J7/K7');
}
function expectAscendingExtendedSortResult() {
expect(getValues(0, 9, 0, 9)?.[0]?.[0]?.v).toBe(1);
expect(getValues(1, 9, 1, 9)?.[0]?.[0]?.v).toBe(1);
expect(getValues(7, 9, 7, 9)?.[0]?.[0]?.v).toBe(7);
expect(get(SheetsSelectionsService).getCurrentLastSelection()?.range).toMatchObject({
startRow: 0,
startColumn: 9,
endRow: 7,
endColumn: 11,
});
}
function expectDescendingExtendedSortResult() {
expect(getValues(0, 9, 0, 9)?.[0]?.[0]?.v).toBe(7);
expect(getValues(6, 9, 6, 9)?.[0]?.[0]?.v).toBe(1);
expect(getValues(7, 9, 7, 9)?.[0]?.[0]?.v).toBe(1);
expect(get(SheetsSelectionsService).getCurrentLastSelection()?.range).toMatchObject({
startRow: 0,
startColumn: 9,
endRow: 7,
endColumn: 11,
});
}
it('sorts the selected range ascending through executeCommand', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeAscCommand.id)).toBe(true);
expectAscendingSortResult();
});
it('sorts the selected range descending through executeCommand and rewrites formulas', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeDescCommand.id)).toBe(true);
expectDescendingSortResult();
});
it('sorts ascending from the extended range menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeAscExtCommand.id)).toBe(true);
expectAscendingExtendedSortResult();
});
it('sorts descending from the extended range menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeDescExtCommand.id)).toBe(true);
expectDescendingExtendedSortResult();
});
it('sorts ascending from the context menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeAscInCtxMenuCommand.id)).toBe(true);
expectAscendingSortResult();
});
it('sorts descending from the context menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeDescInCtxMenuCommand.id)).toBe(true);
expectDescendingSortResult();
});
it('sorts ascending from the extended context menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeAscExtInCtxMenuCommand.id)).toBe(true);
expectAscendingExtendedSortResult();
});
it('sorts descending from the extended context menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeDescExtInCtxMenuCommand.id)).toBe(true);
expectDescendingExtendedSortResult();
});
it('opens custom sort panel for the selected range', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeCustomCommand.id)).toBe(true);
const customSortState = get(SheetsSortUIService).customSortState();
expect(customSortState?.show).toBe(true);
expect(customSortState?.location).toMatchObject({
unitId: 'test',
subUnitId: 'sheet1',
colIndex: 9,
range: {
startRow: 0,
startColumn: 9,
endRow: 6,
endColumn: 11,
},
});
});
it('opens custom sort panel from the context menu command', async () => {
selectSortRange();
expect(await commandService.executeCommand(SortRangeCustomInCtxMenuCommand.id)).toBe(true);
const customSortState = get(SheetsSortUIService).customSortState();
expect(customSortState?.show).toBe(true);
expect(customSortState?.location?.range).toMatchObject({
startRow: 0,
startColumn: 9,
endRow: 6,
endColumn: 11,
});
});
});

View 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 { IAccessor, ICommand } from '@univerjs/core';
import { CommandType } from '@univerjs/core';
import { SheetsSortUIService } from '../../services/sheets-sort-ui.service';
export const SortRangeAscCommand: ICommand = {
id: 'sheet.command.sort-range-asc',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(true, false);
},
};
export const SortRangeAscExtCommand: ICommand = {
id: 'sheet.command.sort-range-asc-ext',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(true, true);
},
};
export const SortRangeDescCommand: ICommand = {
id: 'sheet.command.sort-range-desc',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(false, false);
},
};
export const SortRangeDescExtCommand: ICommand = {
id: 'sheet.command.sort-range-desc-ext',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(false, true);
},
};
export const SortRangeCustomCommand: ICommand = {
id: 'sheet.command.sort-range-custom',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortCustomize();
},
};
export const SortRangeAscInCtxMenuCommand: ICommand = {
id: 'sheet.command.sort-range-asc-ctx',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(true, false);
},
};
export const SortRangeAscExtInCtxMenuCommand: ICommand = {
id: 'sheet.command.sort-range-asc-ext-ctx',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(true, true);
},
};
export const SortRangeDescInCtxMenuCommand: ICommand = {
id: 'sheet.command.sort-range-desc-ctx',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(false, false);
},
};
export const SortRangeDescExtInCtxMenuCommand: ICommand = {
id: 'sheet.command.sort-range-desc-ext-ctx',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortDirectly(false, true);
},
};
export const SortRangeCustomInCtxMenuCommand: ICommand = {
id: 'sheet.command.sort-range-custom-ctx',
type: CommandType.COMMAND,
handler: async (accessor: IAccessor) => {
const sortService = accessor.get(SheetsSortUIService);
return await sortService.triggerSortCustomize();
},
};

View 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_SORT_UI_PLUGIN_CONFIG_KEY = 'sheets-sort-ui.config';
export const configSymbol = Symbol(SHEETS_SORT_UI_PLUGIN_CONFIG_KEY);
export interface IUniverSheetsSortUIConfig {
menu?: MenuConfig;
}
export const defaultPluginConfig: IUniverSheetsSortUIConfig = {};

View File

@@ -0,0 +1,52 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Disposable, Inject } from '@univerjs/core';
import { AscendingIcon, CustomSortIcon, DescendingIcon, ExpandAscendingIcon, ExpandDescendingIcon } from '@univerjs/icons';
import { ComponentManager, IconManager } from '@univerjs/ui';
import { CustomSortPanel } from '../views/CustomSortPanel';
export class ComponentsController extends Disposable {
constructor(
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
@Inject(IconManager) private readonly _iconManager: IconManager
) {
super();
this._registerIcons();
this._registerComponents();
}
private _registerIcons(): void {
this.disposeWithMe(this._iconManager.register({
AscendingIcon,
ExpandAscendingIcon,
DescendingIcon,
ExpandDescendingIcon,
CustomSortIcon,
}));
}
private _registerComponents(): void {
([
['CustomSortPanel', CustomSortPanel],
] as const).forEach(([key, comp]) => {
this.disposeWithMe(
this._componentManager.register(key, comp)
);
});
}
}

View File

@@ -0,0 +1,40 @@
/**
* 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 { AscendingIcon, CustomSortIcon, DescendingIcon, ExpandAscendingIcon, ExpandDescendingIcon } from '@univerjs/icons';
import { ComponentManager, IconManager } from '@univerjs/ui';
import { MobileCustomSortPanel } from '../../views/mobile/MobileCustomSortPanel';
export const MOBILE_CUSTOM_SORT_PANEL = 'MobileCustomSortPanel';
export class MobileComponentsController extends Disposable {
constructor(
@Inject(ComponentManager) componentManager: ComponentManager,
@Inject(IconManager) iconManager: IconManager
) {
super();
this.disposeWithMe(iconManager.register({
AscendingIcon,
ExpandAscendingIcon,
DescendingIcon,
ExpandDescendingIcon,
CustomSortIcon,
}));
this.disposeWithMe(componentManager.register(MOBILE_CUSTOM_SORT_PANEL, MobileCustomSortPanel));
}
}

View File

@@ -0,0 +1,95 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { LocaleKey } from '../../locale/types';
import { ICommandService, Inject, LocaleService, RxDisposable } from '@univerjs/core';
import { serializeRange } from '@univerjs/engine-formula';
import { SortRangeCommand } from '@univerjs/sheets-sort';
import { SheetsRenderService } from '@univerjs/sheets-ui';
import { IDialogService, IMenuManagerService } from '@univerjs/ui';
import { takeUntil } from 'rxjs';
import {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from '../../commands/commands/sheets-sort.command';
import { mobileMenuSchema } from '../../menu/mobile-schema';
import { SheetsSortUIService } from '../../services/sheets-sort-ui.service';
import { MOBILE_CUSTOM_SORT_PANEL } from './components.controller';
const MOBILE_CUSTOM_SORT_DIALOG_ID = 'mobile-custom-sort-dialog';
export class SheetsSortMobileUIController extends RxDisposable {
constructor(
@ICommandService private readonly _commandService: ICommandService,
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
@IDialogService private readonly _dialogService: IDialogService,
@Inject(SheetsRenderService) private readonly _sheetRenderService: SheetsRenderService,
@Inject(LocaleService) private readonly _localeService: LocaleService,
@Inject(SheetsSortUIService) private readonly _sheetsSortUIService: SheetsSortUIService
) {
super();
this._initCommands();
this._menuManagerService.mergeMenu(mobileMenuSchema);
this._initCustomSortPanel();
}
private _initCommands(): void {
[
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeCustomCommand,
SortRangeAscInCtxMenuCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeCustomInCtxMenuCommand,
].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command)));
this.disposeWithMe(this._sheetRenderService.registerSkeletonChangingMutations(SortRangeCommand.id));
}
private _initCustomSortPanel(): void {
this._sheetsSortUIService.customSortState$.pipe(takeUntil(this.dispose$)).subscribe((state) => {
if (state?.show && state.location) {
this._dialogService.open({
id: MOBILE_CUSTOM_SORT_DIALOG_ID,
title: {
title: `${this._localeService.t<LocaleKey>('sheets-sort-ui.general.sort-custom')}: ${serializeRange(state.location.range)}`,
},
children: { label: MOBILE_CUSTOM_SORT_PANEL },
mask: true,
onClose: () => this._sheetsSortUIService.closeCustomSortPanel(),
});
return;
}
if (state && !state.show) {
this._dialogService.close(MOBILE_CUSTOM_SORT_DIALOG_ID);
}
});
}
}

View File

@@ -0,0 +1,17 @@
/**
* 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.
*/
// TODO:@yuhongz maybe we should add shortcut for sorting

View File

@@ -0,0 +1,133 @@
/**
* 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 { UIPartsService } from '@univerjs/ui';
import type { LocaleKey } from '../locale/types';
import type { ISheetSortLocation } from '../services/sheets-sort-ui.service';
import { ICommandService, Inject, Injector, LocaleService, RxDisposable } from '@univerjs/core';
import { serializeRange } from '@univerjs/engine-formula';
import { SortRangeCommand } from '@univerjs/sheets-sort';
import { SheetsRenderService, SheetsUIPart } from '@univerjs/sheets-ui';
import {
connectInjector,
IDialogService,
ILayoutService,
IMenuManagerService,
IUIPartsService,
} from '@univerjs/ui';
import { takeUntil } from 'rxjs';
import {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from '../commands/commands/sheets-sort.command';
import { menuSchema } from '../menu/schema';
import { SheetsSortUIService } from '../services/sheets-sort-ui.service';
import EmbedSortBtn from '../views/EmbedSortBtn';
const CUSTOM_SORT_DIALOG_ID = 'custom-sort-dialog';
const CUSTOM_SORT_PANEL_WIDTH = 560;
const CUSTOM_SORT_PANEL_TOP = 128;
export class SheetsSortUIController extends RxDisposable {
constructor(
@ICommandService private readonly _commandService: ICommandService,
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
@IDialogService private readonly _dialogService: IDialogService,
@ILayoutService private readonly _layoutService: ILayoutService,
@IUIPartsService private readonly _uiPartsService: UIPartsService,
@Inject(SheetsRenderService) private _sheetRenderService: SheetsRenderService,
@Inject(LocaleService) private readonly _localeService: LocaleService,
@Inject(SheetsSortUIService) private readonly _sheetsSortUIService: SheetsSortUIService,
@Inject(Injector) private _injector: Injector
) {
super();
this._initCommands();
this._initMenu();
this._initUI();
}
private _initMenu() {
this._menuManagerService.mergeMenu(menuSchema);
}
private _initCommands(): void {
[
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeCustomCommand,
SortRangeAscInCtxMenuCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeCustomInCtxMenuCommand,
].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command)));
this.disposeWithMe(this._sheetRenderService.registerSkeletonChangingMutations(SortRangeCommand.id));
}
private _initUI(): void {
this.disposeWithMe(
this._uiPartsService.registerComponent(SheetsUIPart.FILTER_PANEL_EMBED_POINT, () => connectInjector(EmbedSortBtn, this._injector))
);
// this controller is also responsible for toggling the CustomSortDialog
this._sheetsSortUIService.customSortState$.pipe(takeUntil(this.dispose$)).subscribe((newState) => {
if (newState && newState.show && newState.location) {
this._openCustomSortPanel(newState.location);
} else if (newState && !newState?.show) {
this._closePanel();
}
});
}
private _openCustomSortPanel(location: ISheetSortLocation): void {
this._dialogService.open({
id: CUSTOM_SORT_DIALOG_ID,
draggable: true,
width: CUSTOM_SORT_PANEL_WIDTH,
title: { title: `${this._localeService.t<LocaleKey>('sheets-sort-ui.general.sort-custom')}: ${serializeRange(location.range)}` },
children: { label: 'CustomSortPanel' },
defaultPosition: getCustomSortDialogDefaultPosition(),
preservePositionOnDestroy: false,
onClose: () => this._closePanel(),
mask: true,
});
}
private _closePanel(): void {
this._dialogService.close(CUSTOM_SORT_DIALOG_ID);
queueMicrotask(() => this._layoutService.focus());
}
}
function getCustomSortDialogDefaultPosition(): { x: number; y: number } {
const x = (window.innerWidth - CUSTOM_SORT_PANEL_WIDTH) / 2;
const y = CUSTOM_SORT_PANEL_TOP;
return { x, y };
}

View File

@@ -0,0 +1,2 @@
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,34 @@
/**
* 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 {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from './commands/commands/sheets-sort.command';
export type { IUniverSheetsSortUIConfig } from './config/config';
export { menuSchema as SheetsSortUIMenuSchema } from './menu/schema';
export { UniverSheetsSortMobileUIPlugin } from './mobile-plugin';
export { UniverSheetsSortUIPlugin } from './plugin';

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'فرز',
'sort-asc': 'تصاعدي',
'sort-desc': 'تنازلي',
'sort-custom': 'فرز مخصص',
'sort-asc-ext': 'فرز تصاعدي ممتد',
'sort-desc-ext': 'فرز تنازلي ممتد',
'sort-asc-cur': 'تصاعدي',
'sort-desc-cur': 'تنازلي',
},
error: {
'merge-size': 'يحتوي النطاق المحدد على خلايا مدمجة بأحجام مختلفة ولا يمكن فرزها.',
empty: 'النطاق المحدد فارغ ولا يمكن فرزه.',
single: 'يحتوي النطاق المحدد على صف واحد فقط ولا يمكن فرزه.',
'formula-array': 'يحتوي النطاق المحدد على صيغ مصفوفة ولا يمكن فرزه.',
},
dialog: {
'sort-reminder': 'تذكير الفرز',
'sort-reminder-desc': 'توسيع نطاق الفرز أم الاحتفاظ بنطاق الفرز الحالي؟',
'sort-reminder-ext': 'توسيع نطاق الفرز',
'sort-reminder-no': 'الاحتفاظ بنطاق الفرز',
'first-row-check': 'الصف الأول لا يشارك في الفرز',
'add-condition': 'إضافة شرط',
'delete-condition': 'حذف الشرط',
cancel: 'إلغاء',
confirm: 'تأكيد',
},
info: {
tooltip: 'تلميح',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Ordena',
'sort-asc': 'Ascendent',
'sort-desc': 'Descendent',
'sort-custom': 'Ordre personalitzat',
'sort-asc-ext': 'Expandeix ascendent',
'sort-desc-ext': 'Expandeix descendent',
'sort-asc-cur': 'Ascendent',
'sort-desc-cur': 'Descendent',
},
error: {
'merge-size': 'L\'interval seleccionat conté cel·les combinades de diferents mides, que no es poden ordenar.',
empty: 'L\'interval seleccionat no té contingut i no es pot ordenar.',
single: 'L\'interval seleccionat només té una fila i no es pot ordenar.',
'formula-array': 'L\'interval seleccionat té fórmules de matriu i no es pot ordenar.',
},
dialog: {
'sort-reminder': 'Recordatori d\'ordenació',
'sort-reminder-desc': 'Estendre l\'interval d\'ordenació o mantenir-lo?',
'sort-reminder-ext': 'Estendre l\'interval d\'ordenació',
'sort-reminder-no': 'Mantenir l\'interval d\'ordenació',
'first-row-check': 'La primera fila no participa en l\'ordenació',
'add-condition': 'Afegeix condició',
'delete-condition': 'Suprimeix la condició',
cancel: 'Cancel·lar',
confirm: 'Confirmar',
},
info: {
tooltip: 'Informació sobre l\'eina',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Sortieren',
'sort-asc': 'Aufsteigend',
'sort-desc': 'Absteigend',
'sort-custom': 'Benutzerdefiniertes Sortieren',
'sort-asc-ext': 'Aufsteigend erweitern',
'sort-desc-ext': 'Absteigend erweitern',
'sort-asc-cur': 'Aufsteigend',
'sort-desc-cur': 'Absteigend',
},
error: {
'merge-size': 'Der ausgewählte Bereich enthält verbundene Zellen unterschiedlicher Größe und kann nicht sortiert werden.',
empty: 'Der ausgewählte Bereich hat keinen Inhalt und kann nicht sortiert werden.',
single: 'Der ausgewählte Bereich hat nur eine Zeile und kann nicht sortiert werden.',
'formula-array': 'Der ausgewählte Bereich enthält Matrixformeln und kann nicht sortiert werden.',
},
dialog: {
'sort-reminder': 'Sortierhinweis',
'sort-reminder-desc': 'Bereichssortierung erweitern oder beibehalten?',
'sort-reminder-ext': 'Bereichssortierung erweitern',
'sort-reminder-no': 'Bereichssortierung beibehalten',
'first-row-check': 'Erste Zeile nimmt nicht am Sortieren teil',
'add-condition': 'Bedingung hinzufügen',
'delete-condition': 'Bedingung löschen',
cancel: 'Abbrechen',
confirm: 'Bestätigen',
},
info: {
tooltip: 'Tooltip',
},
},
};
export default locale;

View File

@@ -0,0 +1,52 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const locale = {
'sheets-sort-ui': {
general: {
sort: 'Sort',
'sort-asc': 'Ascending',
'sort-desc': 'Descending',
'sort-custom': 'Custom Sort',
'sort-asc-ext': 'Expand Ascending',
'sort-desc-ext': 'Expand Descending',
'sort-asc-cur': 'Ascending',
'sort-desc-cur': 'Descending',
},
error: {
'merge-size': 'The selected range contains merged cells of different sizes, which cannot be sorted.',
empty: 'The selected range has no content and cannot be sorted.',
single: 'The selected range has only one row and cannot be sorted.',
'formula-array': 'The selected range has array formulas and cannot be sorted.',
},
dialog: {
'sort-reminder': 'Sort Reminder',
'sort-reminder-desc': 'Extend range sorting or keep range sorting?',
'sort-reminder-ext': 'Extend range sorting',
'sort-reminder-no': 'Keep range sorting',
'first-row-check': 'First row does not participate in sorting',
'add-condition': 'Add condition',
'delete-condition': 'Delete condition',
cancel: 'Cancel',
confirm: 'Confirm',
},
info: {
tooltip: 'Tooltip',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Ordenar',
'sort-asc': 'Ascendente',
'sort-desc': 'Descendente',
'sort-custom': 'Orden personalizado',
'sort-asc-ext': 'Expandir ascendente',
'sort-desc-ext': 'Expandir descendente',
'sort-asc-cur': 'Ascendente',
'sort-desc-cur': 'Descendente',
},
error: {
'merge-size': 'El rango seleccionado contiene celdas combinadas de diferentes tamaños, que no se pueden ordenar.',
empty: 'El rango seleccionado no tiene contenido y no se puede ordenar.',
single: 'El rango seleccionado solo tiene una fila y no se puede ordenar.',
'formula-array': 'El rango seleccionado tiene fórmulas de matriz y no se puede ordenar.',
},
dialog: {
'sort-reminder': 'Recordatorio de ordenación',
'sort-reminder-desc': '¿Extender el rango de ordenación o mantener el rango?',
'sort-reminder-ext': 'Extender el rango de ordenación',
'sort-reminder-no': 'Mantener el rango de ordenación',
'first-row-check': 'La primera fila no participa en la ordenación',
'add-condition': 'Agregar condición',
'delete-condition': 'Eliminar condición',
cancel: 'Cancelar',
confirm: 'Confirmar',
},
info: {
tooltip: 'Información sobre la herramienta',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'مرتب‌سازی',
'sort-asc': 'صعودی',
'sort-desc': 'نزولی',
'sort-custom': 'مرتب‌سازی سفارشی',
'sort-asc-ext': 'گسترش صعودی',
'sort-desc-ext': 'گسترش نزولی',
'sort-asc-cur': 'صعودی',
'sort-desc-cur': 'نزولی',
},
error: {
'merge-size': 'محدوده انتخاب شده حاوی سلول‌های ادغام شده با اندازه‌های مختلف است که نمی‌توان مرتب کرد.',
empty: 'محدوده انتخاب شده هیچ محتوایی ندارد و نمی‌توان مرتب کرد.',
single: 'محدوده انتخاب شده فقط یک ردیف دارد و نمی‌توان مرتب کرد.',
'formula-array': 'محدوده انتخاب شده دارای فرمول‌های آرایه‌ای است و نمی‌توان مرتب کرد.',
},
dialog: {
'sort-reminder': 'یادآوری مرتب‌سازی',
'sort-reminder-desc': 'گسترش مرتب‌سازی محدوده یا حفظ مرتب‌سازی محدوده؟',
'sort-reminder-ext': 'گسترش مرتب‌سازی محدوده',
'sort-reminder-no': 'حفظ مرتب‌سازی محدوده',
'first-row-check': 'ردیف اول در مرتب‌سازی شرکت نمی‌کند',
'add-condition': 'افزودن شرط',
'delete-condition': 'حذف شرط',
cancel: 'انصراف',
confirm: 'تایید',
},
info: {
tooltip: 'راهنما',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Trier',
'sort-asc': 'Croissant',
'sort-desc': 'Décroissant',
'sort-custom': 'Tri personnalisé',
'sort-asc-ext': 'Étendre croissant',
'sort-desc-ext': 'Étendre décroissant',
'sort-asc-cur': 'Croissant',
'sort-desc-cur': 'Décroissant',
},
error: {
'merge-size': 'La plage sélectionnée contient des cellules fusionnées de tailles différentes, qui ne peuvent pas être triées.',
empty: 'La plage sélectionnée ne contient aucun contenu et ne peut pas être triée.',
single: 'La plage sélectionnée ne contient qu\'une seule ligne et ne peut pas être triée.',
'formula-array': 'La plage sélectionnée contient des formules matricielles et ne peut pas être triée.',
},
dialog: {
'sort-reminder': 'Rappel de tri',
'sort-reminder-desc': 'Étendre le tri de la plage ou conserver le tri de la plage?',
'sort-reminder-ext': 'Étendre le tri de la plage',
'sort-reminder-no': 'Conserver le tri de la plage',
'first-row-check': 'La première ligne ne participe pas au tri',
'add-condition': 'Ajouter une condition',
'delete-condition': 'Supprimer la condition',
cancel: 'Annuler',
confirm: 'Confirmer',
},
info: {
tooltip: 'Info-bulle',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Urutkan',
'sort-asc': 'Menaik',
'sort-desc': 'Menurun',
'sort-custom': 'Urutan Kustom',
'sort-asc-ext': 'Perluas Menaik',
'sort-desc-ext': 'Perluas Menurun',
'sort-asc-cur': 'Menaik',
'sort-desc-cur': 'Menurun',
},
error: {
'merge-size': 'Rentang yang dipilih mengandung sel yang digabungkan dengan ukuran berbeda, yang tidak dapat diurutkan.',
empty: 'Rentang yang dipilih tidak memiliki konten dan tidak dapat diurutkan.',
single: 'Rentang yang dipilih hanya memiliki satu baris dan tidak dapat diurutkan.',
'formula-array': 'Rentang yang dipilih memiliki rumus array dan tidak dapat diurutkan.',
},
dialog: {
'sort-reminder': 'Pengingat Urutan',
'sort-reminder-desc': 'Perluas pengurutan rentang atau pertahankan pengurutan rentang?',
'sort-reminder-ext': 'Perluas pengurutan rentang',
'sort-reminder-no': 'Pertahankan pengurutan rentang',
'first-row-check': 'Baris pertama tidak ikut dalam pengurutan',
'add-condition': 'Tambah kondisi',
'delete-condition': 'Hapus kondisi',
cancel: 'Batal',
confirm: 'Konfirmasi',
},
info: {
tooltip: 'Tooltip',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Ordinamento',
'sort-asc': 'Crescente',
'sort-desc': 'Decrescente',
'sort-custom': 'Ordinamento personalizzato',
'sort-asc-ext': 'Espandi crescente',
'sort-desc-ext': 'Espandi decrescente',
'sort-asc-cur': 'Crescente',
'sort-desc-cur': 'Decrescente',
},
error: {
'merge-size': "L'intervallo selezionato contiene celle unite di dimensioni diverse e non può essere ordinato.",
empty: "L'intervallo selezionato non ha contenuto e non può essere ordinato.",
single: "L'intervallo selezionato ha una sola riga e non può essere ordinato.",
'formula-array': "L'intervallo selezionato contiene formule matriciali e non può essere ordinato.",
},
dialog: {
'sort-reminder': 'Promemoria ordinamento',
'sort-reminder-desc': 'Estendere l\'ordinamento all\'intervallo o mantenerlo?',
'sort-reminder-ext': 'Estendi ordinamento intervallo',
'sort-reminder-no': 'Mantieni ordinamento intervallo',
'first-row-check': 'La prima riga non partecipa all\'ordinamento',
'add-condition': 'Aggiungi condizione',
'delete-condition': 'Elimina condizione',
cancel: 'Annulla',
confirm: 'Conferma',
},
info: {
tooltip: 'Suggerimento',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: '並べ替え',
'sort-asc': '昇順',
'sort-desc': '降順',
'sort-custom': 'カスタム順序',
'sort-asc-ext': '昇順(拡張)',
'sort-desc-ext': '降順(拡張)',
'sort-asc-cur': '昇順',
'sort-desc-cur': '降順',
},
error: {
'merge-size': '選択範囲に異なるサイズの結合セルが含まれているため、並べ替えができません。',
empty: '選択範囲にデータがないため、並べ替えができません。',
single: '選択範囲に1行しか含まれていないため、並べ替えができません。',
'formula-array': '選択範囲に配列数式が含まれているため、並べ替えができません。',
},
dialog: {
'sort-reminder': '並べ替えの確認',
'sort-reminder-desc': '範囲を拡張して並べ替えますか、それとも範囲を維持して並べ替えますか?',
'sort-reminder-ext': '範囲を拡張して並べ替え',
'sort-reminder-no': '範囲を維持して並べ替え',
'first-row-check': '先頭行を並べ替えの対象に含めない',
'add-condition': '条件を追加',
'delete-condition': '条件を削除',
cancel: 'キャンセル',
confirm: '確認',
},
info: {
tooltip: 'ツールチップ',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: '정렬',
'sort-asc': '오름차순',
'sort-desc': '내림차순',
'sort-custom': '사용자 정의 순서',
'sort-asc-ext': '오름차순 확장',
'sort-desc-ext': '내림차순 확장',
'sort-asc-cur': '오름차순',
'sort-desc-cur': '내림차순',
},
error: {
'merge-size': '선택한 범위에 서로 다른 크기의 병합 셀이 있어 정렬할 수 없습니다.',
empty: '선택한 범위에 내용이 없어 정렬할 수 없습니다.',
single: '선택한 범위에 한 행만 있어 정렬할 수 없습니다.',
'formula-array': '선택한 범위에 배열 수식이 있어 정렬할 수 없습니다.',
},
dialog: {
'sort-reminder': '정렬 알림',
'sort-reminder-desc': '범위 정렬 확장 또는 범위 정렬 유지?',
'sort-reminder-ext': '범위 정렬 확장',
'sort-reminder-no': '범위 정렬 유지',
'first-row-check': '첫 번째 행은 정렬에 참여하지 않습니다',
'add-condition': '조건 추가',
'delete-condition': '조건 삭제',
cancel: '취소',
confirm: '확인',
},
info: {
tooltip: '툴팁',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Sortuj',
'sort-asc': 'Rosnąco',
'sort-desc': 'Malejąco',
'sort-custom': 'Sortowanie niestandardowe',
'sort-asc-ext': 'Rozszerz rosnąco',
'sort-desc-ext': 'Rozszerz malejąco',
'sort-asc-cur': 'Rosnąco',
'sort-desc-cur': 'Malejąco',
},
error: {
'merge-size': 'Wybrany zakres zawiera scalone komórki o różnych rozmiarach, których nie można sortować.',
empty: 'Wybrany zakres nie zawiera treści i nie można go sortować.',
single: 'Wybrany zakres ma tylko jeden wiersz i nie można go sortować.',
'formula-array': 'Wybrany zakres zawiera formuły tablicowe i nie można go sortować.',
},
dialog: {
'sort-reminder': 'Przypomnienie o sortowaniu',
'sort-reminder-desc': 'Rozszerzyć sortowanie zakresu czy zachować sortowanie zakresu?',
'sort-reminder-ext': 'Rozszerz sortowanie zakresu',
'sort-reminder-no': 'Zachowaj sortowanie zakresu',
'first-row-check': 'Pierwszy wiersz nie bierze udziału w sortowaniu',
'add-condition': 'Dodaj warunek',
'delete-condition': 'Usuń warunek',
cancel: 'Anuluj',
confirm: 'Potwierdź',
},
info: {
tooltip: 'Podpowiedź',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Ordenar',
'sort-asc': 'Crescente',
'sort-desc': 'Decrescente',
'sort-custom': 'Ordenação Personalizada',
'sort-asc-ext': 'Expandir Crescente',
'sort-desc-ext': 'Expandir Decrescente',
'sort-asc-cur': 'Crescente',
'sort-desc-cur': 'Decrescente',
},
error: {
'merge-size': 'O intervalo selecionado contém células mescladas de tamanhos diferentes e não pode ser ordenado.',
empty: 'O intervalo selecionado não tem conteúdo e não pode ser ordenado.',
single: 'O intervalo selecionado tem apenas uma linha e não pode ser ordenado.',
'formula-array': 'O intervalo selecionado possui fórmulas de matriz e não pode ser ordenado.',
},
dialog: {
'sort-reminder': 'Lembrete de Ordenação',
'sort-reminder-desc': 'Expandir a ordenação do intervalo ou manter a ordenação do intervalo?',
'sort-reminder-ext': 'Expandir ordenação do intervalo',
'sort-reminder-no': 'Manter ordenação do intervalo',
'first-row-check': 'A primeira linha não participa da ordenação',
'add-condition': 'Adicionar condição',
'delete-condition': 'Excluir condição',
cancel: 'Cancelar',
confirm: 'Confirmar',
},
info: {
tooltip: 'Dica',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Сортировка',
'sort-asc': 'По возрастанию',
'sort-desc': 'По убыванию',
'sort-custom': 'Пользовательская сортировка',
'sort-asc-ext': 'Расширенная сортировка по возрастанию',
'sort-desc-ext': 'Расширенная сортировка по убыванию',
'sort-asc-cur': 'По возрастанию',
'sort-desc-cur': 'По убыванию',
},
error: {
'merge-size': 'Выбранный диапазон содержит объединенные ячейки разного размера, которые нельзя отсортировать.',
empty: 'Выбранный диапазон не содержит данных и не может быть отсортирован.',
single: 'Выбранный диапазон содержит только одну строку и не может быть отсортирован.',
'formula-array': 'Выбранный диапазон содержит массив формул и не может быть отсортирован.',
},
dialog: {
'sort-reminder': 'Напоминание о сортировке',
'sort-reminder-desc': 'Расширить сортировку диапазона или оставить сортировку диапазона?',
'sort-reminder-ext': 'Расширить сортировку диапазона',
'sort-reminder-no': 'Оставить сортировку диапазона',
'first-row-check': 'Первая строка контента не участвует в сортировке',
'add-condition': 'Добавить условие',
'delete-condition': 'Удалить условие',
cancel: 'Отмена',
confirm: 'Подтвердить',
},
info: {
tooltip: 'Подсказка',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: 'Triediť',
'sort-asc': 'Vzostupne',
'sort-desc': 'Zostupne',
'sort-custom': 'Vlastné triedenie',
'sort-asc-ext': 'Rozšíriť vzostupne',
'sort-desc-ext': 'Rozšíriť zostupne',
'sort-asc-cur': 'Vzostupne',
'sort-desc-cur': 'Zostupne',
},
error: {
'merge-size': 'Vybraný rozsah obsahuje zlúčené bunky rôznych veľkostí, ktoré nie je možné triediť.',
empty: 'Vybraný rozsah nemá žiadny obsah a nedá sa triediť.',
single: 'Vybraný rozsah má iba jeden riadok a nedá sa triediť.',
'formula-array': 'Vybraný rozsah obsahuje maticové vzorce a nedá sa triediť.',
},
dialog: {
'sort-reminder': 'Upozornenie na triedenie',
'sort-reminder-desc': 'Rozšíriť triedenie rozsahu alebo zachovať triedenie rozsahu?',
'sort-reminder-ext': 'Rozšíriť triedenie rozsahu',
'sort-reminder-no': 'Zachovať triedenie rozsahu',
'first-row-check': 'Prvý riadok sa nezúčastňuje triedenia',
'add-condition': 'Pridať podmienku',
'delete-condition': 'Odstrániť podmienku',
cancel: 'Zrušiť',
confirm: 'Potvrdiť',
},
info: {
tooltip: 'Bublinový tip',
},
},
};
export default locale;

View File

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

View File

@@ -0,0 +1,55 @@
/**
* 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-sort-ui': {
general: {
sort: 'Sắp xếp',
'sort-asc': 'Tăng dần',
'sort-desc': 'Giảm dần',
'sort-custom': 'Tùy chỉnh sắp xếp',
'sort-asc-ext': 'Tăng dần khu vực mở rộng',
'sort-desc-ext': 'Giảm dần khu vực mở rộng',
'sort-asc-cur': 'Tăng dần khu vực hiện tại',
'sort-desc-cur': 'Giảm dần khu vực hiện tại',
},
error: {
'merge-size': 'Kích thước của các ô đã hợp nhất trong vùng được chọn không đồng nhất, không thể sắp xếp.',
empty: 'Vùng được chọn không có nội dung, không thể sắp xếp.',
single: 'Vùng được chọn chỉ có một hàng, không thể sắp xếp.',
'formula-array': 'Vùng được chọn chứa công thức mảng, không thể sắp xếp.',
},
dialog: {
'sort-reminder': 'Nhắc nhở sắp xếp',
'sort-reminder-desc': 'Hiện tại chỉ sắp xếp vùng được chọn, có muốn mở rộng phạm vi sắp xếp không?',
'sort-reminder-ext': 'Mở rộng phạm vi sắp xếp',
'sort-reminder-no': 'Giữ nguyên phạm vi sắp xếp được chọn',
'first-row-check': 'Hàng đầu tiên không tham gia sắp xếp',
'add-condition': 'Thêm điều kiện sắp xếp',
'delete-condition': 'Xóa điều kiện',
cancel: 'Hủy bỏ',
confirm: 'Xác nhận',
},
info: {
tooltip: 'Tooltip',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: '排序',
'sort-asc': '升序',
'sort-desc': '降序',
'sort-custom': '自定义排序',
'sort-asc-ext': '拓展区域升序',
'sort-desc-ext': '拓展区域降序',
'sort-asc-cur': '当前区域升序',
'sort-desc-cur': '当前区域降序',
},
error: {
'merge-size': '所选区域的合并单元格的大小不一致,无法排序。',
empty: '所选区域无内容,无法排序。',
single: '所选区域仅有一行,无法排序。',
'formula-array': '所选区域含数组公式,无法排序。',
},
dialog: {
'sort-reminder': '排序提醒',
'sort-reminder-desc': '当前仅会对选中区域进行排序,是否拓展排序范围?',
'sort-reminder-ext': '拓展排序范围',
'sort-reminder-no': '保持所选排序范围',
'first-row-check': '标题不参与排序',
'add-condition': '添加排序条件',
'delete-condition': '删除排序条件',
cancel: '取消',
confirm: '确认',
},
info: {
tooltip: '提示',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: '排序',
'sort-asc': '升序',
'sort-desc': '降序',
'sort-custom': '自訂排序',
'sort-asc-ext': '拓展區域升序',
'sort-desc-ext': '拓展區域降序',
'sort-asc-cur': '目前區域升序',
'sort-desc-cur': '當前區域降序',
},
error: {
'merge-size': '所選區域的合併儲存格的大小不一致,無法排序。 ',
empty: '所選區域無內容,無法排序。 ',
single: '所選區域只有一列,無法排序。 ',
'formula-array': '所選區域含陣列公式,無法排序。 ',
},
dialog: {
'sort-reminder': '排序提醒',
'sort-reminder-desc': '目前只會對選取區域進行排序,是否拓展排序範圍? ',
'sort-reminder-ext': '拓展排序範圍',
'sort-reminder-no': '保持所選排序範圍',
'first-row-check': '標題不參與排序',
'add-condition': '新增排序條件',
'delete-condition': '刪除排序條件',
cancel: '取消',
confirm: '確認',
},
info: {
tooltip: '提示',
},
},
};
export default locale;

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type enUS from './en-US';
const locale: typeof enUS = {
'sheets-sort-ui': {
general: {
sort: '排序',
'sort-asc': '升序',
'sort-desc': '降序',
'sort-custom': '自訂排序',
'sort-asc-ext': '拓展區域升序',
'sort-desc-ext': '拓展區域降序',
'sort-asc-cur': '目前區域升序',
'sort-desc-cur': '當前區域降序',
},
error: {
'merge-size': '所選區域的合併儲存格的大小不一致,無法排序。 ',
empty: '所選區域無內容,無法排序。 ',
single: '所選區域只有一列,無法排序。 ',
'formula-array': '所選區域含陣列公式,無法排序。 ',
},
dialog: {
'sort-reminder': '排序提醒',
'sort-reminder-desc': '目前只會對選取區域進行排序,是否拓展排序範圍? ',
'sort-reminder-ext': '拓展排序範圍',
'sort-reminder-no': '保持所選排序範圍',
'first-row-check': '標題不參與排序',
'add-condition': '新增排序條件',
'delete-condition': '刪除排序條件',
cancel: '取消',
confirm: '確認',
},
info: {
tooltip: '提示',
},
},
};
export default locale;

View File

@@ -0,0 +1,103 @@
/**
* 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 { ContextMenuGroup, ContextMenuPosition, RibbonDataGroup } from '@univerjs/ui';
import {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from '../commands/commands/sheets-sort.command';
import {
SHEETS_SORT_CTX_MENU_ID,
SHEETS_SORT_MENU_ID,
sortRangeAscCtxMenuFactory,
sortRangeAscExtCtxMenuFactory,
sortRangeAscExtMenuFactory,
sortRangeAscMenuFactory,
sortRangeCtxMenuFactory,
sortRangeCustomCtxMenuFactory,
sortRangeCustomMenuFactory,
sortRangeDescCtxMenuFactory,
sortRangeDescExtCtxMenuFactory,
sortRangeDescExtMenuFactory,
sortRangeDescMenuFactory,
sortRangeMenuFactory,
} from './sheets-sort.menu';
export const mobileMenuSchema: MenuSchemaType = {
[RibbonDataGroup.ORGANIZATION]: {
[SHEETS_SORT_MENU_ID]: {
order: 3,
menuItemFactory: sortRangeMenuFactory,
[SortRangeAscCommand.id]: {
order: 0,
menuItemFactory: sortRangeAscMenuFactory,
},
[SortRangeAscExtCommand.id]: {
order: 1,
menuItemFactory: sortRangeAscExtMenuFactory,
},
[SortRangeDescCommand.id]: {
order: 2,
menuItemFactory: sortRangeDescMenuFactory,
},
[SortRangeDescExtCommand.id]: {
order: 3,
menuItemFactory: sortRangeDescExtMenuFactory,
},
[SortRangeCustomCommand.id]: {
order: 4,
menuItemFactory: sortRangeCustomMenuFactory,
},
},
},
[ContextMenuPosition.MAIN_AREA]: {
[ContextMenuGroup.DATA]: {
[SHEETS_SORT_CTX_MENU_ID]: {
order: 0,
menuItemFactory: sortRangeCtxMenuFactory,
[SortRangeAscInCtxMenuCommand.id]: {
order: 0,
menuItemFactory: sortRangeAscCtxMenuFactory,
},
[SortRangeAscExtInCtxMenuCommand.id]: {
order: 1,
menuItemFactory: sortRangeAscExtCtxMenuFactory,
},
[SortRangeDescInCtxMenuCommand.id]: {
order: 2,
menuItemFactory: sortRangeDescCtxMenuFactory,
},
[SortRangeDescExtInCtxMenuCommand.id]: {
order: 3,
menuItemFactory: sortRangeDescExtCtxMenuFactory,
},
[SortRangeCustomInCtxMenuCommand.id]: {
order: 4,
menuItemFactory: sortRangeCustomCtxMenuFactory,
},
},
},
},
};

View File

@@ -0,0 +1,104 @@
/**
* 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 { ContextMenuGroup, ContextMenuPosition, RibbonDataGroup } from '@univerjs/ui';
import {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from '../commands/commands/sheets-sort.command';
import {
SHEETS_SORT_CTX_MENU_ID,
SHEETS_SORT_MENU_ID,
sortRangeAscCtxMenuFactory,
sortRangeAscExtCtxMenuFactory,
sortRangeAscExtMenuFactory,
sortRangeAscMenuFactory,
sortRangeCtxMenuFactory,
sortRangeCustomCtxMenuFactory,
sortRangeCustomMenuFactory,
sortRangeDescCtxMenuFactory,
sortRangeDescExtCtxMenuFactory,
sortRangeDescExtMenuFactory,
sortRangeDescMenuFactory,
sortRangeMenuFactory,
} from './sheets-sort.menu';
export const menuSchema: MenuSchemaType = {
[RibbonDataGroup.ORGANIZATION]: {
[SHEETS_SORT_MENU_ID]: {
order: 3,
gridLayout: { row: 2, column: 2 },
menuItemFactory: sortRangeMenuFactory,
[SortRangeAscCommand.id]: {
order: 0,
menuItemFactory: sortRangeAscMenuFactory,
},
[SortRangeAscExtCommand.id]: {
order: 1,
menuItemFactory: sortRangeAscExtMenuFactory,
},
[SortRangeDescCommand.id]: {
order: 2,
menuItemFactory: sortRangeDescMenuFactory,
},
[SortRangeDescExtCommand.id]: {
order: 3,
menuItemFactory: sortRangeDescExtMenuFactory,
},
[SortRangeCustomCommand.id]: {
order: 4,
menuItemFactory: sortRangeCustomMenuFactory,
},
},
},
[ContextMenuPosition.MAIN_AREA]: {
[ContextMenuGroup.DATA]: {
[SHEETS_SORT_CTX_MENU_ID]: {
order: 0,
menuItemFactory: sortRangeCtxMenuFactory,
[SortRangeAscInCtxMenuCommand.id]: {
order: 0,
menuItemFactory: sortRangeAscCtxMenuFactory,
},
[SortRangeAscExtInCtxMenuCommand.id]: {
order: 1,
menuItemFactory: sortRangeAscExtCtxMenuFactory,
},
[SortRangeDescInCtxMenuCommand.id]: {
order: 2,
menuItemFactory: sortRangeDescCtxMenuFactory,
},
[SortRangeDescExtInCtxMenuCommand.id]: {
order: 3,
menuItemFactory: sortRangeDescExtCtxMenuFactory,
},
[SortRangeCustomInCtxMenuCommand.id]: {
order: 4,
menuItemFactory: sortRangeCustomCtxMenuFactory,
},
},
},
},
};

View File

@@ -0,0 +1,189 @@
/**
* 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 { IMenuItem } from '@univerjs/ui';
import type { LocaleKey } from '../locale/types';
import { UniverInstanceType } from '@univerjs/core';
import {
RangeProtectionPermissionEditPoint,
WorkbookEditablePermission,
WorksheetEditPermission,
WorksheetSortPermission,
} from '@univerjs/sheets';
import { getCurrentExclusiveRangeInterest$, getCurrentRangeDisable$ } from '@univerjs/sheets-ui';
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
import {
SortRangeAscCommand,
SortRangeAscExtCommand,
SortRangeAscExtInCtxMenuCommand,
SortRangeAscInCtxMenuCommand,
SortRangeCustomCommand,
SortRangeCustomInCtxMenuCommand,
SortRangeDescCommand,
SortRangeDescExtCommand,
SortRangeDescExtInCtxMenuCommand,
SortRangeDescInCtxMenuCommand,
} from '../commands/commands/sheets-sort.command';
export const SHEETS_SORT_MENU_ID = 'sheet.menu.sheets-sort';
export const SHEETS_SORT_CTX_MENU_ID = 'sheet.menu.sheets-sort-ctx';
export function sortRangeMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SHEETS_SORT_MENU_ID,
type: MenuItemType.SUBITEMS,
icon: 'AscendingIcon',
tooltip: 'sheets-sort-ui.general.sort',
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}
export function sortRangeAscMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SortRangeAscCommand.id,
icon: 'AscendingIcon',
title: 'sheets-sort-ui.general.sort-asc-cur',
type: MenuItemType.BUTTON,
hidden$: getCurrentExclusiveRangeInterest$(accessor),
};
}
export function sortRangeAscExtMenuFactory(): IMenuItem<LocaleKey> {
return {
id: SortRangeAscExtCommand.id,
title: 'sheets-sort-ui.general.sort-asc-ext',
icon: 'ExpandAscendingIcon',
type: MenuItemType.BUTTON,
};
}
export function sortRangeDescMenuFactory(): IMenuItem<LocaleKey> {
return {
id: SortRangeDescCommand.id,
title: 'sheets-sort-ui.general.sort-desc-cur',
icon: 'DescendingIcon',
type: MenuItemType.BUTTON,
};
}
export function sortRangeDescExtMenuFactory(): IMenuItem<LocaleKey> {
return {
id: SortRangeDescExtCommand.id,
title: 'sheets-sort-ui.general.sort-desc-ext',
icon: 'ExpandDescendingIcon',
type: MenuItemType.BUTTON,
};
}
export function sortRangeCustomMenuFactory(): IMenuItem<LocaleKey> {
return {
id: SortRangeCustomCommand.id,
title: 'sheets-sort-ui.general.sort-custom',
type: MenuItemType.BUTTON,
icon: 'CustomSortIcon',
};
}
export function sortRangeCtxMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SHEETS_SORT_CTX_MENU_ID,
title: 'sheets-sort-ui.general.sort',
type: MenuItemType.SUBITEMS,
icon: 'AscendingIcon',
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}
export function sortRangeAscCtxMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SortRangeAscInCtxMenuCommand.id,
title: 'sheets-sort-ui.general.sort-asc-cur',
type: MenuItemType.BUTTON,
icon: 'AscendingIcon',
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}
export function sortRangeAscExtCtxMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SortRangeAscExtInCtxMenuCommand.id,
title: 'sheets-sort-ui.general.sort-asc-ext',
type: MenuItemType.BUTTON,
icon: 'ExpandAscendingIcon',
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}
export function sortRangeDescCtxMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SortRangeDescInCtxMenuCommand.id,
title: 'sheets-sort-ui.general.sort-desc-cur',
type: MenuItemType.BUTTON,
icon: 'DescendingIcon',
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}
export function sortRangeDescExtCtxMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SortRangeDescExtInCtxMenuCommand.id,
title: 'sheets-sort-ui.general.sort-desc-ext',
type: MenuItemType.BUTTON,
icon: 'ExpandDescendingIcon',
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}
export function sortRangeCustomCtxMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
return {
id: SortRangeCustomInCtxMenuCommand.id,
title: 'sheets-sort-ui.general.sort-custom',
type: MenuItemType.BUTTON,
icon: 'CustomSortIcon',
disabled$: getCurrentRangeDisable$(accessor, {
workbookTypes: [WorkbookEditablePermission],
worksheetTypes: [WorksheetSortPermission, WorksheetEditPermission],
rangeTypes: [RangeProtectionPermissionEditPoint],
}),
};
}

View File

@@ -0,0 +1,69 @@
/**
* 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 { IUniverSheetsSortUIConfig } from './config/config';
import {
DependentOn,
IConfigService,
Inject,
Injector,
merge,
Plugin,
registerDependencies,
touchDependencies,
UniverInstanceType,
} from '@univerjs/core';
import { UniverSheetsPlugin } from '@univerjs/sheets';
import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort';
import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui';
import { UniverMobileUIPlugin } from '@univerjs/ui';
import pkg from '../package.json';
import { defaultPluginConfig, SHEETS_SORT_UI_PLUGIN_CONFIG_KEY } from './config/config';
import { MobileComponentsController } from './controllers/mobile/components.controller';
import { SheetsSortMobileUIController } from './controllers/mobile/ui.controller';
import { SheetsSortUIService } from './services/sheets-sort-ui.service';
@DependentOn(UniverSheetsPlugin, UniverSheetsSortPlugin, UniverSheetsMobileUIPlugin, UniverMobileUIPlugin)
export class UniverSheetsSortMobileUIPlugin extends Plugin {
static override type = UniverInstanceType.UNIVER_SHEET;
static override pluginName = 'SHEET_SORT_UI_PLUGIN';
static override packageName = pkg.name;
static override version = pkg.version;
constructor(
private readonly _config: Partial<IUniverSheetsSortUIConfig> = defaultPluginConfig,
@Inject(Injector) protected override readonly _injector: Injector,
@IConfigService private readonly _configService: IConfigService
) {
super();
const { ...rest } = merge({}, defaultPluginConfig, this._config);
this._configService.setConfig(SHEETS_SORT_UI_PLUGIN_CONFIG_KEY, rest);
}
override onStarting(): void {
registerDependencies(this._injector, [
[MobileComponentsController],
[SheetsSortUIService],
[SheetsSortMobileUIController],
]);
touchDependencies(this._injector, [[MobileComponentsController]]);
}
override onRendered(): void {
touchDependencies(this._injector, [[SheetsSortMobileUIController]]);
}
}

View File

@@ -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 { Dependency } from '@univerjs/core';
import type { IUniverSheetsSortUIConfig } from './config/config';
import {
DependentOn,
IConfigService,
Inject,
Injector,
merge,
Plugin,
UniverInstanceType,
} from '@univerjs/core';
import { UniverSheetsPlugin } from '@univerjs/sheets';
import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort';
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
import pkg from '../package.json';
import { defaultPluginConfig, SHEETS_SORT_UI_PLUGIN_CONFIG_KEY } from './config/config';
import { ComponentsController } from './controllers/components.controller';
import { SheetsSortUIController } from './controllers/ui.controller';
import { SheetsSortUIService } from './services/sheets-sort-ui.service';
@DependentOn(UniverSheetsPlugin, UniverSheetsSortPlugin, UniverSheetsUIPlugin)
export class UniverSheetsSortUIPlugin extends Plugin {
static override type = UniverInstanceType.UNIVER_SHEET;
static override pluginName = 'SHEET_SORT_UI_PLUGIN';
static override packageName = pkg.name;
static override version = pkg.version;
constructor(
private readonly _config: Partial<IUniverSheetsSortUIConfig> = defaultPluginConfig,
@Inject(Injector) protected readonly _injector: Injector,
@IConfigService private readonly _configService: IConfigService
) {
super();
// Manage the plugin configuration.
const { ...rest } = merge(
{},
defaultPluginConfig,
this._config
);
this._configService.setConfig(SHEETS_SORT_UI_PLUGIN_CONFIG_KEY, rest);
}
override onStarting(): void {
this._injector.add([ComponentsController]);
this._injector.get(ComponentsController);
([
[SheetsSortUIService],
[SheetsSortUIController],
] as Dependency[]).forEach((d) => this._injector.add(d));
}
override onRendered(): void {
this._injector.get(SheetsSortUIController);
}
}

View File

@@ -0,0 +1,280 @@
/**
* 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 {
CommandService,
ConfigService,
ContextService,
DesktopLogService,
Disposable,
ICommandService,
IConfigService,
IConfirmService,
IContextService,
ILogService,
Injector,
IUniverInstanceService,
LocaleService,
RANGE_TYPE,
TestConfirmService,
UniverInstanceService,
Workbook,
} from '@univerjs/core';
import { FormulaDataModel, LexerTreeBuilder } from '@univerjs/engine-formula';
import { SheetsSelectionsService } from '@univerjs/sheets';
import { SheetsSortService, SortType } from '@univerjs/sheets-sort';
import { Subject } from 'rxjs';
import { describe, expect, it } from 'vitest';
import zhCN from '../../locale/zh-CN';
import { EXTEND_TYPE, SheetsSortUIService } from '../sheets-sort-ui.service';
class RecordingCommandService {
readonly commands: Array<{ id: string; params?: unknown }> = [];
executeCommand(id: string, params?: unknown): Promise<boolean> {
this.commands.push({ id, params });
return Promise.resolve(true);
}
registerCommand(_command: ICommand): Disposable {
return new Disposable();
}
}
class RecordingConfirmService {
readonly confirmOptions$ = new Subject<unknown[]>();
readonly confirmations: unknown[] = [];
result = true;
extendType = EXTEND_TYPE.KEEP;
open(params: unknown): Disposable {
this.confirmations.push(params);
return new Disposable();
}
confirm(params: { children?: { title?: { props?: { onChange?: (value: string) => void } } } }): Promise<boolean> {
this.confirmations.push(params);
if (this.extendType === EXTEND_TYPE.EXTEND) {
params.children?.title?.props?.onChange?.('1');
}
return Promise.resolve(this.result);
}
close(_id: string): void {}
}
class RecordingSheetsSortService {
readonly appliedSorts: Array<{ option: unknown; unitId?: string; subUnitId?: string }> = [];
failedCheck: 'single' | 'merge' | 'formula' | 'empty' | null = null;
singleCheck(): boolean {
return this.failedCheck !== 'single';
}
mergeCheck(): boolean {
return this.failedCheck !== 'merge';
}
formulaCheck(): boolean {
return this.failedCheck !== 'formula';
}
emptyCheck(): boolean {
return this.failedCheck !== 'empty';
}
applySort(option: unknown, unitId?: string, subUnitId?: string): void {
this.appliedSorts.push({ option, unitId, subUnitId });
}
}
function createService(useRecordingServices = false) {
const injector = new Injector();
injector.add([ILogService, { useClass: DesktopLogService }]);
injector.add([IConfigService, { useClass: ConfigService }]);
injector.add([IContextService, { useClass: ContextService }]);
injector.add([IUniverInstanceService, { useClass: UniverInstanceService }]);
injector.add([ICommandService, { useClass: (useRecordingServices ? RecordingCommandService : CommandService) as never }]);
injector.add([IConfirmService, { useClass: (useRecordingServices ? RecordingConfirmService : TestConfirmService) as never }]);
injector.add([LexerTreeBuilder]);
injector.add([FormulaDataModel]);
injector.add([SheetsSelectionsService]);
if (useRecordingServices) {
injector.add([SheetsSortService, { useClass: RecordingSheetsSortService as never }]);
} else {
injector.add([SheetsSortService]);
}
injector.add([LocaleService]);
injector.add([SheetsSortUIService]);
injector.get(LocaleService).load({ zhCN });
const univerInstanceService = injector.get(IUniverInstanceService) as UniverInstanceService;
const workbook = injector.createInstance(Workbook, {
id: 'unit-1',
sheets: {
'sheet-1': {
id: 'sheet-1',
cellData: {
0: {
1: { v: '0:1' },
2: { v: '0:2' },
3: { v: '0:3' },
},
},
},
},
sheetOrder: ['sheet-1'],
});
univerInstanceService.__addUnit(workbook);
univerInstanceService.focusUnit('unit-1');
return {
injector,
service: injector.get(SheetsSortUIService),
sortService: injector.get(SheetsSortService) as unknown as RecordingSheetsSortService,
commandService: injector.get(ICommandService) as unknown as RecordingCommandService,
confirmService: injector.get(IConfirmService) as unknown as RecordingConfirmService,
selectionsService: injector.get(SheetsSelectionsService),
};
}
describe('SheetsSortUIService', () => {
it('tracks the custom sort panel location and derives column titles from the selected range', () => {
const { service } = createService();
const location = {
unitId: 'unit-1',
subUnitId: 'sheet-1',
colIndex: 1,
range: { startRow: 0, endRow: 4, startColumn: 1, endColumn: 3 },
};
service.showCustomSortPanel(location);
expect(service.customSortState()).toEqual({ location, show: true });
expect(service.getTitles(true)).toEqual([
{ index: 1, label: '0:1' },
{ index: 2, label: '0:2' },
{ index: 3, label: '0:3' },
]);
service.closeCustomSortPanel();
expect(service.customSortState()).toEqual({ show: false });
});
it('sorts the current selection directly with the requested direction', async () => {
const { service, sortService, selectionsService } = createService(true);
selectionsService.addSelections([{
range: { startRow: 0, endRow: 2, startColumn: 1, endColumn: 3, rangeType: RANGE_TYPE.NORMAL },
primary: {
actualRow: 0,
actualColumn: 2,
startRow: 0,
endRow: 2,
startColumn: 1,
endColumn: 3,
isMerged: false,
isMergedMainCell: false,
},
style: null,
}]);
expect(await service.triggerSortDirectly(false, false)).toBe(true);
expect(sortService.appliedSorts).toEqual([{
unitId: 'unit-1',
subUnitId: 'sheet-1',
option: {
orderRules: [{ type: SortType.DESC, colIndex: 2 }],
range: { startRow: 0, endRow: 2, startColumn: 1, endColumn: 3, rangeType: RANGE_TYPE.NORMAL },
},
}]);
});
it('extends a partial selection before opening custom sort when the user chooses extend', async () => {
const { service, sortService, commandService, confirmService, selectionsService } = createService(true);
confirmService.extendType = EXTEND_TYPE.EXTEND;
selectionsService.addSelections([{
range: { startRow: 0, endRow: 0, startColumn: 1, endColumn: 1, rangeType: RANGE_TYPE.NORMAL },
primary: {
actualRow: 0,
actualColumn: 1,
startRow: 0,
endRow: 0,
startColumn: 1,
endColumn: 1,
isMerged: false,
isMergedMainCell: false,
},
style: null,
}]);
expect(await service.triggerSortCustomize()).toBe(true);
expect(commandService.commands[0]).toMatchObject({
id: 'sheet.operation.set-selections',
params: {
selections: [{
range: { startRow: 0, endRow: 0, startColumn: 1, endColumn: 3 },
}],
},
});
expect(service.customSortState()?.location?.range).toMatchObject({
startRow: 0,
endRow: 0,
startColumn: 1,
endColumn: 3,
});
expect(sortService.appliedSorts).toEqual([]);
});
it('cancels custom sort when the extend prompt is rejected', async () => {
const { service, confirmService, selectionsService } = createService(true);
confirmService.result = false;
selectionsService.addSelections([{
range: { startRow: 0, endRow: 0, startColumn: 1, endColumn: 1, rangeType: RANGE_TYPE.NORMAL },
primary: {
actualRow: 0,
actualColumn: 1,
startRow: 0,
endRow: 0,
startColumn: 1,
endColumn: 1,
isMerged: false,
isMergedMainCell: false,
},
style: null,
}]);
expect(await service.triggerSortCustomize()).toBe(false);
expect(service.customSortState()).toBeNull();
});
it('blocks sorting and shows a check error when the selected range is invalid', async () => {
const { service, sortService, confirmService } = createService(true);
const location = {
unitId: 'unit-1',
subUnitId: 'sheet-1',
colIndex: 1,
range: { startRow: 0, endRow: 0, startColumn: 1, endColumn: 3 },
};
sortService.failedCheck = 'single';
expect(await service.triggerSortDirectly(true, false, location)).toBe(false);
expect(sortService.appliedSorts).toEqual([]);
expect(confirmService.confirmations).toMatchObject([{ id: 'sort-range-check-error' }]);
});
});

View File

@@ -0,0 +1,278 @@
/**
* 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 { IRange, Nullable, Workbook, Worksheet } from '@univerjs/core';
import type { ISheetRangeLocation } from '@univerjs/sheets';
import type { ISortOption } from '@univerjs/sheets-sort';
import type { LocaleKey } from '../locale/types';
import {
Disposable,
ICommandService,
IConfirmService,
Inject,
IUniverInstanceService,
LocaleService,
LocaleType,
Tools,
UniverInstanceType,
} from '@univerjs/core';
import { expandToContinuousRange, getPrimaryForRange, SetSelectionsOperation, SheetsSelectionsService } from '@univerjs/sheets';
import { SheetsSortService, SortType } from '@univerjs/sheets-sort';
import { BehaviorSubject } from 'rxjs';
import { createExtendConfirm } from '../views/ExtendConfirm';
import { createSortCheckError } from '../views/SortCheckError';
export enum EXTEND_TYPE {
KEEP = 'keep',
EXTEND = 'extend',
CANCEL = 'cancel',
}
export interface ICustomSortState {
location?: ISheetSortLocation;
show: boolean;
}
export interface ISheetSortLocation extends ISheetRangeLocation {
colIndex: number;
}
const SORT_ERROR_MESSAGE = {
MERGE_ERROR: 'sheets-sort-ui.error.merge-size',
EMPTY_ERROR: 'sheets-sort-ui.error.empty',
SINGLE_ERROR: 'sheets-sort-ui.error.single',
FORMULA_ARRAY: 'sheets-sort-ui.error.formula-array',
};
export class SheetsSortUIService extends Disposable {
private readonly _customSortState$ = new BehaviorSubject<Nullable<ICustomSortState>>(null);
readonly customSortState$ = this._customSortState$.asObservable();
constructor(
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
@IConfirmService private readonly _confirmService: IConfirmService,
@Inject(SheetsSelectionsService) private readonly _selectionManagerService: SheetsSelectionsService,
@Inject(SheetsSortService) private readonly _sheetsSortService: SheetsSortService,
@Inject(LocaleService) private readonly _localeService: LocaleService,
@ICommandService private readonly _commandService: ICommandService
) {
super();
}
async triggerSortDirectly(asc: boolean, extend: boolean, sheetRangeLocation?: ISheetSortLocation): Promise<boolean> {
const location = sheetRangeLocation || await this._detectSortLocation(extend);
if (!location) {
return false;
}
const check = this._check(location);
if (!check) {
return false;
}
const sortOption: ISortOption = {
orderRules: [{
type: asc ? SortType.ASC : SortType.DESC,
colIndex: location.colIndex,
}],
range: location.range,
};
this._sheetsSortService.applySort(sortOption, location.unitId, location.subUnitId);
return true;
}
async triggerSortCustomize() {
const location = await this._detectSortLocation();
if (!location) {
return false;
}
const check = this._check(location);
if (!check) {
return false;
}
// open customize dialog
this.showCustomSortPanel(location);
return true;
}
customSortState() {
return this._customSortState$.getValue();
}
getTitles(hasTitle: boolean) {
const location = this.customSortState()?.location;
if (!location) {
return [];
}
const { unitId, subUnitId, range } = location;
const worksheet = (this._univerInstanceService.getUnit(unitId) as Workbook)?.getSheetBySheetId(subUnitId);
if (!worksheet) {
return [];
}
const colTranslator = colIndexTranslator(this._localeService);
return Array.from({ length: range.endColumn - range.startColumn + 1 }, (_, i) => {
const cellValue = worksheet.getCell(range.startRow, i + range.startColumn)?.v;
return {
index: i + range.startColumn,
label: hasTitle ?
`${cellValue ?? colTranslator(i + range.startColumn)}` :
colTranslator(i + range.startColumn),
};
});
}
setSelection(unitId: string, subUnitId: string, range: IRange) {
const worksheet = (this._univerInstanceService.getUnit(unitId) as Workbook)?.getSheetBySheetId(subUnitId);
if (!worksheet) {
return;
}
const setSelectionsOperationParams = {
unitId,
subUnitId,
selections: [{ range, primary: getPrimaryForRange(range, worksheet), style: null }],
};
this._commandService.executeCommand(SetSelectionsOperation.id, setSelectionsOperationParams);
}
async showCheckError(content: string): Promise<boolean> {
return await this._confirmService.confirm({
id: 'sort-range-check-error',
title: {
title: this._localeService.t<LocaleKey>('sheets-sort-ui.info.tooltip'),
},
children: {
title: createSortCheckError(this._localeService.t(content)),
},
cancelText: this._localeService.t<LocaleKey>('sheets-sort-ui.dialog.cancel'),
confirmText: this._localeService.t<LocaleKey>('sheets-sort-ui.dialog.confirm'),
});
}
async showExtendConfirm(): Promise<EXTEND_TYPE> {
let shouldExtend = false;
const confirm = await this._confirmService.confirm({
id: 'extend-sort-range-dialog',
title: {
title: this._localeService.t<LocaleKey>('sheets-sort-ui.dialog.sort-reminder'),
},
children: {
title: createExtendConfirm((value: string) => {
shouldExtend = value === '1';
}),
},
width: 400,
cancelText: this._localeService.t<LocaleKey>('sheets-sort-ui.dialog.cancel'),
confirmText: this._localeService.t<LocaleKey>('sheets-sort-ui.dialog.confirm'),
});
if (confirm) {
return shouldExtend ? EXTEND_TYPE.EXTEND : EXTEND_TYPE.KEEP;
}
return EXTEND_TYPE.CANCEL;
}
showCustomSortPanel(location: ISheetSortLocation) {
this._customSortState$.next({ location, show: true });
}
closeCustomSortPanel() {
this._customSortState$.next({ show: false });
}
private _check(location: ISheetSortLocation) {
const singleCheck = this._sheetsSortService.singleCheck(location);
if (!singleCheck) {
this.showCheckError(SORT_ERROR_MESSAGE.SINGLE_ERROR);
return false;
}
const mergeCheck = this._sheetsSortService.mergeCheck(location);
if (!mergeCheck) {
this.showCheckError(SORT_ERROR_MESSAGE.MERGE_ERROR);
return false;
}
const formulaCheck = this._sheetsSortService.formulaCheck(location);
if (!formulaCheck) {
this.showCheckError(SORT_ERROR_MESSAGE.FORMULA_ARRAY);
return false;
}
const emptyCheck = this._sheetsSortService.emptyCheck(location);
if (!emptyCheck) {
this.showCheckError(SORT_ERROR_MESSAGE.EMPTY_ERROR);
return false;
}
return true;
}
private async _detectSortLocation(extend?: boolean): Promise<Nullable<ISheetSortLocation>> {
const workbook = this._univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_SHEET) as Workbook;
const worksheet = workbook.getActiveSheet() as Worksheet;
const unitId = workbook.getUnitId();
const subUnitId = worksheet.getSheetId();
const selection = this._selectionManagerService.getCurrentLastSelection();
if (!selection) {
return null;
}
let range: IRange;
if (extend === true) {
range = expandToContinuousRange(selection.range, { up: true, down: true, left: true, right: true }, worksheet);
this.setSelection(unitId, subUnitId, range);
} else if (extend === false) {
range = selection.range;
} else {
const confirmRes = await this.showExtendConfirm();
if (confirmRes === EXTEND_TYPE.CANCEL) {
return null;
}
if (confirmRes === EXTEND_TYPE.KEEP) {
range = selection.range;
} else {
range = expandToContinuousRange(selection.range, { up: true, down: true, left: true, right: true }, worksheet);
this.setSelection(unitId, subUnitId, range);
}
}
return {
range,
unitId,
subUnitId,
colIndex: selection.primary.actualColumn,
};
}
}
function colIndexTranslator(localeService: LocaleService) {
return (colIndex: number) => {
const colName = Tools.chatAtABC(colIndex);
const currentLocale = localeService.getCurrentLocale();
switch (currentLocale) {
case LocaleType.ZH_CN:
return `"${colName}"列`;
case LocaleType.EN_US:
return `Column "${colName}"`;
default:
return `Column "${colName}"`;
}
};
}

View File

@@ -0,0 +1,335 @@
/**
* 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 { IRange, Nullable } from '@univerjs/core';
import type { IOrderRule } from '@univerjs/sheets-sort';
import type { LocaleKey } from '../locale/types';
import type { ICustomSortState } from '../services/sheets-sort-ui.service';
import { LocaleService, throttle } from '@univerjs/core';
import {
Button,
Checkbox,
clsx,
DraggableList,
Dropdown,
Radio,
RadioGroup,
scrollbarClassName,
} from '@univerjs/design';
import { CheckMarkIcon, DeleteEmptyIcon, GripVerticalIcon, IncreaseIcon, MoreDownIcon } from '@univerjs/icons';
import { SheetsSortService, SortType } from '@univerjs/sheets-sort';
import { useDependency, useObservable } from '@univerjs/ui';
import { useCallback, useEffect, useRef, useState } from 'react';
import { SheetsSortUIService } from '../services/sheets-sort-ui.service';
export interface ICustomSortPanelProps {
range: IRange;
onListChange: (value: IOrderRule[]) => void;
}
export function CustomSortPanel() {
const sheetsSortUIService = useDependency(SheetsSortUIService);
const state = useObservable(sheetsSortUIService.customSortState$);
if (!state || !state.location) {
return null;
}
return <CustomSortPanelImpl state={state} />;
}
function CustomSortPanelImpl({ state }: { state: ICustomSortState }) {
const sheetsSortService = useDependency(SheetsSortService);
const localeService = useDependency(LocaleService);
const sheetsSortUIService = useDependency(SheetsSortUIService);
const [hasTitle, setHasTitle] = useState(false);
const [scrollPosition, setScrollPosition] = useState(0);
const listEndRef = useRef<HTMLDivElement>(null);
const { range, unitId, subUnitId } = state.location!;
const titles = sheetsSortUIService.getTitles(hasTitle);
const [list, setList] = useState<IOrderRule[]>([
{ type: SortType.ASC, colIndex: range.startColumn },
]);
const onItemChange = useCallback((index: number, value: Nullable<IOrderRule>) => {
const newList = [...list];
if (value === null) {
newList.splice(index, 1);
} else {
newList[index] = value as IOrderRule;
}
setList(newList as IOrderRule[]);
}, [list]);
const newItem = useCallback(
throttle(() => {
const newList = [...list];
const nextColIndex = findNextColIndex(range, list);
if (nextColIndex !== null) {
newList.push({ type: SortType.ASC, colIndex: nextColIndex });
setList(newList);
}
}, 200),
[list, range]
);
const apply = useCallback((orderRules: IOrderRule[], hasTitle: boolean) => {
sheetsSortService.applySort({ range, orderRules, hasTitle });
sheetsSortUIService.closeCustomSortPanel();
}, [sheetsSortService, sheetsSortUIService, range]);
const cancel = useCallback(() => {
sheetsSortUIService.closeCustomSortPanel();
}, [sheetsSortUIService]);
const setTitle = useCallback((value: boolean) => {
setHasTitle(value);
if (value) {
sheetsSortUIService.setSelection(unitId, subUnitId, { ...range, startRow: range.startRow + 1 });
} else {
sheetsSortUIService.setSelection(unitId, subUnitId, range);
}
}, [sheetsSortUIService, range, subUnitId, unitId]);
useEffect(() => {
if (listEndRef.current && list.length > 5) {
listEndRef.current.scrollTop = listEndRef.current.scrollHeight;
}
}, [list]);
const canNew = list.length < titles.length;
const dragList = list.map((item) => ({ ...item, id: `${item.colIndex}` }));
return (
<div>
<div onMouseDown={(e) => { e.stopPropagation(); }}>
<div className="univer-mb-2 univer-flex univer-items-center univer-justify-between">
<Checkbox checked={hasTitle} onChange={(value) => setTitle(!!value)}>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.first-row-check')}
</Checkbox>
{canNew
? (
<div
className={`
univer-flex univer-cursor-pointer univer-select-none univer-items-center
univer-text-base
`}
onClick={newItem}
>
<IncreaseIcon />
<span className="univer-ml-1.5">{localeService.t<LocaleKey>('sheets-sort-ui.dialog.add-condition')}</span>
</div>
)
: (
<div
className={`
univer-flex univer-cursor-pointer univer-select-none univer-items-center
univer-text-base univer-text-primary-500
disabled:univer-cursor-not-allowed disabled:univer-divide-opacity-30
disabled:univer-text-gray-800
`}
>
<IncreaseIcon />
<span className="univer-ml-1.5 univer-text-xs">{localeService.t<LocaleKey>('sheets-sort-ui.dialog.add-condition')}</span>
</div>
)}
</div>
<div
ref={listEndRef}
className={clsx('univer-max-h-[310px] univer-overflow-y-auto univer-overflow-x-hidden', scrollbarClassName)}
onScroll={(e) => {
const position = e.currentTarget.scrollTop;
setScrollPosition(position);
}}
>
<DraggableList
list={dragList}
onListChange={setList}
idKey="id"
draggableHandle="[data-u-comp=sort-panel-item-handler]"
itemRender={(item) => (
<SortOptionItem
titles={titles}
list={dragList}
item={item}
onChange={(value, index) => onItemChange(index, value)}
scrollPosition={scrollPosition}
/>
)}
rowHeight={32}
margin={[0, 12]}
/>
</div>
</div>
<div className="univer-mt-5 univer-flex univer-justify-end">
<Button
className="univer-ml-3"
onClick={() => cancel()}
>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.cancel')}
</Button>
<Button
className="univer-ml-3"
variant="primary"
onClick={() => apply(list, hasTitle)}
>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.confirm')}
</Button>
</div>
</div>
);
}
interface ISortOptionItemProps {
titles: { index: number; label: string }[];
list: IOrderRule[];
item: IOrderRule;
scrollPosition: number;
onChange: (value: Nullable<IOrderRule>, index: number) => void;
}
export function SortOptionItem(props: ISortOptionItemProps) {
const { list, item, titles, onChange, scrollPosition } = props;
const localeService = useDependency(LocaleService);
const availableMenu = titles.filter((title) => (!list.some((item) => item.colIndex === title.index)) || title.index === item.colIndex);
const currentIndex = list.findIndex((listItem) => listItem.colIndex === item.colIndex);
const handleChangeColIndex = useCallback((menuItem: { index: number; label: string }) => {
onChange({ ...item, colIndex: menuItem.index }, currentIndex);
setVisible(false);
}, [currentIndex, item, onChange]);
const [visible, setVisible] = useState(false);
const onVisibleChange = (visible: boolean) => {
setVisible(visible);
};
useEffect(() => {
setVisible(false);
}, [scrollPosition]);
const showDelete = list.length > 1;
const itemLabel = titles.find((title) => title.index === item.colIndex)?.label;
return (
<div className="univer-grid univer-grid-flow-col univer-grid-cols-2 univer-items-center univer-gap-2">
<div className="univer-flex univer-items-center">
<div
data-u-comp="sort-panel-item-handler"
className={`
univer-flex univer-cursor-pointer univer-items-center univer-justify-center univer-text-base
univer-text-gray-700
`}
>
<GripVerticalIcon />
</div>
{/* TODO@wzhudev: change it to the Select component later. */}
<Dropdown
overlay={(
<ul
className={clsx(`
univer-my-0 univer-box-border univer-grid univer-max-h-[310px]
univer-w-[--radix-popper-anchor-width] univer-items-center univer-gap-1
univer-overflow-y-auto univer-overflow-x-hidden univer-rounded-lg univer-border
univer-bg-gray-0 univer-p-1 univer-text-base univer-shadow-lg
`, scrollbarClassName)}
>
{availableMenu.map((menuItem) => (
<li
key={menuItem.index}
onClick={() => handleChangeColIndex(menuItem)}
className={`
univer-relative univer-box-border univer-flex univer-h-7 univer-cursor-pointer
univer-list-none univer-items-center univer-justify-between univer-rounded
univer-px-2 univer-text-sm univer-transition-all
hover:univer-bg-gray-100
dark:hover:!univer-bg-gray-700
`}
>
<span className="univer-max-w-[220px] univer-truncate">
{menuItem.label}
</span>
<span>
{menuItem.index === item.colIndex && (
<CheckMarkIcon />
)}
</span>
</li>
))}
</ul>
)}
open={visible}
onOpenChange={onVisibleChange}
>
<div
className={clsx(`
univer-ml-2 univer-flex univer-w-full univer-items-center univer-justify-between
univer-overflow-hidden univer-rounded-md univer-py-1.5 univer-text-sm univer-text-gray-900
dark:!univer-text-gray-0
`)}
>
<span
className="univer-max-w-[220px] univer-truncate"
>
{itemLabel}
</span>
<MoreDownIcon />
</div>
</Dropdown>
</div>
<div className="univer-flex univer-items-center univer-justify-end univer-gap-2">
<RadioGroup
value={item.type}
onChange={(value) => {
onChange({ ...item, type: value as SortType }, currentIndex);
}}
>
<Radio value={SortType.ASC}>{localeService.t<LocaleKey>('sheets-sort-ui.general.sort-asc')}</Radio>
<Radio value={SortType.DESC}>{localeService.t<LocaleKey>('sheets-sort-ui.general.sort-desc')}</Radio>
</RadioGroup>
<a
className={`
univer-flex univer-cursor-pointer univer-items-center univer-text-sm univer-transition-colors
hover:univer-text-red-500
`}
onClick={() => onChange(null, currentIndex)}
>
{!showDelete && <DeleteEmptyIcon />}
</a>
</div>
</div>
);
}
function findNextColIndex(range: IRange, list: Nullable<IOrderRule>[]): number | null {
const { startColumn, endColumn } = range;
const used = new Set(list.map((item) => item?.colIndex));
for (let i = startColumn; i <= endColumn; i++) {
if (!used.has(i)) {
return i;
}
}
return null;
}

View File

@@ -0,0 +1,64 @@
/**
* 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 { IRange } from '@univerjs/core';
import type { LocaleKey } from '../locale/types';
import { IUniverInstanceService, LocaleService } from '@univerjs/core';
import { Button, ButtonGroup } from '@univerjs/design';
import { AscendingIcon, DescendingIcon } from '@univerjs/icons';
import { getSheetCommandTarget } from '@univerjs/sheets';
import { useDependency } from '@univerjs/ui';
import { useCallback } from 'react';
import { SheetsSortUIService } from '../services/sheets-sort-ui.service';
export interface IEmbedSortBtnProps {
range: IRange;
colIndex: number;
onClose: () => void;
}
export default function EmbedSortBtn(props: IEmbedSortBtnProps) {
const { range, colIndex, onClose } = props;
const sheetsSortUIService = useDependency(SheetsSortUIService);
const univerInstanceService = useDependency(IUniverInstanceService);
const localeService = useDependency(LocaleService);
const apply = useCallback((asc: boolean) => {
const { unitId, subUnitId } = getSheetCommandTarget(univerInstanceService) || {};
if (range && unitId && subUnitId) {
const noTitleRange = { ...range, startRow: range.startRow + 1 };
sheetsSortUIService.triggerSortDirectly(asc, false, { unitId, subUnitId, range: noTitleRange, colIndex });
} else {
throw new Error(`Cannot find the target to sort. unitId: ${unitId}, subUnitId: ${subUnitId}, range: ${range}, colIndex: ${colIndex}`);
}
onClose();
}, [range, colIndex, sheetsSortUIService, univerInstanceService, onClose]);
return (
<ButtonGroup className="univer-mb-3 univer-w-full univer-grid-cols-2">
<Button onClick={() => apply(true)}>
<AscendingIcon />
{localeService.t<LocaleKey>('sheets-sort-ui.general.sort-asc')}
</Button>
<Button onClick={() => apply(false)}>
<DescendingIcon />
{localeService.t<LocaleKey>('sheets-sort-ui.general.sort-desc')}
</Button>
</ButtonGroup>
);
}

View File

@@ -0,0 +1,58 @@
/**
* 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 { Radio, RadioGroup } from '@univerjs/design';
import { useDependency } from '@univerjs/ui';
import { useState } from 'react';
export interface IExtendConfirmProps {
onChange: (value: string) => void;
};
export const ExtendConfirm = (props: IExtendConfirmProps) => {
const [extend, setExtend] = useState<string>('0');
const localeService = useDependency(LocaleService);
return (
<div className="univer-text-sm">
<div className="extend-confirm-desc">
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.sort-reminder-desc')}
</div>
<RadioGroup
className="univer-mt-4"
value={extend}
direction="vertical"
onChange={(value) => {
setExtend(value as string);
props.onChange(value as string);
}}
>
<Radio value="0">
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.sort-reminder-no')}
</Radio>
<Radio value="1">
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.sort-reminder-ext')}
</Radio>
</RadioGroup>
</div>
);
};
export function createExtendConfirm(onChange: IExtendConfirmProps['onChange']) {
return <ExtendConfirm onChange={onChange} />;
}

View 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.
*/
export interface ISortCheckErrorProps {
message: string;
}
export const SortCheckError = (props: ISortCheckErrorProps) => {
return <div>{props.message}</div>;
};
export function createSortCheckError(message: string) {
return <SortCheckError message={message} />;
}

View File

@@ -0,0 +1,404 @@
/**
* 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, IDisposable, IWorkbookData, Workbook, Worksheet } from '@univerjs/core';
import type { Root } from 'react-dom/client';
import {
awaitTime,
ICommandService,
IConfirmService,
ILogService,
Inject,
Injector,
IUniverInstanceService,
LocaleService,
LocaleType,
LogLevel,
Plugin,
toDisposable,
Univer,
UniverInstanceType,
} from '@univerjs/core';
import { FormulaDataModel, LexerTreeBuilder } from '@univerjs/engine-formula';
import {
ReorderRangeCommand,
ReorderRangeMutation,
SetSelectionsOperation,
SheetInterceptorService,
SheetSkeletonService,
SheetsSelectionsService,
} from '@univerjs/sheets';
import { SheetsSortController, SheetsSortService } from '@univerjs/sheets-sort';
import { RediContext } from '@univerjs/ui';
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { Subject } from 'rxjs';
import { afterEach, describe, expect, it } from 'vitest';
import enUS from '../../locale/en-US';
import { SheetsSortUIService } from '../../services/sheets-sort-ui.service';
import { CustomSortPanel } from '../CustomSortPanel';
import EmbedSortBtn from '../EmbedSortBtn';
import { ExtendConfirm } from '../ExtendConfirm';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const UNIT_ID = 'test';
const SUB_UNIT_ID = 'sheet1';
class TestConfirmService implements IConfirmService {
readonly confirmOptions$ = new Subject<unknown[]>();
open(): IDisposable {
return toDisposable(() => undefined);
}
confirm(): Promise<boolean> {
return Promise.resolve(true);
}
close() {
// The tested sort paths close panels through SheetsSortUIService.
}
}
function createWorkbookData(): IWorkbookData {
return {
id: UNIT_ID,
appVersion: '3.0.0-alpha',
locale: LocaleType.EN_US,
name: 'sort view test',
sheetOrder: [SUB_UNIT_ID],
styles: {},
sheets: {
[SUB_UNIT_ID]: {
id: SUB_UNIT_ID,
name: 'Sheet1',
rowCount: 20,
columnCount: 20,
cellData: {
0: {
0: { v: 'Name' },
1: { v: 'Score' },
2: { v: 'Region' },
},
1: {
0: { v: 'Cat' },
1: { v: 20, t: 2 },
2: { v: 'North' },
},
2: {
0: { v: 'Amy' },
1: { v: 10, t: 2 },
2: { v: 'East' },
},
3: {
0: { v: 'Ben' },
1: { v: 30, t: 2 },
2: { v: 'West' },
},
},
},
},
};
}
function createSortViewTestBed() {
const univer = new Univer();
const injector = univer.__getInjector();
class TestPlugin extends Plugin {
static override pluginName = 'sheets-sort-ui-view-test-plugin';
static override type = UniverInstanceType.UNIVER_SHEET;
constructor(
_config: undefined,
@Inject(Injector) protected readonly _injector: Injector
) {
super();
}
override onStarting(): void {
([
[SheetsSelectionsService],
[SheetInterceptorService],
[SheetSkeletonService],
[FormulaDataModel],
[LexerTreeBuilder],
[SheetsSortController],
[SheetsSortService],
[SheetsSortUIService],
[IConfirmService, { useClass: TestConfirmService }],
] as Dependency[]).forEach((dependency) => this._injector.add(dependency));
}
override onReady(): void {
this._injector.get(SheetsSortController);
}
}
injector.get(LocaleService).load({ [LocaleType.EN_US]: enUS });
injector.get(LocaleService).setLocale(LocaleType.EN_US);
univer.registerPlugin(TestPlugin);
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
injector.get(IUniverInstanceService).focusUnit(UNIT_ID);
injector.get(ILogService).setLogLevel(LogLevel.SILENT);
const commandService = injector.get(ICommandService);
[SetSelectionsOperation, ReorderRangeMutation, ReorderRangeCommand].forEach((command) => commandService.registerCommand(command));
return {
univer,
injector,
workbook,
worksheet: workbook.getSheetBySheetId(SUB_UNIT_ID)!,
sortUIService: injector.get(SheetsSortUIService),
};
}
async function renderWithInjector(root: Root, testBed: ReturnType<typeof createSortViewTestBed>, node: React.ReactNode) {
await act(async () => {
root.render(
<RediContext.Provider value={{ injector: testBed.injector }}>
{node}
</RediContext.Provider>
);
await awaitTime(20);
});
}
function getColumnValues(worksheet: Worksheet, col: number, rows: number[]) {
return rows.map((row) => worksheet.getCell(row, col)?.v);
}
function getButton(container: HTMLElement, text: string) {
const button = Array.from(container.querySelectorAll('[data-u-comp="button"]'))
.find((item) => item.textContent === text);
expect(button).toBeTruthy();
return button as HTMLElement;
}
function getRadio(container: HTMLElement, text: string, position = 0) {
const radios = Array.from(container.querySelectorAll('[data-u-comp="radio"]'))
.filter((item) => item.textContent === text);
expect(radios[position]).toBeTruthy();
return radios[position].querySelector('input') as HTMLInputElement;
}
function getAddCondition(container: HTMLElement) {
const trigger = Array.from(container.querySelectorAll('div'))
.find((item) => item.textContent === 'Add condition');
expect(trigger).toBeTruthy();
return trigger as HTMLElement;
}
function setCellValue(worksheet: Worksheet, row: number, col: number, value: string | number) {
worksheet.getCellMatrix().setValue(row, col, { v: value, t: typeof value === 'number' ? 2 : 1 });
}
function setDuplicateNamesForConditionTests(worksheet: Worksheet) {
setCellValue(worksheet, 1, 0, 'Sam');
setCellValue(worksheet, 1, 1, 20);
setCellValue(worksheet, 2, 0, 'Amy');
setCellValue(worksheet, 2, 1, 10);
setCellValue(worksheet, 3, 0, 'Sam');
setCellValue(worksheet, 3, 1, 30);
}
describe('Sheets sort views', () => {
let root: Root | undefined;
let container: HTMLDivElement | undefined;
let currentTestBed: ReturnType<typeof createSortViewTestBed> | undefined;
afterEach(() => {
act(() => {
root?.unmount();
});
container?.remove();
currentTestBed?.univer.dispose();
root = undefined;
container = undefined;
currentTestBed = undefined;
});
it('sorts the selected data rows from the embedded descending action', async () => {
currentTestBed = createSortViewTestBed();
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
let closeCount = 0;
await renderWithInjector(
root,
currentTestBed, (
<EmbedSortBtn
range={{ startRow: 0, endRow: 3, startColumn: 0, endColumn: 2 }}
colIndex={1}
onClose={() => {
closeCount += 1;
}}
/>
));
await act(async () => {
getButton(container!, 'Descending').dispatchEvent(new MouseEvent('click', { bubbles: true }));
await awaitTime(20);
});
expect(getColumnValues(currentTestBed.worksheet, 0, [0, 1, 2, 3])).toEqual(['Name', 'Ben', 'Cat', 'Amy']);
expect(getColumnValues(currentTestBed.worksheet, 1, [0, 1, 2, 3])).toEqual(['Score', 30, 20, 10]);
expect(closeCount).toBe(1);
});
it('applies custom sort with the first row excluded from sorting', async () => {
currentTestBed = createSortViewTestBed();
currentTestBed.sortUIService.showCustomSortPanel({
unitId: UNIT_ID,
subUnitId: SUB_UNIT_ID,
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 2 },
colIndex: 1,
});
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
await renderWithInjector(root, currentTestBed, <CustomSortPanel />);
const titleCheckbox = container.querySelector('[data-u-comp="checkbox"] input') as HTMLInputElement;
expect(titleCheckbox).toBeTruthy();
await act(async () => {
titleCheckbox.click();
await awaitTime(20);
});
await act(async () => {
getButton(container!, 'Confirm').dispatchEvent(new MouseEvent('click', { bubbles: true }));
await awaitTime(20);
});
expect(getColumnValues(currentTestBed.worksheet, 0, [0, 1, 2, 3])).toEqual(['Name', 'Amy', 'Ben', 'Cat']);
expect(getColumnValues(currentTestBed.worksheet, 1, [0, 1, 2, 3])).toEqual(['Score', 10, 30, 20]);
expect(currentTestBed.sortUIService.customSortState()).toEqual({ show: false });
});
it('reports the selected extend range option through the rendered confirmation body', async () => {
currentTestBed = createSortViewTestBed();
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
const selectedOptions: string[] = [];
await renderWithInjector(
root,
currentTestBed,
<ExtendConfirm onChange={(value) => selectedOptions.push(value)} />
);
await act(async () => {
getRadio(container!, 'Extend range sorting').click();
await awaitTime(20);
});
await act(async () => {
getRadio(container!, 'Keep range sorting').click();
await awaitTime(20);
});
expect(selectedOptions).toEqual(['1', '0']);
});
it('applies an added custom sort condition as a secondary rule', async () => {
currentTestBed = createSortViewTestBed();
setDuplicateNamesForConditionTests(currentTestBed.worksheet);
currentTestBed.sortUIService.showCustomSortPanel({
unitId: UNIT_ID,
subUnitId: SUB_UNIT_ID,
range: { startRow: 1, endRow: 3, startColumn: 0, endColumn: 1 },
colIndex: 0,
});
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
await renderWithInjector(root, currentTestBed, <CustomSortPanel />);
await act(async () => {
getAddCondition(container!).click();
await awaitTime(260);
});
await act(async () => {
getRadio(container!, 'Descending', 1).click();
await awaitTime(20);
});
await act(async () => {
getButton(container!, 'Confirm').dispatchEvent(new MouseEvent('click', { bubbles: true }));
await awaitTime(20);
});
expect(getColumnValues(currentTestBed.worksheet, 0, [1, 2, 3])).toEqual(['Amy', 'Sam', 'Sam']);
expect(getColumnValues(currentTestBed.worksheet, 1, [1, 2, 3])).toEqual([10, 30, 20]);
expect(currentTestBed.sortUIService.customSortState()).toEqual({ show: false });
});
it('removes an added custom sort condition before confirming', async () => {
currentTestBed = createSortViewTestBed();
setDuplicateNamesForConditionTests(currentTestBed.worksheet);
currentTestBed.sortUIService.showCustomSortPanel({
unitId: UNIT_ID,
subUnitId: SUB_UNIT_ID,
range: { startRow: 1, endRow: 3, startColumn: 0, endColumn: 1 },
colIndex: 0,
});
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
await renderWithInjector(root, currentTestBed, <CustomSortPanel />);
await act(async () => {
getAddCondition(container!).click();
await awaitTime(260);
});
await act(async () => {
getRadio(container!, 'Descending', 1).click();
await awaitTime(20);
});
const removeLinks = Array.from(container.querySelectorAll('a'));
expect(removeLinks[1]).toBeTruthy();
await act(async () => {
removeLinks[1].dispatchEvent(new MouseEvent('click', { bubbles: true }));
await awaitTime(20);
});
expect(Array.from(container.querySelectorAll('[data-u-comp="radio"]'))
.filter((item) => item.textContent === 'Descending')).toHaveLength(1);
await act(async () => {
getButton(container!, 'Confirm').dispatchEvent(new MouseEvent('click', { bubbles: true }));
await awaitTime(20);
});
expect(getColumnValues(currentTestBed.worksheet, 0, [1, 2, 3])).toEqual(['Amy', 'Sam', 'Sam']);
expect(getColumnValues(currentTestBed.worksheet, 1, [1, 2, 3])).toEqual([10, 20, 30]);
expect(currentTestBed.sortUIService.customSortState()).toEqual({ show: false });
});
});

View File

@@ -0,0 +1,157 @@
/**
* 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 { IOrderRule } from '@univerjs/sheets-sort';
import type { LocaleKey } from '../../locale/types';
import type { ISheetSortLocation } from '../../services/sheets-sort-ui.service';
import { LocaleService } from '@univerjs/core';
import { Button, Checkbox, MobileSelect } from '@univerjs/design';
import { DeleteEmptyIcon } from '@univerjs/icons';
import { SheetsSortService, SortType } from '@univerjs/sheets-sort';
import { useDependency, useObservable } from '@univerjs/ui';
import { useState } from 'react';
import { SheetsSortUIService } from '../../services/sheets-sort-ui.service';
export function MobileCustomSortPanel() {
const sheetsSortUIService = useDependency(SheetsSortUIService);
const state = useObservable(sheetsSortUIService.customSortState$);
if (!state?.location) {
return null;
}
return <MobileCustomSortPanelContent location={state.location} />;
}
function MobileCustomSortPanelContent({ location }: { location: ISheetSortLocation }) {
const sheetsSortService = useDependency(SheetsSortService);
const sheetsSortUIService = useDependency(SheetsSortUIService);
const localeService = useDependency(LocaleService);
const { range, unitId, subUnitId } = location;
const [hasTitle, setHasTitle] = useState(false);
const [orderRules, setOrderRules] = useState<IOrderRule[]>([
{ type: SortType.ASC, colIndex: range.startColumn },
]);
const titles = sheetsSortUIService.getTitles(hasTitle);
const canAddCondition = orderRules.length < titles.length;
function updateRule(index: number, update: Partial<IOrderRule>) {
setOrderRules((current) => current.map((rule, ruleIndex) =>
ruleIndex === index ? { ...rule, ...update } : rule));
}
function addCondition() {
setOrderRules((current) => {
const nextTitle = titles.find((title) => !current.some((rule) => rule.colIndex === title.index));
return nextTitle
? [...current, { type: SortType.ASC, colIndex: nextTitle.index }]
: current;
});
}
function setTitle(value: boolean) {
setHasTitle(value);
sheetsSortUIService.setSelection(
unitId,
subUnitId,
value ? { ...range, startRow: range.startRow + 1 } : range
);
}
function apply() {
sheetsSortService.applySort({ range, orderRules, hasTitle }, unitId, subUnitId);
sheetsSortUIService.closeCustomSortPanel();
}
return (
<div className="univer-grid univer-gap-4">
<div className="univer-flex univer-items-center univer-justify-between univer-gap-3">
<Checkbox checked={hasTitle} onChange={(value) => setTitle(Boolean(value))}>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.first-row-check')}
</Checkbox>
<Button variant="text" disabled={!canAddCondition} onClick={addCondition}>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.add-condition')}
</Button>
</div>
<div className="univer-grid univer-gap-3">
{orderRules.map((rule, index) => {
const availableTitles = titles.filter((title) =>
title.index === rule.colIndex || !orderRules.some((item) => item.colIndex === title.index));
return (
<div
key={rule.colIndex}
className="
univer-grid univer-gap-2 univer-rounded-xl univer-bg-gray-50 univer-p-3
dark:!univer-bg-gray-800
"
>
<div className="univer-flex univer-items-center univer-gap-2">
<MobileSelect
className="!univer-min-w-0 univer-flex-1"
value={`${rule.colIndex}`}
options={availableTitles.map((title) => ({
label: title.label,
value: `${title.index}`,
}))}
onChange={(value) => updateRule(index, { colIndex: Number(value) })}
/>
{orderRules.length > 1 && (
<Button
size="icon"
variant="text"
aria-label={localeService.t<LocaleKey>('sheets-sort-ui.dialog.delete-condition')}
onClick={() => setOrderRules((current) => current.filter((_, ruleIndex) => ruleIndex !== index))}
>
<DeleteEmptyIcon />
</Button>
)}
</div>
<MobileSelect
className="univer-w-full"
value={rule.type}
options={[
{
label: localeService.t<LocaleKey>('sheets-sort-ui.general.sort-asc'),
value: SortType.ASC,
},
{
label: localeService.t<LocaleKey>('sheets-sort-ui.general.sort-desc'),
value: SortType.DESC,
},
]}
onChange={(value) => updateRule(index, { type: value as SortType })}
/>
</div>
);
})}
</div>
<div
className="
univer-sticky univer-bottom-0 univer-grid univer-grid-cols-2 univer-gap-3 univer-bg-gray-0 univer-py-3
dark:!univer-bg-gray-900
"
>
<Button size="large" onClick={() => sheetsSortUIService.closeCustomSortPanel()}>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.cancel')}
</Button>
<Button size="large" variant="primary" onClick={apply}>
{localeService.t<LocaleKey>('sheets-sort-ui.dialog.confirm')}
</Button>
</div>
</div>
);
}

View 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;

View File

@@ -0,0 +1,8 @@
{
"extends": "@univerjs-infra/shared/tsconfigs/base",
"compilerOptions": {
"rootDir": "src",
"outDir": "lib/types"
},
"include": ["src"]
}

View 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"
]
}

View File

@@ -0,0 +1,3 @@
import createConfig from '@univerjs-infra/shared/vitest';
export default createConfig();