snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
This commit is contained in:
41
packages/sheets-sort/README.md
Normal file
41
packages/sheets-sort/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# @univerjs/sheets-sort
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-sort)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-sort)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-sort)
|
||||
|
||||
`@univerjs/sheets-sort` adds the core sorting model, commands, and services for Univer Sheets.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-sort` | `UniverSheetsSort` | No | No | Yes |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-sort
|
||||
# or
|
||||
npm install @univerjs/sheets-sort
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort';
|
||||
|
||||
univer.registerPlugin(UniverSheetsSortPlugin);
|
||||
```
|
||||
|
||||
## Integration Notes
|
||||
|
||||
Use this package with `@univerjs/sheets-sort-ui` when users need sorting menus and panels.
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-sort)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
87
packages/sheets-sort/package.json
Normal file
87
packages/sheets-sort/package.json
Normal file
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-sort",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Sorting model, commands, and services 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",
|
||||
"spreadsheet",
|
||||
"plugin"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./facade": "./src/facade/index.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"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/engine-formula": "workspace:*",
|
||||
"@univerjs/sheets": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import {
|
||||
BooleanNumber,
|
||||
ILogService,
|
||||
Inject,
|
||||
Injector,
|
||||
IUniverInstanceService,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
Plugin,
|
||||
Tools,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { FormulaDataModel } from '@univerjs/engine-formula';
|
||||
import { SheetInterceptorService, SheetSkeletonService, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { SheetsSortController } from '../../../controllers/sheets-sort.controller';
|
||||
import { SheetsSortService } from '../../../services/sheets-sort.service';
|
||||
|
||||
const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
name: 'sheet1',
|
||||
cellData: {
|
||||
0: {
|
||||
0: {
|
||||
v: 1,
|
||||
},
|
||||
1: {
|
||||
v: 20,
|
||||
},
|
||||
2: {
|
||||
v: 100,
|
||||
},
|
||||
},
|
||||
1: {
|
||||
0: {
|
||||
v: 2,
|
||||
},
|
||||
1: {
|
||||
v: 19,
|
||||
},
|
||||
2: {
|
||||
v: 100,
|
||||
},
|
||||
},
|
||||
2: {
|
||||
0: {
|
||||
v: 3,
|
||||
},
|
||||
1: {
|
||||
v: 18,
|
||||
},
|
||||
2: {
|
||||
v: 100,
|
||||
},
|
||||
},
|
||||
3: {
|
||||
0: {
|
||||
v: 4,
|
||||
},
|
||||
1: {
|
||||
v: 17,
|
||||
},
|
||||
2: {
|
||||
v: 200,
|
||||
},
|
||||
},
|
||||
4: {
|
||||
0: {
|
||||
v: 5,
|
||||
},
|
||||
1: {
|
||||
v: 16,
|
||||
},
|
||||
2: {
|
||||
v: 200,
|
||||
},
|
||||
},
|
||||
5: {
|
||||
0: {
|
||||
v: 6,
|
||||
},
|
||||
1: {
|
||||
v: 15,
|
||||
},
|
||||
2: {
|
||||
v: 200,
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
columnData: {
|
||||
1: {
|
||||
hd: BooleanNumber.FALSE,
|
||||
},
|
||||
},
|
||||
rowData: {
|
||||
1: {
|
||||
hd: BooleanNumber.FALSE,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
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([SheetsSortService]);
|
||||
injector.add([SheetsSortController]);
|
||||
injector.add([SheetsSelectionsService, { useClass: mockSelectionManagerService as any }]);
|
||||
injector.add([SheetInterceptorService]);
|
||||
injector.add([SheetSkeletonService]);
|
||||
injector.add([FormulaDataModel, { useClass: mockFormulaDataModel }]);
|
||||
|
||||
dependencies?.forEach((d) => injector.add(d));
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this._injector.get(SheetsSortController);
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
return {
|
||||
univer,
|
||||
get: injector.get.bind(injector),
|
||||
sheet,
|
||||
};
|
||||
}
|
||||
|
||||
class mockSelectionManagerService {
|
||||
replace() {
|
||||
}
|
||||
}
|
||||
|
||||
class mockFormulaDataModel {
|
||||
getArrayFormulaRange() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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 type { ISortRangeCommandParams } from '../sheets-sort.command';
|
||||
import { ICommandService, IUniverInstanceService } from '@univerjs/core';
|
||||
import { ReorderRangeCommand, ReorderRangeMutation, SetSelectionsOperation } from '@univerjs/sheets';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { SortRangeCommand } from '../sheets-sort.command';
|
||||
import { createCommandTestBed } from './create-command-test-bed';
|
||||
|
||||
describe('Test "Sort Range Commands"', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let commandService: ICommandService;
|
||||
let getData: (row: number, col: number) => Nullable<ICellData>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createCommandTestBed();
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
commandService = get(ICommandService);
|
||||
commandService.registerCommand(SetSelectionsOperation);
|
||||
commandService.registerCommand(ReorderRangeMutation);
|
||||
commandService.registerCommand(ReorderRangeCommand);
|
||||
const univerInstanceService = get(IUniverInstanceService);
|
||||
const workbook = univerInstanceService.getUnit('test') as Workbook;
|
||||
const worksheet = workbook?.getSheetBySheetId('sheet1')!;
|
||||
getData = (row: number, col: number) => {
|
||||
const data = worksheet.getCellMatrix().getValue(row, col);
|
||||
if (data) {
|
||||
return data;
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
});
|
||||
|
||||
afterEach(() => univer.dispose());
|
||||
|
||||
describe('sort with single col', () => {
|
||||
it('asc case: ', async () => {
|
||||
const params = {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
hasTitle: false,
|
||||
orderRules: [{ colIndex: 1, type: 'asc' }],
|
||||
range: {
|
||||
startRow: 0,
|
||||
endRow: 5,
|
||||
startColumn: 0,
|
||||
endColumn: 2,
|
||||
},
|
||||
} as ISortRangeCommandParams;
|
||||
const result = await commandService.executeCommand(SortRangeCommand.id, params);
|
||||
expect(result).toBeTruthy();
|
||||
expect(getData(0, 0)?.v).toBe(6);
|
||||
expect(getData(0, 1)?.v).toBe(15);
|
||||
expect(getData(0, 2)?.v).toBe(200);
|
||||
expect(getData(5, 0)?.v).toBe(1);
|
||||
expect(getData(5, 1)?.v).toBe(20);
|
||||
expect(getData(5, 2)?.v).toBe(100);
|
||||
});
|
||||
|
||||
it('desc case: ', async () => {
|
||||
const params = {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
hasTitle: false,
|
||||
orderRules: [{ colIndex: 0, type: 'desc' }],
|
||||
range: {
|
||||
startRow: 0,
|
||||
endRow: 5,
|
||||
startColumn: 0,
|
||||
endColumn: 2,
|
||||
},
|
||||
} as ISortRangeCommandParams;
|
||||
const result = await commandService.executeCommand(SortRangeCommand.id, params);
|
||||
expect(result).toBeTruthy();
|
||||
expect(getData(0, 0)?.v).toBe(6);
|
||||
expect(getData(0, 1)?.v).toBe(15);
|
||||
expect(getData(0, 2)?.v).toBe(200);
|
||||
expect(getData(5, 0)?.v).toBe(1);
|
||||
expect(getData(5, 1)?.v).toBe(20);
|
||||
expect(getData(5, 2)?.v).toBe(100);
|
||||
});
|
||||
});
|
||||
|
||||
describe('sort with multiple cols', () => {
|
||||
it('asc + desc combine case', async () => {
|
||||
const params = {
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
hasTitle: false,
|
||||
orderRules: [{ colIndex: 2, type: 'desc' }, { colIndex: 1, type: 'asc' }],
|
||||
range: {
|
||||
startRow: 0,
|
||||
endRow: 5,
|
||||
startColumn: 0,
|
||||
endColumn: 2,
|
||||
},
|
||||
} as ISortRangeCommandParams;
|
||||
const result = await commandService.executeCommand(SortRangeCommand.id, params);
|
||||
expect(result).toBeTruthy();
|
||||
expect(getData(0, 0)?.v).toBe(6);
|
||||
expect(getData(0, 1)?.v).toBe(15);
|
||||
expect(getData(0, 2)?.v).toBe(200);
|
||||
expect(getData(5, 0)?.v).toBe(1);
|
||||
expect(getData(5, 1)?.v).toBe(20);
|
||||
expect(getData(5, 2)?.v).toBe(100);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,161 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// This file provides a ton of mutations to manipulate `FilterModel`.
|
||||
// These models would be held on `SheetsFilterService`.
|
||||
|
||||
import type { IAccessor, ICellData, ICommand, IRange, Nullable, Worksheet } from '@univerjs/core';
|
||||
import type { IReorderRangeCommandParams, ISheetCommandSharedParams } from '@univerjs/sheets';
|
||||
import type { IOrderRule, SortType } from '../../services/interface';
|
||||
import { CommandType, ICommandService, IUniverInstanceService, Rectangle, sequenceExecute } from '@univerjs/core';
|
||||
import { getSheetCommandTarget, ReorderRangeCommand } from '@univerjs/sheets';
|
||||
import { SheetsSortService } from '../../services/sheets-sort.service';
|
||||
|
||||
export interface ISortRangeCommandParams extends ISheetCommandSharedParams {
|
||||
range: IRange;
|
||||
orderRules: IOrderRule[];
|
||||
hasTitle: boolean;
|
||||
}
|
||||
|
||||
export interface IRowComparator {
|
||||
index: number;
|
||||
value: Array<Nullable<ICellData>>;
|
||||
}
|
||||
|
||||
export enum ORDER {
|
||||
KEEP = 1,
|
||||
EXCHANGE = -1,
|
||||
EQUAL = 0,
|
||||
}
|
||||
|
||||
export type CellValue = number | string | null;
|
||||
|
||||
export type ICellValueCompareFn = (type: SortType, a: Nullable<ICellData>, b: Nullable<ICellData>) => Nullable<number>;
|
||||
|
||||
export const SortRangeCommand: ICommand = {
|
||||
id: 'sheet.command.sort-range',
|
||||
type: CommandType.COMMAND,
|
||||
|
||||
handler: (accessor: IAccessor, params: ISortRangeCommandParams) => {
|
||||
const { range, orderRules, hasTitle, unitId, subUnitId } = params;
|
||||
const sortService = accessor.get(SheetsSortService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const { worksheet } = getSheetCommandTarget(univerInstanceService, params) || {};
|
||||
if (!worksheet) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const mergeDataInRange = worksheet.getMergeData().filter((mergeData) => {
|
||||
return Rectangle.contains(range, mergeData);
|
||||
});
|
||||
const mergeMainRowIndexes = mergeDataInRange.map((mergeData) => {
|
||||
return mergeData.startRow;
|
||||
});
|
||||
|
||||
const { startRow: rangeStartRow, endRow } = range;
|
||||
const startRow = hasTitle ? rangeStartRow + 1 : rangeStartRow;
|
||||
const toReorder: IRowComparator[] = [];
|
||||
|
||||
const oldOrder: number[] = [];
|
||||
for (let rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
|
||||
if (worksheet.getRowFiltered(rowIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (worksheet.getRowRawVisible(rowIndex) === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mergeDataInRange.length && !mergeMainRowIndexes.includes(rowIndex)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
toReorder.push({
|
||||
index: rowIndex,
|
||||
value: getRowCellData(worksheet, rowIndex, orderRules),
|
||||
});
|
||||
oldOrder.push(rowIndex);
|
||||
}
|
||||
const compareFns: ICellValueCompareFn[] = sortService.getAllCompareFns();
|
||||
|
||||
toReorder.sort(reorderFnGenerator(orderRules, combineCompareFnsAsOne(compareFns)));
|
||||
|
||||
const order: { [key: number]: number } = {};
|
||||
|
||||
toReorder.forEach(({ index, value }, oldIndex) => {
|
||||
order[oldOrder[oldIndex]] = index;
|
||||
});
|
||||
|
||||
const reorderRangeCommand = {
|
||||
id: ReorderRangeCommand.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range,
|
||||
order,
|
||||
} as unknown as IReorderRangeCommandParams,
|
||||
};
|
||||
|
||||
const commandService = accessor.get(ICommandService);
|
||||
const res = sequenceExecute([reorderRangeCommand], commandService);
|
||||
return res.result;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
function getRowCellData(
|
||||
worksheet: Worksheet,
|
||||
rowIndex: number,
|
||||
orderRules: IOrderRule[]
|
||||
): Nullable<ICellData>[] {
|
||||
const result: Nullable<ICellData>[] = [];
|
||||
orderRules.forEach(({ colIndex }) => {
|
||||
result.push(worksheet.getCellRaw(rowIndex, colIndex));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function combineCompareFnsAsOne(compareFns: ICellValueCompareFn[]) {
|
||||
return (type: SortType, a: Nullable<ICellData>, b: Nullable<ICellData>) => {
|
||||
for (let i = 0; i < compareFns.length; i++) {
|
||||
const res = compareFns[i](type, a, b);
|
||||
// null means can't compare in this fn.
|
||||
if (res != null) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
// All fns can't compare these two value, means equal.
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
|
||||
function reorderFnGenerator(orderRules: IOrderRule[], valueCompare: ICellValueCompareFn) {
|
||||
return function (a: IRowComparator, b: IRowComparator): number {
|
||||
let ret: Nullable<number> = null;
|
||||
|
||||
for (let index = 0; index < orderRules.length; index++) {
|
||||
const aCellData = a.value[index];
|
||||
const bCellData = b.value[index];
|
||||
ret = valueCompare(orderRules[index].type, aCellData, bCellData);
|
||||
|
||||
if (ret !== 0 && ret !== null && ret !== undefined) {
|
||||
return ret as number;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
};
|
||||
}
|
||||
24
packages/sheets-sort/src/config/config.ts
Normal file
24
packages/sheets-sort/src/config/config.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const SHEETS_SORT_PLUGIN_CONFIG_KEY = 'sheets-sort.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_SORT_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsSortConfig {
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsSortConfig = {};
|
||||
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* 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, Nullable } from '@univerjs/core';
|
||||
import type { ICellValueCompareFn } from '../commands/commands/sheets-sort.command';
|
||||
import { CellValueType, Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import { SortRangeCommand } from '../commands/commands/sheets-sort.command';
|
||||
import { SheetsSortService } from '../services/sheets-sort.service';
|
||||
import { compareNull, compareNumber, compareString, isNullValue } from './utils';
|
||||
|
||||
export type ICommonComparableCellValue = number | string | null;
|
||||
|
||||
export class SheetsSortController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(SheetsSortService) private readonly _sortService: SheetsSortService
|
||||
) {
|
||||
super();
|
||||
this._initCommands();
|
||||
this._registerCompareFns();
|
||||
}
|
||||
|
||||
private _initCommands(): void {
|
||||
[
|
||||
SortRangeCommand,
|
||||
].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command)));
|
||||
}
|
||||
|
||||
private _registerCompareFns(): void {
|
||||
const commonFn: ICellValueCompareFn = (type, a, b) => {
|
||||
const valueA: ICommonComparableCellValue = this._getCommonValue(a);
|
||||
const valueB: ICommonComparableCellValue = this._getCommonValue(b);
|
||||
|
||||
const compareTypeFns = [
|
||||
compareNull,
|
||||
compareString,
|
||||
compareNumber,
|
||||
];
|
||||
|
||||
for (let i = 0; i < compareTypeFns.length; i++) {
|
||||
const res = compareTypeFns[i](valueA, valueB, type);
|
||||
if (res !== null) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
this._sortService.registerCompareFn(commonFn);
|
||||
}
|
||||
|
||||
private _getCommonValue(a: Nullable<ICellData>): ICommonComparableCellValue {
|
||||
if (isNullValue(a)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const richTextValue = a?.p?.body?.dataStream;
|
||||
if (richTextValue) {
|
||||
return richTextValue;
|
||||
}
|
||||
|
||||
if (a?.t === CellValueType.NUMBER) {
|
||||
return Number.parseFloat(`${a.v}`);
|
||||
}
|
||||
if (a?.t === CellValueType.STRING) {
|
||||
if (typeof a.v === 'number') {
|
||||
return a.v;
|
||||
}
|
||||
return `${a.v}`;
|
||||
}
|
||||
if (a?.t === CellValueType.BOOLEAN) {
|
||||
return `${a.v}`;
|
||||
}
|
||||
if (a?.t === CellValueType.FORCE_STRING) {
|
||||
return Number.parseFloat(`${a.v}`);
|
||||
}
|
||||
return `${a?.v}`;
|
||||
}
|
||||
}
|
||||
125
packages/sheets-sort/src/controllers/utils.ts
Normal file
125
packages/sheets-sort/src/controllers/utils.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
/**
|
||||
* 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, Nullable } from '@univerjs/core';
|
||||
import type { ICommonComparableCellValue } from './sheets-sort.controller';
|
||||
import { SortType } from '../services/interface';
|
||||
|
||||
export enum ORDER {
|
||||
POSITIVE = 1,
|
||||
NEGATIVE = -1,
|
||||
ZERO = 0,
|
||||
}
|
||||
|
||||
const removeStringSymbol = (str: string) => {
|
||||
return str.replace(/-/gi, '').replace(/'/gi, '');
|
||||
};
|
||||
|
||||
export const compareNull = (
|
||||
a1: ICommonComparableCellValue,
|
||||
a2: ICommonComparableCellValue
|
||||
) => {
|
||||
const isA1Null = a1 === null || a1 === '';
|
||||
const isA2Null = a2 === null || a2 === '';
|
||||
|
||||
if (isA1Null && isA2Null) return ORDER.ZERO;
|
||||
|
||||
if (isA1Null) return ORDER.POSITIVE;
|
||||
|
||||
if (isA2Null) return ORDER.NEGATIVE;
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const compareNumber = (
|
||||
a1: ICommonComparableCellValue,
|
||||
a2: ICommonComparableCellValue,
|
||||
type: SortType
|
||||
) => {
|
||||
const isA1Num = typeof a1 === 'number';
|
||||
const isA2Num = typeof a2 === 'number';
|
||||
|
||||
if (isA1Num && isA2Num) {
|
||||
if (a1 < a2) {
|
||||
return type === SortType.ASC ? ORDER.NEGATIVE : ORDER.POSITIVE;
|
||||
}
|
||||
if (a1 > a2) {
|
||||
return type === SortType.ASC ? ORDER.POSITIVE : ORDER.NEGATIVE;
|
||||
}
|
||||
return ORDER.ZERO;
|
||||
}
|
||||
|
||||
if (isA1Num) {
|
||||
return type === SortType.ASC ? ORDER.POSITIVE : ORDER.NEGATIVE;
|
||||
}
|
||||
|
||||
if (isA2Num) {
|
||||
return type === SortType.ASC ? ORDER.NEGATIVE : ORDER.POSITIVE;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const compareString = (a1: ICommonComparableCellValue, a2: ICommonComparableCellValue, type: SortType) => {
|
||||
const isA1Str = typeof a1 === 'string';
|
||||
const isA2Str = typeof a2 === 'string';
|
||||
|
||||
if (isA1Str) {
|
||||
a1 = removeStringSymbol((a1 as string).toLocaleLowerCase()) as string;
|
||||
}
|
||||
if (isA2Str) {
|
||||
a2 = removeStringSymbol((a2 as string).toLocaleLowerCase()) as string;
|
||||
}
|
||||
|
||||
if (!isA1Str && !isA2Str) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isA1Str && isA2Str) {
|
||||
const a1AsString = a1 as string;
|
||||
const a2AsString = a2 as string;
|
||||
if (a1AsString < a2AsString) {
|
||||
return type === SortType.ASC ? ORDER.NEGATIVE : ORDER.POSITIVE;
|
||||
}
|
||||
if (a1AsString > a2AsString) {
|
||||
return type === SortType.ASC ? ORDER.POSITIVE : ORDER.NEGATIVE;
|
||||
}
|
||||
return ORDER.ZERO;
|
||||
}
|
||||
|
||||
if (isA1Str) {
|
||||
return type === SortType.ASC ? ORDER.POSITIVE : ORDER.NEGATIVE;
|
||||
}
|
||||
|
||||
if (isA2Str) {
|
||||
return type === SortType.ASC ? ORDER.NEGATIVE : ORDER.POSITIVE;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const isNullValue = (cell: Nullable<ICellData>) => {
|
||||
if (!cell) {
|
||||
return true;
|
||||
}
|
||||
if (Object.keys(cell).length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (cell?.v == null && cell?.p == null) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
94
packages/sheets-sort/src/facade/__tests__/f-sort.spec.ts
Normal file
94
packages/sheets-sort/src/facade/__tests__/f-sort.spec.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
/**
|
||||
* 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, IUniverInstanceService } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import { DefinedNamesService, IDefinedNamesService } from '@univerjs/engine-formula';
|
||||
import { ReorderRangeCommand, ReorderRangeMutation, SetSelectionsOperation } from '@univerjs/sheets';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import { createCommandTestBed } from '../../commands/commands/__tests__/create-command-test-bed';
|
||||
import '@univerjs/sheets/facade';
|
||||
import '@univerjs/sheets-sort/facade';
|
||||
|
||||
describe('sheets-sort facade', () => {
|
||||
let univer: Univer;
|
||||
let get: Injector['get'];
|
||||
let univerAPI: FUniver;
|
||||
let getData: (row: number, col: number) => Nullable<ICellData>;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createCommandTestBed(undefined, [
|
||||
[IDefinedNamesService, { useClass: DefinedNamesService }],
|
||||
]);
|
||||
univer = testBed.univer;
|
||||
get = testBed.get;
|
||||
|
||||
const commandService = get(ICommandService);
|
||||
commandService.registerCommand(SetSelectionsOperation);
|
||||
commandService.registerCommand(ReorderRangeMutation);
|
||||
commandService.registerCommand(ReorderRangeCommand);
|
||||
|
||||
const workbook = get(IUniverInstanceService).getUnit<Workbook>('test')!;
|
||||
const worksheet = workbook.getSheetBySheetId('sheet1')!;
|
||||
getData = (row: number, col: number) => worksheet.getCellMatrix().getValue(row, col) ?? undefined;
|
||||
univerAPI = FUniver.newAPI(univer.__getInjector());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
univer.dispose();
|
||||
});
|
||||
|
||||
it('sorts a selected range by a facade column spec relative to the range', () => {
|
||||
const worksheet = univerAPI.getActiveWorkbook()!.getActiveSheet();
|
||||
|
||||
worksheet.getRange('A1:C6').sort({ column: 1, ascending: true });
|
||||
|
||||
expect(getData(0, 0)?.v).toBe(6);
|
||||
expect(getData(0, 1)?.v).toBe(15);
|
||||
expect(getData(5, 0)?.v).toBe(1);
|
||||
expect(getData(5, 1)?.v).toBe(20);
|
||||
});
|
||||
|
||||
it('sorts the whole worksheet and emits before/after sort events with user-facing columns', () => {
|
||||
const events: Array<{ phase: string; column: number; ascending: boolean }> = [];
|
||||
const beforeDisposable = univerAPI.addEvent(univerAPI.Event.SheetBeforeRangeSort, (params) => {
|
||||
events.push({
|
||||
phase: 'before',
|
||||
column: params.sortColumn[0].column,
|
||||
ascending: params.sortColumn[0].ascending,
|
||||
});
|
||||
});
|
||||
const afterDisposable = univerAPI.addEvent(univerAPI.Event.SheetRangeSorted, (params) => {
|
||||
events.push({
|
||||
phase: 'after',
|
||||
column: params.sortColumn[0].column,
|
||||
ascending: params.sortColumn[0].ascending,
|
||||
});
|
||||
});
|
||||
|
||||
univerAPI.getActiveWorkbook()!.getActiveSheet().sort(0, false);
|
||||
|
||||
expect(getData(0, 0)?.v).toBe(6);
|
||||
expect(events).toEqual([
|
||||
{ phase: 'before', column: 0, ascending: false },
|
||||
{ phase: 'after', column: 0, ascending: false },
|
||||
]);
|
||||
|
||||
beforeDisposable.dispose();
|
||||
afterDisposable.dispose();
|
||||
});
|
||||
});
|
||||
89
packages/sheets-sort/src/facade/f-event.ts
Normal file
89
packages/sheets-sort/src/facade/f-event.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IEventBase } from '@univerjs/core/facade';
|
||||
import type { FRange, FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
|
||||
import { FEventName } from '@univerjs/core/facade';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFSheetsSortEventNameMixin {
|
||||
/**
|
||||
* This event will be emitted when a range on a worksheet is sorted.
|
||||
* @see {@link ISheetRangeSortParams}
|
||||
* @example
|
||||
* ```typescript
|
||||
* const callbackDisposable = univerAPI.addEvent(univerAPI.Event.SheetRangeSorted, (params) => {
|
||||
* console.log(params);
|
||||
* const { workbook, worksheet, range, sortColumn } = params;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `callbackDisposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
SheetRangeSorted: 'SheetRangeSorted';
|
||||
|
||||
/**
|
||||
* This event will be emitted before sorting a range on a worksheet.
|
||||
* @see {@link ISheetRangeSortParams}
|
||||
* @example
|
||||
* ```typescript
|
||||
* const callbackDisposable = univerAPI.addEvent(univerAPI.Event.SheetBeforeRangeSort, (params) => {
|
||||
* console.log(params);
|
||||
* const { workbook, worksheet, range, sortColumn } = params;
|
||||
*
|
||||
* // Cancel the sorting operation.
|
||||
* params.cancel = true;
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `callbackDisposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
SheetBeforeRangeSort: 'SheetBeforeRangeSort';
|
||||
}
|
||||
|
||||
export class FSheetsSortEventNameMixin implements IFSheetsSortEventNameMixin {
|
||||
get SheetRangeSorted(): 'SheetRangeSorted' { return 'SheetRangeSorted' as const; }
|
||||
get SheetBeforeRangeSort(): 'SheetBeforeRangeSort' { return 'SheetBeforeRangeSort' as const; }
|
||||
}
|
||||
|
||||
interface ISortColumn {
|
||||
column: number;
|
||||
ascending: boolean;
|
||||
}
|
||||
|
||||
export interface ISheetsRangeSortEventParams extends IEventBase {
|
||||
workbook: FWorkbook;
|
||||
worksheet: FWorksheet;
|
||||
range: FRange;
|
||||
sortColumn: ISortColumn[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface ISheetsSortEventParamConfig {
|
||||
SheetBeforeRangeSort: ISheetsRangeSortEventParams;
|
||||
SheetRangeSorted: ISheetsRangeSortEventParams;
|
||||
}
|
||||
|
||||
FEventName.extend(FSheetsSortEventNameMixin);
|
||||
declare module '@univerjs/core/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FEventName extends IFSheetsSortEventNameMixin { }
|
||||
interface IEventParamConfig extends ISheetsSortEventParamConfig { }
|
||||
}
|
||||
80
packages/sheets-sort/src/facade/f-range.ts
Normal file
80
packages/sheets-sort/src/facade/f-range.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IOrderRule } from '@univerjs/sheets-sort';
|
||||
import { SortRangeCommand, SortType } from '@univerjs/sheets-sort';
|
||||
import { FRange } from '@univerjs/sheets/facade';
|
||||
|
||||
export type SortColumnSpec = { column: number; ascending: boolean } | number;
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFRangeSheetsSortMixin {
|
||||
/**
|
||||
* Sorts the cells in the given range, by column(s) and order specified.
|
||||
* @param {SortColumnSpec | SortColumnSpec[]} column The column index with order or an array of column indexes with order. The range first column index is 0.
|
||||
* @returns {FRange} The range itself for chaining.
|
||||
* @example
|
||||
* ```typescript
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
* const fRange = fWorksheet.getRange('D1:G10');
|
||||
*
|
||||
* // Sorts the range by the first column in ascending order.
|
||||
* fRange.sort(0);
|
||||
*
|
||||
* // Sorts the range by the first column in descending order.
|
||||
* fRange.sort({ column: 0, ascending: false });
|
||||
*
|
||||
* // Sorts the range by the first column in descending order and the second column in ascending order.
|
||||
* fRange.sort([{ column: 0, ascending: false }, 1]);
|
||||
* ```
|
||||
*/
|
||||
sort(column: SortColumnSpec | SortColumnSpec[]): FRange;
|
||||
}
|
||||
|
||||
export class FRangeSheetsSortMixin extends FRange implements IFRangeSheetsSortMixin {
|
||||
override sort(column: SortColumnSpec | SortColumnSpec[]): FRange {
|
||||
const columnBase = this._range.startColumn;
|
||||
const columns = Array.isArray(column) ? column : [column];
|
||||
|
||||
const orderRules: IOrderRule[] = columns.map((c) => {
|
||||
if (typeof c === 'number') {
|
||||
return { colIndex: c + columnBase, type: SortType.ASC };
|
||||
}
|
||||
return {
|
||||
colIndex: c.column + columnBase,
|
||||
type: c.ascending ? SortType.ASC : SortType.DESC,
|
||||
};
|
||||
});
|
||||
this._commandService.syncExecuteCommand(SortRangeCommand.id, {
|
||||
orderRules,
|
||||
range: this._range,
|
||||
hasTitle: false,
|
||||
unitId: this._workbook.getUnitId(),
|
||||
subUnitId: this._worksheet.getSheetId(),
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
FRange.extend(FRangeSheetsSortMixin);
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FRange extends IFRangeSheetsSortMixin {}
|
||||
}
|
||||
97
packages/sheets-sort/src/facade/f-univer.ts
Normal file
97
packages/sheets-sort/src/facade/f-univer.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ICommandInfo, Injector } from '@univerjs/core';
|
||||
import type { ISortRangeCommandParams } from '@univerjs/sheets-sort';
|
||||
import type { ISheetsRangeSortEventParams } from './f-event';
|
||||
import { ICommandService } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import { SortRangeCommand, SortType } from '@univerjs/sheets-sort';
|
||||
|
||||
export class FUniverSheetsSortMixin extends FUniver {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
override _initialize(injector: Injector): void {
|
||||
const commandService = injector.get(ICommandService);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetBeforeRangeSort,
|
||||
() => commandService.beforeCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id !== SortRangeCommand.id) return;
|
||||
this._beforeRangeSort(commandInfo as Readonly<ICommandInfo<ISortRangeCommandParams>>);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.SheetRangeSorted,
|
||||
() => commandService.onCommandExecuted((commandInfo) => {
|
||||
if (commandInfo.id !== SortRangeCommand.id) return;
|
||||
this._onRangeSorted(commandInfo as Readonly<ICommandInfo<ISortRangeCommandParams>>);
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
private _beforeRangeSort(commandInfo: Readonly<ICommandInfo<ISortRangeCommandParams>>): void {
|
||||
const params = commandInfo.params!;
|
||||
const fWorkbook = this.getWorkbook(params.unitId)!;
|
||||
const fWorksheet = fWorkbook.getSheetBySheetId(params.subUnitId)!;
|
||||
const { startColumn, endColumn, startRow, endRow } = params.range;
|
||||
const fRange = fWorksheet.getRange(startRow, startColumn, endRow - startRow + 1, endColumn - startColumn + 1);
|
||||
const eventParams: ISheetsRangeSortEventParams = {
|
||||
workbook: fWorkbook,
|
||||
worksheet: fWorksheet,
|
||||
range: fRange,
|
||||
sortColumn: params.orderRules.map((rule) => ({
|
||||
column: rule.colIndex - startColumn,
|
||||
ascending: rule.type === SortType.ASC,
|
||||
})),
|
||||
};
|
||||
|
||||
this.fireEvent(this.Event.SheetBeforeRangeSort, eventParams);
|
||||
if (eventParams.cancel) {
|
||||
throw new Error('SortRangeCommand canceled.');
|
||||
}
|
||||
}
|
||||
|
||||
private _onRangeSorted(commandInfo: Readonly<ICommandInfo<ISortRangeCommandParams>>): void {
|
||||
const params = commandInfo.params!;
|
||||
const fWorkbook = this.getWorkbook(params.unitId)!;
|
||||
const fWorksheet = fWorkbook.getSheetBySheetId(params.subUnitId)!;
|
||||
const { startColumn, endColumn, startRow, endRow } = params.range;
|
||||
const fRange = fWorksheet.getRange(startRow, startColumn, endRow - startRow + 1, endColumn - startColumn + 1);
|
||||
const eventParams: ISheetsRangeSortEventParams = {
|
||||
workbook: fWorkbook,
|
||||
worksheet: fWorksheet,
|
||||
range: fRange,
|
||||
sortColumn: params.orderRules.map((rule) => ({
|
||||
column: rule.colIndex - startColumn,
|
||||
ascending: rule.type === SortType.ASC,
|
||||
})),
|
||||
};
|
||||
|
||||
this.fireEvent(this.Event.SheetRangeSorted, eventParams);
|
||||
if (eventParams.cancel) {
|
||||
throw new Error('SortRangeCommand canceled.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FUniver.extend(FUniverSheetsSortMixin);
|
||||
77
packages/sheets-sort/src/facade/f-worksheet.ts
Normal file
77
packages/sheets-sort/src/facade/f-worksheet.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRange } from '@univerjs/core';
|
||||
import type { IOrderRule } from '@univerjs/sheets-sort';
|
||||
import { RANGE_TYPE } from '@univerjs/core';
|
||||
import { SortRangeCommand, SortType } from '@univerjs/sheets-sort';
|
||||
import { FWorksheet } from '@univerjs/sheets/facade';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFWorksheetSortMixin {
|
||||
/**
|
||||
* Sort the worksheet by the specified column.
|
||||
* @param {number} colIndex The column index to sort by.
|
||||
* @param {boolean} [asc=true] The sort order. `true` for ascending, `false` for descending. The column A index is 0.
|
||||
* @returns {FWorksheet} The worksheet itself for chaining.
|
||||
* @example
|
||||
* ```typescript
|
||||
* const fWorkbook = univerAPI.getActiveWorkbook();
|
||||
* const fWorksheet = fWorkbook.getSheetByName('Sheet1');
|
||||
* if (!fWorksheet) return;
|
||||
*
|
||||
* // Sorts the worksheet by the column A in ascending order.
|
||||
* fWorksheet.sort(0);
|
||||
*
|
||||
* // Sorts the worksheet by the column A in descending order.
|
||||
* fWorksheet.sort(0, false);
|
||||
* ```
|
||||
*/
|
||||
sort(colIndex: number, asc?: boolean): FWorksheet;
|
||||
}
|
||||
|
||||
export class FWorksheetSortMixin extends FWorksheet implements IFWorksheetSortMixin {
|
||||
override sort(colIndex: number, asc = true): FWorksheet {
|
||||
const orderRules: IOrderRule[] = [{
|
||||
colIndex,
|
||||
type: asc ? SortType.ASC : SortType.DESC,
|
||||
}];
|
||||
|
||||
const range: IRange = {
|
||||
startRow: 0,
|
||||
startColumn: 0,
|
||||
endRow: this._worksheet.getRowCount() - 1,
|
||||
endColumn: this._worksheet.getColumnCount() - 1,
|
||||
rangeType: RANGE_TYPE.ALL,
|
||||
};
|
||||
this._commandService.syncExecuteCommand(SortRangeCommand.id, {
|
||||
orderRules,
|
||||
range,
|
||||
hasTitle: false,
|
||||
unitId: this._workbook.getUnitId(),
|
||||
subUnitId: this._worksheet.getSheetId(),
|
||||
});
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
FWorksheet.extend(FWorksheetSortMixin);
|
||||
declare module '@univerjs/sheets/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FWorksheet extends IFWorksheetSortMixin {}
|
||||
}
|
||||
25
packages/sheets-sort/src/facade/index.ts
Normal file
25
packages/sheets-sort/src/facade/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './f-univer';
|
||||
import './f-range';
|
||||
import './f-worksheet';
|
||||
import './f-event';
|
||||
|
||||
export type * from './f-event';
|
||||
export type * from './f-range';
|
||||
export type * from './f-univer';
|
||||
export type * from './f-worksheet';
|
||||
24
packages/sheets-sort/src/index.ts
Normal file
24
packages/sheets-sort/src/index.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { SortRangeCommand } from './commands/commands/sheets-sort.command';
|
||||
export type { ISortRangeCommandParams } from './commands/commands/sheets-sort.command';
|
||||
export type { IUniverSheetsSortConfig } from './config/config';
|
||||
export { SheetsSortController } from './controllers/sheets-sort.controller';
|
||||
export { UniverSheetsSortPlugin } from './plugin';
|
||||
export { SortType } from './services/interface';
|
||||
export type { IOrderRule, ISortOption } from './services/interface';
|
||||
export { SheetsSortService } from './services/sheets-sort.service';
|
||||
60
packages/sheets-sort/src/plugin.ts
Normal file
60
packages/sheets-sort/src/plugin.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* 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 { IUniverSheetsSortConfig } from './config/config';
|
||||
import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_SORT_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { SheetsSortController } from './controllers/sheets-sort.controller';
|
||||
import { SheetsSortService } from './services/sheets-sort.service';
|
||||
|
||||
@DependentOn(UniverFormulaEnginePlugin, UniverSheetsPlugin)
|
||||
export class UniverSheetsSortPlugin extends Plugin {
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
static override pluginName = 'SHEET_SORT_PLUGIN';
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsSortConfig> = 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_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[SheetsSortController],
|
||||
[SheetsSortService],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this._injector.get(SheetsSortController);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* 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 { ICellValueCompareFn } from '../../commands/commands/sheets-sort.command';
|
||||
import { ICommandService, Injector, IUniverInstanceService } from '@univerjs/core';
|
||||
import { FormulaDataModel } from '@univerjs/engine-formula';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { SortRangeCommand } from '../../commands/commands/sheets-sort.command';
|
||||
import { SheetsSortService } from '../sheets-sort.service';
|
||||
|
||||
describe('SheetsSortService', () => {
|
||||
let service: SheetsSortService;
|
||||
let sheet: any;
|
||||
let arrayFormulaRange: any;
|
||||
let executeCommand: ReturnType<typeof vi.fn>;
|
||||
|
||||
beforeEach(() => {
|
||||
const injector = new Injector();
|
||||
sheet = {
|
||||
getSheetId: () => 'sheet-1',
|
||||
getMergeData: () => [],
|
||||
getCellRaw: () => null,
|
||||
};
|
||||
arrayFormulaRange = {};
|
||||
executeCommand = vi.fn(async () => true);
|
||||
|
||||
const workbook = {
|
||||
getUnitId: () => 'book-1',
|
||||
getSheetBySheetId: (sheetId: string) => (sheetId === 'sheet-1' ? sheet : null),
|
||||
getActiveSheet: () => sheet,
|
||||
};
|
||||
|
||||
class TestUniverInstanceService {
|
||||
getUnit = (unitId: string) => (unitId === 'book-1' ? workbook : null);
|
||||
getCurrentUnitOfType = () => workbook;
|
||||
}
|
||||
|
||||
class TestCommandService {
|
||||
executeCommand = executeCommand;
|
||||
}
|
||||
|
||||
class TestFormulaDataModel {
|
||||
getArrayFormulaRange = () => arrayFormulaRange;
|
||||
}
|
||||
|
||||
injector.add([IUniverInstanceService, { useClass: TestUniverInstanceService as never }]);
|
||||
injector.add([ICommandService, { useClass: TestCommandService as never }]);
|
||||
injector.add([FormulaDataModel, { useClass: TestFormulaDataModel as never }]);
|
||||
injector.add([SheetsSortService]);
|
||||
service = injector.get(SheetsSortService);
|
||||
});
|
||||
|
||||
it('requires multi-row ranges for sort actions', () => {
|
||||
expect(service.singleCheck({ unitId: 'book-1', subUnitId: 'sheet-1', range: { startRow: 2, endRow: 2, startColumn: 0, endColumn: 2 } })).toBe(false);
|
||||
expect(service.singleCheck({ unitId: 'book-1', subUnitId: 'sheet-1', range: { startRow: 2, endRow: 4, startColumn: 0, endColumn: 2 } })).toBe(true);
|
||||
});
|
||||
|
||||
it('uses the most recently registered comparator first', () => {
|
||||
const first: ICellValueCompareFn = () => 1;
|
||||
const second: ICellValueCompareFn = () => -1;
|
||||
|
||||
service.registerCompareFn(first);
|
||||
service.registerCompareFn(second);
|
||||
|
||||
expect(service.getAllCompareFns()).toEqual([second, first]);
|
||||
});
|
||||
|
||||
it('validates sort ranges against merge layout and non-empty cells', () => {
|
||||
const location = { unitId: 'book-1', subUnitId: 'sheet-1', range: { startRow: 0, endRow: 1, startColumn: 0, endColumn: 1 } };
|
||||
|
||||
expect(service.mergeCheck(location)).toBe(true);
|
||||
sheet.getMergeData = () => [{ startRow: 0, endRow: 1, startColumn: 0, endColumn: 1 }];
|
||||
expect(service.mergeCheck(location)).toBe(true);
|
||||
sheet.getMergeData = () => [
|
||||
{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 },
|
||||
{ startRow: 1, endRow: 1, startColumn: 1, endColumn: 1 },
|
||||
];
|
||||
expect(service.mergeCheck(location)).toBe(false);
|
||||
|
||||
sheet.getCellRaw = (row: number, col: number) => (row === 1 && col === 1 ? { v: 'value' } : null);
|
||||
expect(service.emptyCheck(location)).toBe(true);
|
||||
sheet.getCellRaw = () => null;
|
||||
expect(service.emptyCheck(location)).toBe(false);
|
||||
|
||||
expect(service.mergeCheck({ ...location, unitId: 'missing' })).toBe(false);
|
||||
expect(service.emptyCheck({ ...location, unitId: 'missing' })).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects sort ranges that intersect array formulas', () => {
|
||||
const location = { unitId: 'book-1', subUnitId: 'sheet-1', range: { startRow: 0, endRow: 2, startColumn: 0, endColumn: 2 } };
|
||||
|
||||
expect(service.formulaCheck(location)).toBe(true);
|
||||
|
||||
arrayFormulaRange = {
|
||||
'book-1': {
|
||||
'sheet-1': {
|
||||
1: {
|
||||
1: { startRow: 1, endRow: 3, startColumn: 1, endColumn: 3 },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
expect(service.formulaCheck(location)).toBe(false);
|
||||
});
|
||||
|
||||
it('applies sort options through the command service with explicit or active sheet targets', async () => {
|
||||
service.applySort({
|
||||
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 2 },
|
||||
orderRules: [{ colIndex: 1, type: 'asc' }] as never,
|
||||
hasTitle: true,
|
||||
});
|
||||
service.applySort({
|
||||
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 2 },
|
||||
orderRules: [] as never,
|
||||
}, 'book-2', 'sheet-2');
|
||||
|
||||
expect(executeCommand).toHaveBeenNthCalledWith(1, SortRangeCommand.id, {
|
||||
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 2 },
|
||||
orderRules: [{ colIndex: 1, type: 'asc' }],
|
||||
hasTitle: true,
|
||||
unitId: 'book-1',
|
||||
subUnitId: 'sheet-1',
|
||||
});
|
||||
expect(executeCommand).toHaveBeenNthCalledWith(2, SortRangeCommand.id, {
|
||||
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 2 },
|
||||
orderRules: [],
|
||||
hasTitle: false,
|
||||
unitId: 'book-2',
|
||||
subUnitId: 'sheet-2',
|
||||
});
|
||||
});
|
||||
});
|
||||
33
packages/sheets-sort/src/services/interface.ts
Normal file
33
packages/sheets-sort/src/services/interface.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRange } from '@univerjs/core';
|
||||
|
||||
export interface ISortOption {
|
||||
range: IRange;
|
||||
orderRules: IOrderRule[];
|
||||
hasTitle?: boolean;
|
||||
}
|
||||
|
||||
export enum SortType {
|
||||
DESC = 'desc', // Z-A
|
||||
ASC = 'asc', // A-Z
|
||||
}
|
||||
|
||||
export interface IOrderRule {
|
||||
type: SortType;
|
||||
colIndex: number;
|
||||
}
|
||||
140
packages/sheets-sort/src/services/sheets-sort.service.ts
Normal file
140
packages/sheets-sort/src/services/sheets-sort.service.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* 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, Workbook } from '@univerjs/core';
|
||||
import type { ISheetRangeLocation } from '@univerjs/sheets';
|
||||
import type { ICellValueCompareFn } from '../commands/commands/sheets-sort.command';
|
||||
import type { ISortOption } from './interface';
|
||||
import {
|
||||
Disposable,
|
||||
ICommandService,
|
||||
Inject,
|
||||
IUniverInstanceService,
|
||||
Rectangle,
|
||||
} from '@univerjs/core';
|
||||
import { FormulaDataModel } from '@univerjs/engine-formula';
|
||||
import { getSheetCommandTarget } from '@univerjs/sheets';
|
||||
import { SortRangeCommand } from '../commands/commands/sheets-sort.command';
|
||||
import { isNullValue } from '../controllers/utils';
|
||||
|
||||
export class SheetsSortService extends Disposable {
|
||||
private _compareFns: ICellValueCompareFn[] = [];
|
||||
|
||||
constructor(
|
||||
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(FormulaDataModel) private readonly _formulaDataModel: FormulaDataModel
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
mergeCheck(location: ISheetRangeLocation) {
|
||||
const { unitId, subUnitId, range } = location;
|
||||
const sheet = (this._univerInstanceService.getUnit(unitId) as Workbook)?.getSheetBySheetId(subUnitId);
|
||||
if (!sheet) {
|
||||
return false;
|
||||
}
|
||||
const mergeDataInRange = sheet.getMergeData().filter((merge) => Rectangle.contains(range, merge));
|
||||
if (mergeDataInRange.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isRangeDividedEqually(range, mergeDataInRange);
|
||||
}
|
||||
|
||||
emptyCheck(location: ISheetRangeLocation) {
|
||||
const { unitId, subUnitId, range } = location;
|
||||
const sheet = (this._univerInstanceService.getUnit(unitId) as Workbook)?.getSheetBySheetId(subUnitId);
|
||||
if (!sheet) {
|
||||
return false;
|
||||
}
|
||||
for (let row = range.startRow; row <= range.endRow; row++) {
|
||||
for (let col = range.startColumn; col <= range.endColumn; col++) {
|
||||
if (!isNullValue(sheet.getCellRaw(row, col))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
singleCheck(location: ISheetRangeLocation) {
|
||||
if (location.range.startRow === location.range.endRow) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
formulaCheck(location: ISheetRangeLocation) {
|
||||
const { unitId, subUnitId, range } = location;
|
||||
const arrayFormulaRange = this._formulaDataModel.getArrayFormulaRange()?.[unitId]?.[subUnitId];
|
||||
for (const row in arrayFormulaRange) {
|
||||
const rowData = arrayFormulaRange[Number(row)];
|
||||
for (const col in rowData) {
|
||||
const arrayFormula = rowData[Number(col)];
|
||||
if (arrayFormula && Rectangle.intersects(range, arrayFormula)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
registerCompareFn(fn: ICellValueCompareFn) {
|
||||
this._compareFns.unshift(fn);
|
||||
}
|
||||
|
||||
getAllCompareFns(): ICellValueCompareFn[] {
|
||||
return this._compareFns;
|
||||
}
|
||||
|
||||
applySort(sortOption: ISortOption, unitId?: string, subUnitId?: string) {
|
||||
const { unitId: _unitId, subUnitId: _subUnitId } = getSheetCommandTarget(this._univerInstanceService) || {};
|
||||
this._commandService.executeCommand(SortRangeCommand.id, {
|
||||
orderRules: sortOption.orderRules,
|
||||
range: sortOption.range,
|
||||
hasTitle: sortOption.hasTitle ?? false,
|
||||
unitId: unitId || _unitId,
|
||||
subUnitId: subUnitId || _subUnitId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function isRangeDividedEqually(range: IRange, merges: IRange[]): boolean {
|
||||
const rangeRows = range.endRow - range.startRow + 1;
|
||||
const rangeCols = range.endColumn - range.startColumn + 1;
|
||||
let mergeRows: number | null = null;
|
||||
let mergeCols: number | null = null;
|
||||
|
||||
const totalArea = rangeRows * rangeCols;
|
||||
let totalMergeArea = 0;
|
||||
for (const merge of merges) {
|
||||
if (merge.startRow >= range.startRow && merge.endRow <= range.endRow && merge.startColumn >= range.startColumn && merge.endColumn <= range.endColumn) {
|
||||
const currentMergeRows = merge.endRow - merge.startRow + 1;
|
||||
const currentMergeCols = merge.endColumn - merge.startColumn + 1;
|
||||
|
||||
if (mergeRows === null && mergeCols === null) {
|
||||
mergeRows = currentMergeRows;
|
||||
mergeCols = currentMergeCols;
|
||||
} else if (currentMergeRows !== mergeRows || currentMergeCols !== mergeCols) {
|
||||
return false;
|
||||
}
|
||||
totalMergeArea += currentMergeRows * currentMergeCols;
|
||||
}
|
||||
}
|
||||
|
||||
return totalMergeArea === totalArea;
|
||||
}
|
||||
8
packages/sheets-sort/tsconfig.json
Normal file
8
packages/sheets-sort/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/sheets-sort/tsconfig.node.json
Normal file
18
packages/sheets-sort/tsconfig.node.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/node",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
3
packages/sheets-sort/vitest.config.ts
Normal file
3
packages/sheets-sort/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
Reference in New Issue
Block a user