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:
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* 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 } from '@univerjs/core';
|
||||
import {
|
||||
ICommandService,
|
||||
Inject,
|
||||
Injector,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
Plugin,
|
||||
toDisposable,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { SheetTableService, TableManager } from '@univerjs/sheets-table';
|
||||
import { SheetCanvasPopManagerService } from '@univerjs/sheets-ui';
|
||||
import { IDialogService } from '@univerjs/ui';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { SheetsTableComponentController } from '../../../controllers/sheet-table-component.controller';
|
||||
import { OpenTableFilterPanelOperation } from '../open-table-filter-dialog.opration';
|
||||
|
||||
interface IAttachedPopup {
|
||||
row: number;
|
||||
column: number;
|
||||
}
|
||||
|
||||
class TestSheetCanvasPopManagerService {
|
||||
readonly attachedPopups: IAttachedPopup[] = [];
|
||||
|
||||
attachPopupToCell(row: number, column: number): IDisposable {
|
||||
this.attachedPopups.push({ row, column });
|
||||
return toDisposable(() => {
|
||||
this.attachedPopups.length = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class TestDialogService {
|
||||
readonly closedIds: string[] = [];
|
||||
|
||||
open(): IDisposable {
|
||||
return toDisposable(() => {});
|
||||
}
|
||||
|
||||
close(id: string): void {
|
||||
this.closedIds.push(id);
|
||||
}
|
||||
|
||||
closeAll(): void {
|
||||
this.closedIds.push('*');
|
||||
}
|
||||
|
||||
getDialogs$() {
|
||||
throw new Error('Dialogs are not observed in this command test.');
|
||||
}
|
||||
}
|
||||
|
||||
interface ITestBed {
|
||||
univer: Univer;
|
||||
get: Injector['get'];
|
||||
workbook: Workbook;
|
||||
}
|
||||
|
||||
function createWorkbookData(): IWorkbookData {
|
||||
return {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: 'test',
|
||||
sheetOrder: ['sheet1'],
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
cellData: {},
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
};
|
||||
}
|
||||
|
||||
function createTestBed(): 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 dependencies: Dependency[] = [
|
||||
[TableManager],
|
||||
[SheetTableService],
|
||||
[SheetCanvasPopManagerService, { useClass: TestSheetCanvasPopManagerService }],
|
||||
[IDialogService, { useClass: TestDialogService }],
|
||||
[SheetsTableComponentController],
|
||||
];
|
||||
dependencies.forEach((dependency) => this._injector.add(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
injector.get(LocaleService).load({
|
||||
[LocaleType.EN_US]: {
|
||||
'sheets-table': {
|
||||
columnPrefix: 'Column',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(OpenTableFilterPanelOperation);
|
||||
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
injector.get(SheetTableService).addTable(
|
||||
workbook.getUnitId(),
|
||||
'sheet1',
|
||||
'Orders',
|
||||
{ startRow: 0, endRow: 4, startColumn: 1, endColumn: 3 },
|
||||
['Name', 'Quantity', 'Owner'],
|
||||
'table-orders'
|
||||
);
|
||||
|
||||
return {
|
||||
univer,
|
||||
get: injector.get.bind(injector),
|
||||
workbook,
|
||||
};
|
||||
}
|
||||
|
||||
describe('OpenTableFilterPanelOperation', () => {
|
||||
let testBed: ITestBed | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
testBed?.univer.dispose();
|
||||
testBed = undefined;
|
||||
});
|
||||
|
||||
it('does not open a panel when params or table are missing', async () => {
|
||||
testBed = createTestBed();
|
||||
const commandService = testBed.get(ICommandService);
|
||||
|
||||
await expect(commandService.executeCommand(OpenTableFilterPanelOperation.id)).resolves.toBe(false);
|
||||
await expect(commandService.executeCommand(OpenTableFilterPanelOperation.id, {
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
tableId: 'missing',
|
||||
row: 1,
|
||||
col: 2,
|
||||
})).resolves.toBe(false);
|
||||
|
||||
expect(testBed.get(SheetsTableComponentController).getCurrentTableFilterInfo()).toBeNull();
|
||||
});
|
||||
|
||||
it('opens the filter panel for the requested table column', async () => {
|
||||
testBed = createTestBed();
|
||||
const commandService = testBed.get(ICommandService);
|
||||
|
||||
await expect(commandService.executeCommand(OpenTableFilterPanelOperation.id, {
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
tableId: 'table-orders',
|
||||
row: 0,
|
||||
col: 2,
|
||||
})).resolves.toBe(true);
|
||||
|
||||
expect(testBed.get(SheetsTableComponentController).getCurrentTableFilterInfo()).toEqual({
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
tableId: 'table-orders',
|
||||
row: 0,
|
||||
column: 2,
|
||||
});
|
||||
expect((testBed.get(SheetCanvasPopManagerService) as unknown as TestSheetCanvasPopManagerService).attachedPopups).toEqual([{ row: 0, column: 2 }]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* 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, IRange, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import type { IDialogPartMethodOptions } from '@univerjs/ui';
|
||||
import {
|
||||
ICommandService,
|
||||
ILogService,
|
||||
Inject,
|
||||
Injector,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
LogLevel,
|
||||
Plugin,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { DefinedNamesService, IDefinedNamesService } from '@univerjs/engine-formula';
|
||||
import { SetSelectionsOperation, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import {
|
||||
AddSheetTableCommand,
|
||||
AddSheetTableMutation,
|
||||
DeleteSheetTableMutation,
|
||||
SheetTableService,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { DesktopDialogService, IDialogService, IUIPartsService, UIPartsService } from '@univerjs/ui';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { openRangeSelector, OpenTableSelectorOperation } from '../open-table-selector.operation';
|
||||
|
||||
interface ITestBed {
|
||||
univer: Univer;
|
||||
get: Injector['get'];
|
||||
workbook?: Workbook;
|
||||
}
|
||||
|
||||
function createWorkbookData(): IWorkbookData {
|
||||
return {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: 'test',
|
||||
sheetOrder: ['sheet1'],
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
name: 'Sheet1',
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
cellData: {
|
||||
0: {
|
||||
0: { v: 'product' },
|
||||
1: { v: 'amount' },
|
||||
},
|
||||
1: {
|
||||
0: { v: 'book' },
|
||||
1: { v: 12 },
|
||||
},
|
||||
2: {
|
||||
0: { v: 'pen' },
|
||||
1: { v: 3 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
};
|
||||
}
|
||||
|
||||
function createTestBed(createWorkbook = true): 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 dependencies: Dependency[] = [
|
||||
[SheetsSelectionsService],
|
||||
[TableManager],
|
||||
[SheetTableService],
|
||||
[IDefinedNamesService, { useClass: DefinedNamesService }],
|
||||
[IUIPartsService, { useClass: UIPartsService }],
|
||||
[IDialogService, { useClass: DesktopDialogService }],
|
||||
];
|
||||
dependencies.forEach((dependency) => this._injector.add(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
|
||||
const localeService = injector.get(LocaleService);
|
||||
localeService.load({
|
||||
[LocaleType.EN_US]: {
|
||||
'sheets-table': {
|
||||
columnPrefix: 'Column',
|
||||
tablePrefix: 'Table',
|
||||
},
|
||||
'sheets-table-ui': {
|
||||
selectRange: 'Select Table Range',
|
||||
},
|
||||
},
|
||||
});
|
||||
localeService.setLocale(LocaleType.EN_US);
|
||||
injector.get(ILogService).setLogLevel(LogLevel.SILENT);
|
||||
|
||||
const commandService = injector.get(ICommandService);
|
||||
[
|
||||
OpenTableSelectorOperation,
|
||||
AddSheetTableCommand,
|
||||
AddSheetTableMutation,
|
||||
DeleteSheetTableMutation,
|
||||
SetSelectionsOperation,
|
||||
].forEach((command) => commandService.registerCommand(command));
|
||||
|
||||
const workbook = createWorkbook
|
||||
? univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData())
|
||||
: undefined;
|
||||
if (workbook) {
|
||||
injector.get(IUniverInstanceService).focusUnit(workbook.getUnitId());
|
||||
}
|
||||
|
||||
return {
|
||||
univer,
|
||||
get: injector.get.bind(injector),
|
||||
workbook,
|
||||
};
|
||||
}
|
||||
|
||||
function getOpenedDialog(dialogService: IDialogService): IDialogPartMethodOptions {
|
||||
return (dialogService as unknown as { _dialogOptions: IDialogPartMethodOptions[] })._dialogOptions.at(-1)!;
|
||||
}
|
||||
|
||||
function getSelectorDialogProps(dialogService: IDialogService) {
|
||||
const label = getOpenedDialog(dialogService).children!.label;
|
||||
if (typeof label === 'string' || !label) {
|
||||
throw new Error('The table selector dialog should expose component props.');
|
||||
}
|
||||
return label.props as {
|
||||
range: IRange;
|
||||
onConfirm: (info: { unitId: string; subUnitId: string; range: IRange }) => void;
|
||||
onCancel: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
describe('OpenTableSelectorOperation', () => {
|
||||
let testBed: ITestBed | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
testBed?.univer.dispose();
|
||||
testBed = undefined;
|
||||
});
|
||||
|
||||
it('resolves a confirmed table range from the selector dialog', async () => {
|
||||
testBed = createTestBed();
|
||||
const dialogService = testBed.get(IDialogService);
|
||||
|
||||
const selection = openRangeSelector(
|
||||
testBed.get(Injector),
|
||||
'test',
|
||||
'sheet1',
|
||||
{ startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 }
|
||||
);
|
||||
getSelectorDialogProps(dialogService).onConfirm({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
range: { startRow: 0, endRow: 2, startColumn: 0, endColumn: 1 },
|
||||
});
|
||||
|
||||
await expect(selection).resolves.toEqual({
|
||||
unitId: 'test',
|
||||
subUnitId: 'sheet1',
|
||||
range: { startRow: 0, endRow: 2, startColumn: 0, endColumn: 1 },
|
||||
});
|
||||
expect(getOpenedDialog(dialogService).open).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when no workbook can receive a table', async () => {
|
||||
testBed = createTestBed(false);
|
||||
|
||||
await expect(testBed.get(ICommandService).executeCommand(OpenTableSelectorOperation.id)).resolves.toBe(false);
|
||||
});
|
||||
|
||||
it('adds a table from an expanded single-cell selection', async () => {
|
||||
testBed = createTestBed();
|
||||
const workbook = testBed.workbook!;
|
||||
const selectionService = testBed.get(SheetsSelectionsService);
|
||||
const commandService = testBed.get(ICommandService);
|
||||
const dialogService = testBed.get(IDialogService);
|
||||
|
||||
selectionService.setSelections(workbook.getUnitId(), 'sheet1', [{
|
||||
range: { startRow: 1, endRow: 1, startColumn: 0, endColumn: 0 },
|
||||
primary: { startRow: 1, endRow: 1, startColumn: 0, endColumn: 0, actualRow: 1, actualColumn: 0, isMerged: false, isMergedMainCell: false },
|
||||
}]);
|
||||
|
||||
const result = commandService.executeCommand(OpenTableSelectorOperation.id);
|
||||
const selectorProps = getSelectorDialogProps(dialogService);
|
||||
expect(selectorProps.range).toEqual({ startRow: 0, endRow: 2, startColumn: 0, endColumn: 1 });
|
||||
selectorProps.onConfirm({
|
||||
unitId: workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
range: selectorProps.range,
|
||||
});
|
||||
|
||||
await expect(result).resolves.toBe(true);
|
||||
expect(testBed.get(TableManager).getTableList(workbook.getUnitId())[0]).toMatchObject({
|
||||
unitId: workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
name: 'Table1',
|
||||
range: { startRow: 0, endRow: 2, startColumn: 0, endColumn: 1 },
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps the workbook unchanged when the selector is cancelled', async () => {
|
||||
testBed = createTestBed();
|
||||
const workbook = testBed.workbook!;
|
||||
const selectionService = testBed.get(SheetsSelectionsService);
|
||||
const commandService = testBed.get(ICommandService);
|
||||
const dialogService = testBed.get(IDialogService);
|
||||
|
||||
selectionService.setSelections(workbook.getUnitId(), 'sheet1', [{
|
||||
range: { startRow: 1, endRow: 2, startColumn: 0, endColumn: 1 },
|
||||
primary: { startRow: 1, endRow: 1, startColumn: 0, endColumn: 0, actualRow: 1, actualColumn: 0, isMerged: false, isMergedMainCell: false },
|
||||
}]);
|
||||
|
||||
const result = commandService.executeCommand(OpenTableSelectorOperation.id);
|
||||
getSelectorDialogProps(dialogService).onCancel();
|
||||
|
||||
await expect(result).resolves.toBe(false);
|
||||
expect(testBed.get(TableManager).getTableList(workbook.getUnitId())).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* 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 type { IOpenTableFilterPanelOperationParams } from '../open-table-filter-dialog.opration';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { TableManager } from '@univerjs/sheets-table';
|
||||
import { SheetsTableMobileComponentController } from '../../../controllers/mobile/sheet-table-component.controller';
|
||||
import { OpenTableFilterPanelOperation } from '../open-table-filter-dialog.opration';
|
||||
|
||||
export const OpenTableFilterPanelMobileOperation: ICommand<IOpenTableFilterPanelOperationParams> = {
|
||||
type: CommandType.OPERATION,
|
||||
id: OpenTableFilterPanelOperation.id,
|
||||
async handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { row, col, unitId, subUnitId, tableId } = params;
|
||||
const tableManager = accessor.get(TableManager);
|
||||
const componentController = accessor.get(SheetsTableMobileComponentController);
|
||||
if (!tableManager.getTable(unitId, tableId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
componentController.openOrToggleFilterPanel({ unitId, subUnitId, row, tableId, column: col });
|
||||
return true;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ICommand } from '@univerjs/core';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { TableManager } from '@univerjs/sheets-table';
|
||||
import { SheetsTableComponentController } from '../../controllers/sheet-table-component.controller';
|
||||
|
||||
export interface IOpenTableFilterPanelOperationParams {
|
||||
row: number;
|
||||
col: number;
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableId: string;
|
||||
}
|
||||
export const OpenTableFilterPanelOperation: ICommand<IOpenTableFilterPanelOperationParams> = {
|
||||
type: CommandType.OPERATION,
|
||||
id: 'sheet.operation.open-table-filter-panel',
|
||||
async handler(accessor, params) {
|
||||
if (!params) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { row, col, unitId, subUnitId, tableId } = params;
|
||||
const tableManager = accessor.get(TableManager);
|
||||
const sheetsTableComponentController = accessor.get(SheetsTableComponentController);
|
||||
|
||||
const table = tableManager.getTable(unitId, tableId);
|
||||
if (!table) {
|
||||
return false;
|
||||
}
|
||||
|
||||
sheetsTableComponentController.openOrToggleFilterPanel({ unitId, subUnitId, row, tableId, column: col });
|
||||
|
||||
return true;
|
||||
|
||||
// const tableRange = table.getRange();
|
||||
// const tableFilter = table.getTableFilterColumn(col - tableRange.startColumn);
|
||||
|
||||
// const dialogServiceProps = {
|
||||
// id: UNIVER_SHEET_TABLE_FILTER_PANEL_ID,
|
||||
// draggable: true,
|
||||
// width: 312,
|
||||
// dialogStyles: {
|
||||
// header: { padding: '16px 16px 0', height: 0 },
|
||||
// body: { padding: '0 16px 16px' },
|
||||
// },
|
||||
// closable: false,
|
||||
// children: {
|
||||
// label: SHEETS_TABLE_FILTER_PANEL_OPENED_KEY,
|
||||
// unitId,
|
||||
// subUnitId,
|
||||
// key: `${row}-${col}-${tableId || ''}`,
|
||||
// tableFilter,
|
||||
// tableId,
|
||||
// columnIndex: col - tableRange.startColumn,
|
||||
// },
|
||||
|
||||
// onClose: (): void => {
|
||||
// dialogService.close(UNIVER_SHEET_TABLE_FILTER_PANEL_ID);
|
||||
// },
|
||||
// destroyOnClose: true,
|
||||
// };
|
||||
// dialogService.open(dialogServiceProps);
|
||||
|
||||
// return true;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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, IRange } from '@univerjs/core';
|
||||
import type { IAddSheetTableCommandParams } from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import { CommandType, ICommandService, IUniverInstanceService, LocaleService } from '@univerjs/core';
|
||||
import {
|
||||
expandToContinuousRange,
|
||||
getSheetCommandTarget,
|
||||
isSingleCellSelection,
|
||||
SheetsSelectionsService,
|
||||
} from '@univerjs/sheets';
|
||||
import { AddSheetTableCommand } from '@univerjs/sheets-table';
|
||||
import { IDialogService } from '@univerjs/ui';
|
||||
import { TABLE_SELECTOR_DIALOG } from '../../const';
|
||||
|
||||
export const OpenTableSelectorOperation = {
|
||||
type: CommandType.OPERATION,
|
||||
id: 'sheet.operation.open-table-selector',
|
||||
async handler(accessor: IAccessor) {
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const commandService = accessor.get(ICommandService);
|
||||
|
||||
const target = getSheetCommandTarget(univerInstanceService);
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { unitId, subUnitId, worksheet } = target;
|
||||
const sheetsSelectionsService = accessor.get(SheetsSelectionsService);
|
||||
|
||||
const lastSelection = sheetsSelectionsService.getCurrentLastSelection();
|
||||
const range = lastSelection?.range ?? { startRow: 0, endRow: 0, startColumn: 0, endColumn: 0 };
|
||||
|
||||
const isSingleCell = isSingleCellSelection(lastSelection);
|
||||
const extendedRange = isSingleCell ? expandToContinuousRange(range, { up: true, left: true, right: true, down: true }, worksheet) : range;
|
||||
|
||||
const rangeInfo = await openRangeSelector(accessor, unitId, subUnitId, extendedRange);
|
||||
if (!rangeInfo) {
|
||||
return false;
|
||||
}
|
||||
|
||||
commandService.executeCommand<IAddSheetTableCommandParams>(AddSheetTableCommand.id, { ...rangeInfo });
|
||||
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
export interface ITableSelectionInfo {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
range: IRange;
|
||||
tableId?: string;
|
||||
}
|
||||
|
||||
export async function openRangeSelector(
|
||||
accessor: IAccessor,
|
||||
unitId: string,
|
||||
subUnitId: string,
|
||||
range: IRange,
|
||||
tableId?: string
|
||||
): Promise<ITableSelectionInfo | null> {
|
||||
const dialogService = accessor.get(IDialogService);
|
||||
const localeService = accessor.get(LocaleService);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const dialogProps = {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range,
|
||||
tableId,
|
||||
onConfirm: (info: ITableSelectionInfo) => {
|
||||
resolve(info);
|
||||
dialogService.close(TABLE_SELECTOR_DIALOG);
|
||||
},
|
||||
onCancel: () => {
|
||||
resolve(null);
|
||||
dialogService.close(TABLE_SELECTOR_DIALOG);
|
||||
},
|
||||
};
|
||||
dialogService.open({
|
||||
id: TABLE_SELECTOR_DIALOG,
|
||||
title: { title: localeService.t<LocaleKey>('sheets-table-ui.selectRange') },
|
||||
draggable: true,
|
||||
mask: false,
|
||||
maskClosable: false,
|
||||
children: {
|
||||
label: {
|
||||
name: TABLE_SELECTOR_DIALOG,
|
||||
props: dialogProps,
|
||||
},
|
||||
},
|
||||
width: 300,
|
||||
onClose: () => {
|
||||
resolve(null);
|
||||
dialogService.close(TABLE_SELECTOR_DIALOG);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
34
packages/sheets-table-ui/src/config/config.ts
Normal file
34
packages/sheets-table-ui/src/config/config.ts
Normal 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 type { MenuConfig } from '@univerjs/ui';
|
||||
|
||||
export const SHEETS_TABLE_UI_PLUGIN_CONFIG_KEY = 'sheets-table-ui.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_TABLE_UI_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsTableUIConfig {
|
||||
anchorHeight?: number;
|
||||
anchorBackgroundColor?: string;
|
||||
hideAnchor?: boolean;
|
||||
|
||||
menu?: MenuConfig;
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsTableUIConfig = {
|
||||
anchorHeight: 24,
|
||||
anchorBackgroundColor: 'rgb(134,139,156)',
|
||||
};
|
||||
35
packages/sheets-table-ui/src/const.ts
Normal file
35
packages/sheets-table-ui/src/const.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const PLUGIN_NAME = 'SHEET_TABLE_UI_PLUGIN';
|
||||
export const SHEETS_TABLE_FILTER_PANEL_OPENED_KEY = 'SHEETS_TABLE_FILTER_PANEL_OPENED_KEY';
|
||||
export const UNIVER_SHEET_TABLE_FILTER_PANEL_ID = 'UNIVER_SHEET_Table_FILTER_PANEL_ID';
|
||||
|
||||
export const TABLE_SELECTOR_DIALOG = 'TABLE_SELECTOR_DIALOG';
|
||||
export const SHEET_TABLE_RENAME_DIALOG = 'SHEET_TABLE_RENAME_DIALOG';
|
||||
export const SHEET_TABLE_RENAME_DIALOG_ID = 'SHEET_TABLE_RENAME_DIALOG_ID';
|
||||
|
||||
export const SHEET_TABLE_THEME_PANEL_ID = 'SHEET_TABLE_THEME_PANEL_ID';
|
||||
export const SHEET_TABLE_THEME_PANEL = 'SHEET_TABLE_THEME_PANEL';
|
||||
export const SHEET_TABLE_MENU = 'SHEET_TABLE_MENU';
|
||||
export const SHEET_TABLE_MOBILE_MENU = 'SHEET_TABLE_MOBILE_MENU';
|
||||
|
||||
export const TABLE_CUSTOM_NAME_PREFIX = 'table-custom-';
|
||||
export const TABLE_DEFAULT_NAME_PREFIX = 'table-default-';
|
||||
export const TABLE_DEFAULT_BG_COLOR = 'rgb(255, 255, 255)';
|
||||
export const TABLE_BORDER_NONE = 'none';
|
||||
export const TABLE_BORDER_DEFAULT = '1px solid rgb(var(--grey-200))';
|
||||
export const TABLE_BORDER_SELECTED = '1px solid rgb(var(--blue-500))';
|
||||
@@ -0,0 +1,107 @@
|
||||
/**
|
||||
* 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 { BehaviorSubject } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, UNIVER_SHEET_TABLE_FILTER_PANEL_ID } from '../../const';
|
||||
import { SheetsTableComponentController } from '../sheet-table-component.controller';
|
||||
|
||||
function createController() {
|
||||
const contextValues = new Map<string, unknown>();
|
||||
const panelOpened$ = new BehaviorSubject<unknown>(undefined);
|
||||
const popupDisposables: Array<{ dispose: ReturnType<typeof vi.fn> }> = [];
|
||||
const contextService = {
|
||||
getContextValue: vi.fn((key: string) => contextValues.get(key)),
|
||||
setContextValue: vi.fn((key: string, value: unknown) => {
|
||||
contextValues.set(key, value);
|
||||
panelOpened$.next(value);
|
||||
}),
|
||||
subscribeContextValue$: vi.fn(() => panelOpened$),
|
||||
};
|
||||
const popupService = {
|
||||
attachPopupToCell: vi.fn((..._args: unknown[]) => {
|
||||
const disposable = { dispose: vi.fn() };
|
||||
popupDisposables.push(disposable);
|
||||
return disposable;
|
||||
}),
|
||||
};
|
||||
const dialogService = { close: vi.fn() };
|
||||
const controller = new SheetsTableComponentController(
|
||||
contextService as never,
|
||||
popupService as never,
|
||||
dialogService as never
|
||||
);
|
||||
|
||||
return { controller, contextService, popupService, dialogService, popupDisposables };
|
||||
}
|
||||
|
||||
describe('SheetsTableComponentController', () => {
|
||||
it('opens, toggles and reopens the table filter popup based on current filter cell', () => {
|
||||
const { controller, contextService, popupService, popupDisposables } = createController();
|
||||
const info = { unitId: 'unit-1', subUnitId: 'sheet-1', tableId: 'table-1', row: 2, column: 3 };
|
||||
|
||||
controller.openOrToggleFilterPanel(info);
|
||||
|
||||
expect(contextService.setContextValue).toHaveBeenCalledWith(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, true);
|
||||
expect(popupService.attachPopupToCell).toHaveBeenCalledWith(2, 3, expect.objectContaining({
|
||||
componentKey: SHEETS_TABLE_FILTER_PANEL_OPENED_KEY,
|
||||
direction: 'horizontal',
|
||||
offset: [5, 0],
|
||||
portal: true,
|
||||
}));
|
||||
expect(controller.getCurrentTableFilterInfo()).toEqual(info);
|
||||
|
||||
controller.openOrToggleFilterPanel(info);
|
||||
|
||||
expect(contextService.setContextValue).toHaveBeenLastCalledWith(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, false);
|
||||
expect(popupDisposables[0].dispose).toHaveBeenCalled();
|
||||
expect(controller.getCurrentTableFilterInfo()).toBeNull();
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('disposes the previous popup and opens a new one when switching to another filter cell', () => {
|
||||
const { controller, contextService, popupService, popupDisposables } = createController();
|
||||
const firstInfo = { unitId: 'unit-1', subUnitId: 'sheet-1', tableId: 'table-1', row: 2, column: 3 };
|
||||
const secondInfo = { unitId: 'unit-1', subUnitId: 'sheet-1', tableId: 'table-1', row: 2, column: 4 };
|
||||
|
||||
controller.openOrToggleFilterPanel(firstInfo);
|
||||
controller.openOrToggleFilterPanel(secondInfo);
|
||||
|
||||
expect(contextService.setContextValue).toHaveBeenCalledTimes(1);
|
||||
expect(popupDisposables[0].dispose).toHaveBeenCalled();
|
||||
expect(popupService.attachPopupToCell).toHaveBeenLastCalledWith(2, 4, expect.any(Object));
|
||||
expect(controller.getCurrentTableFilterInfo()).toEqual(secondInfo);
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('closes the filter dialog and context when clicking outside the popup', () => {
|
||||
const { controller, popupService, dialogService, contextService } = createController();
|
||||
const info = { unitId: 'unit-1', subUnitId: 'sheet-1', tableId: 'table-1', row: 2, column: 3 };
|
||||
|
||||
controller.openOrToggleFilterPanel(info);
|
||||
const popupCall = popupService.attachPopupToCell.mock.calls[0] as [number, number, { onClickOutside: () => void }];
|
||||
const popupOptions = popupCall[2];
|
||||
popupOptions.onClickOutside();
|
||||
|
||||
expect(dialogService.close).toHaveBeenCalledWith(UNIVER_SHEET_TABLE_FILTER_PANEL_ID);
|
||||
expect(contextService.setContextValue).toHaveBeenLastCalledWith(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, false);
|
||||
expect(controller.getCurrentTableFilterInfo()).toBeNull();
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* 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 { CommandType, ContextService } from '@univerjs/core';
|
||||
import { SelectRangeCommand } from '@univerjs/sheets';
|
||||
import { DeleteSheetTableCommand } from '@univerjs/sheets-table';
|
||||
import { MoveSelectionCommand, SelectAllCommand } from '@univerjs/sheets-ui';
|
||||
import { Subject } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SHEET_TABLE_MENU } from '../../const';
|
||||
import { SheetTableControlsRenderController } from '../sheet-table-controls-render.controller';
|
||||
|
||||
interface ITestTable {
|
||||
getId: () => string;
|
||||
getDisplayName: () => string;
|
||||
getRange: () => { startRow: number; endRow: number; startColumn: number; endColumn: number };
|
||||
getTableStyleId: () => string;
|
||||
}
|
||||
|
||||
function createController(hasSkeleton = true, tables: ITestTable[] = []) {
|
||||
const activeSheet$ = new Subject();
|
||||
const currentSkeleton$ = new Subject();
|
||||
const tableAdd$ = new Subject();
|
||||
const selectionChanged$ = new Subject();
|
||||
const sceneMakeDirty = vi.fn();
|
||||
const executeTableCommand = vi.fn();
|
||||
const popupDisposable = { dispose: vi.fn() };
|
||||
const attachPopupByPosition = vi.fn((_bound: unknown, _popup: unknown, _location: unknown) => popupDisposable);
|
||||
const skeleton = hasSkeleton ? { gapConfig: { rowGaps: {} } } : null;
|
||||
let commandListener: ((command: { id: string; type: CommandType }) => void) | undefined;
|
||||
|
||||
const controller = new SheetTableControlsRenderController(
|
||||
{
|
||||
unit: {
|
||||
activeSheet$,
|
||||
getActiveSheet: () => ({ getSheetId: () => 'sheet-1' }),
|
||||
getUnitId: () => 'unit-1',
|
||||
},
|
||||
scene: {
|
||||
addObjects: vi.fn(),
|
||||
removeObjects: vi.fn(),
|
||||
makeDirty: sceneMakeDirty,
|
||||
setCursor: vi.fn(),
|
||||
resetCursor: vi.fn(),
|
||||
},
|
||||
components: { get: vi.fn() },
|
||||
} as never,
|
||||
{} as never,
|
||||
{
|
||||
currentSkeleton$,
|
||||
getCurrentSkeleton: () => skeleton,
|
||||
} as never,
|
||||
{
|
||||
onCommandExecuted: (listener: (command: { id: string; type: CommandType }) => void) => {
|
||||
commandListener = listener;
|
||||
return { dispose: vi.fn() };
|
||||
},
|
||||
executeCommand: executeTableCommand,
|
||||
} as never,
|
||||
{
|
||||
tableAdd$,
|
||||
tableDelete$: new Subject(),
|
||||
tableNameChanged$: new Subject(),
|
||||
tableRangeChanged$: new Subject(),
|
||||
tableThemeChanged$: new Subject(),
|
||||
getTablesBySubunitId: () => tables,
|
||||
getTableById: (_unitId: string, tableId: string) => tables.find((table) => table.getId() === tableId),
|
||||
} as never,
|
||||
{ getRangeThemeStyle: vi.fn() } as never,
|
||||
{ unitPermissionInitStateChange$: new Subject() } as never,
|
||||
{
|
||||
permissionPointUpdate$: new Subject(),
|
||||
getPermissionPoint: () => ({ value: true }),
|
||||
} as never,
|
||||
{
|
||||
selectionChanged$,
|
||||
getCurrentSelections: () => [],
|
||||
} as never,
|
||||
{ resetSelectionsByModelData: vi.fn() } as never,
|
||||
{ refreshTable$: new Subject() } as never,
|
||||
{ t: (key: string) => key } as never,
|
||||
new ContextService(),
|
||||
{} as never,
|
||||
{} as never,
|
||||
{ attachPopupByPosition } as never
|
||||
);
|
||||
|
||||
sceneMakeDirty.mockClear();
|
||||
return {
|
||||
controller,
|
||||
sceneMakeDirty,
|
||||
selectionChanged$,
|
||||
tableAdd$,
|
||||
attachPopupByPosition,
|
||||
executeTableCommand,
|
||||
popupDisposable,
|
||||
executeCommand: (command: { id: string; type: CommandType }) => commandListener?.(command),
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetTableControlsRenderController', () => {
|
||||
it('keeps sheet caches for selection refreshes and invalidates them for table refreshes', () => {
|
||||
const { controller, sceneMakeDirty, selectionChanged$, tableAdd$, executeCommand } = createController();
|
||||
|
||||
selectionChanged$.next({});
|
||||
executeCommand({ id: MoveSelectionCommand.id, type: CommandType.COMMAND });
|
||||
executeCommand({ id: SelectAllCommand.id, type: CommandType.COMMAND });
|
||||
executeCommand({ id: SelectRangeCommand.id, type: CommandType.COMMAND });
|
||||
executeCommand({ id: 'doc.mutation.rich-text-editing', type: CommandType.MUTATION });
|
||||
expect(sceneMakeDirty).not.toHaveBeenCalled();
|
||||
|
||||
tableAdd$.next({});
|
||||
expect(sceneMakeDirty).toHaveBeenCalledOnce();
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('clears unavailable controls without invalidating sheet caches on selection changes', () => {
|
||||
const { controller, sceneMakeDirty, selectionChanged$ } = createController(false);
|
||||
const shape = Reflect.get(controller, '_shape') as {
|
||||
setItems: (items: unknown[]) => void;
|
||||
refreshBounds: () => void;
|
||||
};
|
||||
const setItems = vi.spyOn(shape, 'setItems');
|
||||
const refreshBounds = vi.spyOn(shape, 'refreshBounds');
|
||||
|
||||
selectionChanged$.next({});
|
||||
|
||||
expect(setItems).toHaveBeenCalledWith([]);
|
||||
expect(refreshBounds).toHaveBeenCalledOnce();
|
||||
expect(sceneMakeDirty).not.toHaveBeenCalled();
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
|
||||
it('opens the registered design menu and routes its actions through the existing table commands', async () => {
|
||||
const table: ITestTable = {
|
||||
getId: () => 'table-1',
|
||||
getDisplayName: () => 'Orders',
|
||||
getRange: () => ({ startRow: 1, endRow: 3, startColumn: 2, endColumn: 4 }),
|
||||
getTableStyleId: () => 'theme-1',
|
||||
};
|
||||
const { attachPopupByPosition, controller, executeTableCommand, popupDisposable } = createController(true, [table]);
|
||||
const shape = Reflect.get(controller, '_shape') as {
|
||||
getAnchorRegion: (tableId: string) => unknown;
|
||||
};
|
||||
vi.spyOn(shape, 'getAnchorRegion').mockReturnValue({
|
||||
type: 'anchor-main',
|
||||
tableId: 'table-1',
|
||||
left: 40,
|
||||
top: 20,
|
||||
width: 122,
|
||||
height: 28,
|
||||
});
|
||||
const handleHit = Reflect.get(controller, '_handleHit') as (hit: object) => void;
|
||||
|
||||
handleHit.call(controller, { type: 'anchor-menu-toggle', tableId: 'table-1' });
|
||||
|
||||
expect(attachPopupByPosition).toHaveBeenCalledWith(
|
||||
{ left: 40, right: 162, top: 20, bottom: 48 },
|
||||
expect.objectContaining({ componentKey: SHEET_TABLE_MENU }),
|
||||
{ unitId: 'unit-1', subUnitId: 'sheet-1', row: 1, col: 2 }
|
||||
);
|
||||
const popup = attachPopupByPosition.mock.calls[0][1] as {
|
||||
extraProps: {
|
||||
labels: Record<string, string>;
|
||||
onSelect: (action: 'delete') => void | Promise<void>;
|
||||
};
|
||||
};
|
||||
expect(popup.extraProps.labels).toEqual({
|
||||
rename: 'sheets-table-ui.rename',
|
||||
'update-range': 'sheets-table-ui.updateRange',
|
||||
'set-theme': 'sheets-table-ui.setTheme',
|
||||
delete: 'sheets-table-ui.removeTable',
|
||||
});
|
||||
|
||||
await popup.extraProps.onSelect('delete');
|
||||
|
||||
expect(popupDisposable.dispose).toHaveBeenCalledOnce();
|
||||
expect(executeTableCommand).toHaveBeenCalledWith(DeleteSheetTableCommand.id, {
|
||||
tableId: 'table-1',
|
||||
subUnitId: 'sheet-1',
|
||||
unitId: 'unit-1',
|
||||
});
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,166 @@
|
||||
/**
|
||||
* 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 { INTERCEPTOR_POINT } from '@univerjs/sheets';
|
||||
import { Subject } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { FILTER_ICON_PADDING, FILTER_ICON_SIZE } from '../../views/widgets/table-filter-button.shape';
|
||||
import { SheetsTableFilterButtonRenderController } from '../sheet-table-filter-button-render.controller';
|
||||
|
||||
vi.hoisted(() => {
|
||||
class FakePath2D {
|
||||
constructor(_path?: string) {}
|
||||
}
|
||||
(globalThis as unknown as { Path2D: typeof FakePath2D }).Path2D = FakePath2D;
|
||||
});
|
||||
|
||||
describe('SheetsTableFilterButtonRenderController', () => {
|
||||
it('should dispose rendered button shapes and interceptor resource', () => {
|
||||
const controller = new SheetsTableFilterButtonRenderController(
|
||||
{
|
||||
unit: {
|
||||
getUnitId: () => 'u1',
|
||||
getActiveSheet: () => null,
|
||||
},
|
||||
unitId: 'u1',
|
||||
scene: {
|
||||
addObjects: vi.fn(),
|
||||
makeDirty: vi.fn(),
|
||||
},
|
||||
} as any,
|
||||
{ createInstance: vi.fn() } as any,
|
||||
{
|
||||
currentSkeleton$: new Subject(),
|
||||
getCurrentSkeleton: () => null,
|
||||
} as any,
|
||||
{
|
||||
intercept: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
} as any,
|
||||
{
|
||||
tableAdd$: new Subject(),
|
||||
tableNameChanged$: new Subject(),
|
||||
tableRangeChanged$: new Subject(),
|
||||
tableThemeChanged$: new Subject(),
|
||||
tableDelete$: new Subject(),
|
||||
tableFilterChanged$: new Subject(),
|
||||
getSheetFilterRangeWithState: vi.fn(() => []),
|
||||
} as any,
|
||||
{
|
||||
getRangeThemeStyle: vi.fn(),
|
||||
} as any,
|
||||
{
|
||||
onCommandExecuted: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
} as any
|
||||
);
|
||||
|
||||
const shapeDispose = vi.fn();
|
||||
const interceptorDispose = vi.fn();
|
||||
|
||||
(controller as any)._tableFilterButtonShapes = [{ dispose: shapeDispose }];
|
||||
(controller as any)._buttonRenderDisposable = { dispose: interceptorDispose };
|
||||
|
||||
controller.dispose();
|
||||
|
||||
expect(shapeDispose).toHaveBeenCalledTimes(1);
|
||||
expect(interceptorDispose).toHaveBeenCalledTimes(1);
|
||||
expect((controller as any)._tableFilterButtonShapes).toEqual([]);
|
||||
expect((controller as any)._buttonRenderDisposable).toBeNull();
|
||||
});
|
||||
|
||||
it('adds right padding only to table header cells covered by filter buttons', () => {
|
||||
let interceptor: any;
|
||||
const controller = new SheetsTableFilterButtonRenderController(
|
||||
{
|
||||
unit: {
|
||||
getUnitId: () => 'u1',
|
||||
getActiveSheet: () => null,
|
||||
},
|
||||
unitId: 'u1',
|
||||
scene: {
|
||||
addObjects: vi.fn(),
|
||||
makeDirty: vi.fn(),
|
||||
},
|
||||
} as any,
|
||||
{ createInstance: vi.fn() } as any,
|
||||
{
|
||||
currentSkeleton$: new Subject(),
|
||||
getCurrentSkeleton: () => null,
|
||||
} as any,
|
||||
{
|
||||
intercept: vi.fn((point, config) => {
|
||||
expect(point).toBe(INTERCEPTOR_POINT.CELL_CONTENT);
|
||||
interceptor = config;
|
||||
return { dispose: vi.fn() };
|
||||
}),
|
||||
} as any,
|
||||
{
|
||||
tableAdd$: new Subject(),
|
||||
tableNameChanged$: new Subject(),
|
||||
tableRangeChanged$: new Subject(),
|
||||
tableThemeChanged$: new Subject(),
|
||||
tableDelete$: new Subject(),
|
||||
tableFilterChanged$: new Subject(),
|
||||
getSheetFilterRangeWithState: vi.fn(() => []),
|
||||
} as any,
|
||||
{
|
||||
getRangeThemeStyle: vi.fn(),
|
||||
} as any,
|
||||
{
|
||||
onCommandExecuted: vi.fn(() => ({ dispose: vi.fn() })),
|
||||
} as any
|
||||
);
|
||||
|
||||
(controller as any)._interceptCellContent('u1', 's1', {
|
||||
startRow: 4,
|
||||
endRow: 8,
|
||||
startColumn: 2,
|
||||
endColumn: 4,
|
||||
});
|
||||
|
||||
const rawCell = { v: 'Header', fontRenderExtension: { leftOffset: 3 } };
|
||||
const headerResult = interceptor.handler(rawCell, {
|
||||
unitId: 'u1',
|
||||
subUnitId: 's1',
|
||||
row: 4,
|
||||
col: 3,
|
||||
rawData: rawCell,
|
||||
}, (cell: unknown) => cell);
|
||||
|
||||
expect(headerResult).not.toBe(rawCell);
|
||||
expect(headerResult.fontRenderExtension).toEqual({
|
||||
leftOffset: 3,
|
||||
rightOffset: FILTER_ICON_SIZE + FILTER_ICON_PADDING + 2,
|
||||
});
|
||||
|
||||
const bodyCell = { v: 'Body' };
|
||||
expect(interceptor.handler(bodyCell, {
|
||||
unitId: 'u1',
|
||||
subUnitId: 's1',
|
||||
row: 5,
|
||||
col: 3,
|
||||
rawData: bodyCell,
|
||||
}, (cell: unknown) => cell)).toBe(bodyCell);
|
||||
expect(interceptor.handler(bodyCell, {
|
||||
unitId: 'u1',
|
||||
subUnitId: 'other',
|
||||
row: 4,
|
||||
col: 3,
|
||||
rawData: bodyCell,
|
||||
}, (cell: unknown) => cell)).toBe(bodyCell);
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -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 { Subject } from 'rxjs';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SheetsTableRenderController } from '../sheet-table-render.controller';
|
||||
|
||||
describe('SheetsTableRenderController', () => {
|
||||
it('should dirty skeleton and recalculate on table/theme refresh events', () => {
|
||||
const tableAdd$ = new Subject<any>();
|
||||
const tableDelete$ = new Subject<any>();
|
||||
const tableNameChanged$ = new Subject<any>();
|
||||
const tableRangeChanged$ = new Subject<any>();
|
||||
const tableThemeChanged$ = new Subject<any>();
|
||||
const tableFilterChanged$ = new Subject<any>();
|
||||
const tableInitStatus$ = new Subject<any>();
|
||||
const refreshTable$ = new Subject<number>();
|
||||
|
||||
const makeDirty = vi.fn();
|
||||
const reCalculate = vi.fn();
|
||||
|
||||
const controller = new SheetsTableRenderController(
|
||||
{
|
||||
mainComponent: { makeDirty },
|
||||
} as any,
|
||||
{} as any,
|
||||
{
|
||||
getCurrentParam: () => ({ unitId: 'u1', subUnitId: 's1' }),
|
||||
reCalculate,
|
||||
} as any,
|
||||
{
|
||||
tableAdd$,
|
||||
tableDelete$,
|
||||
tableNameChanged$,
|
||||
tableRangeChanged$,
|
||||
tableThemeChanged$,
|
||||
tableFilterChanged$,
|
||||
tableInitStatus$,
|
||||
} as any,
|
||||
{
|
||||
refreshTable$,
|
||||
} as any
|
||||
);
|
||||
|
||||
tableAdd$.next({});
|
||||
tableDelete$.next({});
|
||||
tableNameChanged$.next({});
|
||||
tableRangeChanged$.next({});
|
||||
tableThemeChanged$.next({});
|
||||
tableFilterChanged$.next({});
|
||||
tableInitStatus$.next(true);
|
||||
refreshTable$.next(1);
|
||||
|
||||
expect(makeDirty).toHaveBeenCalled();
|
||||
expect(reCalculate).toHaveBeenCalledWith({ unitId: 'u1', subUnitId: 's1', dirty: true });
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* 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 { SetSelectionsOperation } from '@univerjs/sheets';
|
||||
import { SelectAllCommand } from '@univerjs/sheets-ui';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SheetTableSelectionController } from '../sheet-table-selection.controller';
|
||||
|
||||
describe('SheetTableSelectionController', () => {
|
||||
it('should toggle select-all behavior between table body and table with header', () => {
|
||||
let interceptCommandConfig: any;
|
||||
|
||||
const tableRange = { startRow: 2, endRow: 5, startColumn: 1, endColumn: 3 };
|
||||
const table = {
|
||||
getRange: () => tableRange,
|
||||
};
|
||||
|
||||
const worksheet = {
|
||||
getSheetId: () => 's1',
|
||||
getMergedCell: () => null,
|
||||
};
|
||||
|
||||
const workbook = {
|
||||
getUnitId: () => 'u1',
|
||||
getActiveSheet: () => worksheet,
|
||||
};
|
||||
|
||||
const controller = new SheetTableSelectionController(
|
||||
{
|
||||
interceptCommand: vi.fn((config: any) => {
|
||||
interceptCommandConfig = config;
|
||||
return { dispose: vi.fn() };
|
||||
}),
|
||||
} as any,
|
||||
{
|
||||
getCurrentUnitOfType: () => workbook,
|
||||
} as any,
|
||||
{
|
||||
getTablesBySubunitId: () => [table],
|
||||
} as any
|
||||
);
|
||||
|
||||
const selectWholeTable = interceptCommandConfig.getMutations({
|
||||
id: SelectAllCommand.id,
|
||||
params: { range: tableRange },
|
||||
});
|
||||
expect(selectWholeTable).toEqual({ redos: [], undos: [] });
|
||||
|
||||
const tableBodyRange = { ...tableRange, startRow: tableRange.startRow + 1 };
|
||||
const selectBodyThenExpandToHeader = interceptCommandConfig.getMutations({
|
||||
id: SelectAllCommand.id,
|
||||
params: { range: tableBodyRange },
|
||||
});
|
||||
|
||||
expect(selectBodyThenExpandToHeader.redos).toHaveLength(1);
|
||||
expect(selectBodyThenExpandToHeader.redos[0]).toEqual({
|
||||
id: SetSelectionsOperation.id,
|
||||
params: expect.objectContaining({
|
||||
unitId: 'u1',
|
||||
subUnitId: 's1',
|
||||
selections: [expect.objectContaining({ range: tableRange })],
|
||||
}),
|
||||
});
|
||||
|
||||
const randomRange = { startRow: 3, endRow: 3, startColumn: 1, endColumn: 2 };
|
||||
const selectRandomThenBody = interceptCommandConfig.getMutations({
|
||||
id: SelectAllCommand.id,
|
||||
params: { range: randomRange },
|
||||
});
|
||||
expect(selectRandomThenBody.redos).toHaveLength(1);
|
||||
expect(selectRandomThenBody.redos[0]).toEqual({
|
||||
id: SetSelectionsOperation.id,
|
||||
params: expect.objectContaining({
|
||||
selections: [expect.objectContaining({ range: tableBodyRange })],
|
||||
}),
|
||||
});
|
||||
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SetRangeThemeMutation } from '@univerjs/sheets';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { SheetTableThemeUIController } from '../sheet-table-theme-ui.controller';
|
||||
|
||||
describe('SheetTableThemeUIController', () => {
|
||||
it('should emit refresh signal only for table custom theme updates', () => {
|
||||
const listeners: Array<(command: any) => void> = [];
|
||||
|
||||
const controller = new SheetTableThemeUIController({
|
||||
onCommandExecuted: (listener: (command: any) => void) => {
|
||||
listeners.push(listener);
|
||||
return { dispose: vi.fn() };
|
||||
},
|
||||
} as any);
|
||||
|
||||
const values: number[] = [];
|
||||
const subscription = controller.refreshTable$.subscribe((value) => values.push(value));
|
||||
|
||||
listeners.forEach((listener) => listener({
|
||||
id: SetRangeThemeMutation.id,
|
||||
params: { styleName: 'table-custom-2026' },
|
||||
}));
|
||||
|
||||
listeners.forEach((listener) => listener({
|
||||
id: SetRangeThemeMutation.id,
|
||||
params: { styleName: 'table-default-0' },
|
||||
}));
|
||||
|
||||
expect(values.length).toBe(1);
|
||||
expect(typeof values[0]).toBe('number');
|
||||
|
||||
subscription.unsubscribe();
|
||||
controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Disposable, Inject } from '@univerjs/core';
|
||||
import { TableIcon } from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import {
|
||||
SHEET_TABLE_MENU,
|
||||
SHEET_TABLE_RENAME_DIALOG,
|
||||
SHEET_TABLE_THEME_PANEL,
|
||||
SHEETS_TABLE_FILTER_PANEL_OPENED_KEY,
|
||||
TABLE_SELECTOR_DIALOG,
|
||||
} from '../const';
|
||||
import { SheetTableFilterPanel } from '../views/components/SheetTableFilterPanel';
|
||||
import { SheetTableMenu } from '../views/components/SheetTableMenu';
|
||||
import { SheetTableRenameDialog } from '../views/components/SheetTableRenameDialog';
|
||||
import { SheetTableSelector } from '../views/components/SheetTableSelector';
|
||||
import { SheetTableThemePanel } from '../views/components/SheetTableThemePanel';
|
||||
|
||||
export class ComponentsController extends Disposable {
|
||||
constructor(
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
|
||||
@Inject(IconManager) private readonly _iconManager: IconManager
|
||||
) {
|
||||
super();
|
||||
|
||||
this._registerComponents();
|
||||
this._registerIcons();
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
([
|
||||
[SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, SheetTableFilterPanel],
|
||||
[SHEET_TABLE_MENU, SheetTableMenu],
|
||||
[SHEET_TABLE_RENAME_DIALOG, SheetTableRenameDialog],
|
||||
] as const).forEach(([key, comp]) => {
|
||||
this.disposeWithMe(this._componentManager.register(key, comp));
|
||||
});
|
||||
|
||||
([
|
||||
[TABLE_SELECTOR_DIALOG, SheetTableSelector],
|
||||
[SHEET_TABLE_THEME_PANEL, SheetTableThemePanel],
|
||||
] as const).forEach(([key, comp]) => {
|
||||
this.disposeWithMe(this._componentManager.register(key, comp));
|
||||
});
|
||||
}
|
||||
|
||||
private _registerIcons(): void {
|
||||
this.disposeWithMe(this._iconManager.register({
|
||||
TableIcon,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Disposable, Inject } from '@univerjs/core';
|
||||
import { TableIcon } from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import {
|
||||
SHEET_TABLE_MOBILE_MENU,
|
||||
SHEET_TABLE_RENAME_DIALOG,
|
||||
SHEET_TABLE_THEME_PANEL,
|
||||
SHEETS_TABLE_FILTER_PANEL_OPENED_KEY,
|
||||
TABLE_SELECTOR_DIALOG,
|
||||
} from '../../const';
|
||||
import { MobileSheetTableFilterPanel } from '../../views/components/MobileSheetTableFilterPanel';
|
||||
import { MobileSheetTableMenu } from '../../views/components/MobileSheetTableMenu';
|
||||
import { MobileSheetTableRenameDialog } from '../../views/components/MobileSheetTableRenameDialog';
|
||||
import { MobileSheetTableSelector } from '../../views/components/MobileSheetTableSelector';
|
||||
import { MobileSheetTableThemePanel } from '../../views/components/MobileSheetTableThemePanel';
|
||||
|
||||
export class MobileComponentsController extends Disposable {
|
||||
constructor(
|
||||
@Inject(ComponentManager) componentManager: ComponentManager,
|
||||
@Inject(IconManager) iconManager: IconManager
|
||||
) {
|
||||
super();
|
||||
|
||||
([
|
||||
[SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, MobileSheetTableFilterPanel],
|
||||
[SHEET_TABLE_MOBILE_MENU, MobileSheetTableMenu],
|
||||
[SHEET_TABLE_RENAME_DIALOG, MobileSheetTableRenameDialog],
|
||||
[TABLE_SELECTOR_DIALOG, MobileSheetTableSelector],
|
||||
[SHEET_TABLE_THEME_PANEL, MobileSheetTableThemePanel],
|
||||
] as const).forEach(([key, component]) => {
|
||||
this.disposeWithMe(componentManager.register(key, component));
|
||||
});
|
||||
this.disposeWithMe(iconManager.register({ TableIcon }));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, UNIVER_SHEET_TABLE_FILTER_PANEL_ID } from '../../const';
|
||||
import { SheetsTableComponentController } from '../sheet-table-component.controller';
|
||||
|
||||
export class SheetsTableMobileComponentController extends SheetsTableComponentController {
|
||||
protected override _openFilterPopup(): void {
|
||||
if (!this.getCurrentTableFilterInfo()) {
|
||||
throw new Error('[SheetsFilterUIController]: no filter model when opening filter popup!');
|
||||
}
|
||||
|
||||
this._dialogService.open({
|
||||
id: UNIVER_SHEET_TABLE_FILTER_PANEL_ID,
|
||||
title: { title: 'sheets-table-ui.filter.title' },
|
||||
children: { label: SHEETS_TABLE_FILTER_PANEL_OPENED_KEY },
|
||||
onClose: () => {
|
||||
this._contextService.setContextValue(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, false);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { SheetTableMenuAction } from '../../views/components/SheetTableMenu';
|
||||
import { toDisposable } from '@univerjs/core';
|
||||
import { SHEET_TABLE_MOBILE_MENU } from '../../const';
|
||||
import { SheetTableControlsRenderController } from '../sheet-table-controls-render.controller';
|
||||
|
||||
const SHEET_TABLE_MOBILE_MENU_DIALOG_ID = 'sheet-table-mobile-menu';
|
||||
|
||||
export class SheetTableControlsMobileRenderController extends SheetTableControlsRenderController {
|
||||
protected override _toggleTableMenu(unitId: string, subUnitId: string, tableId: string): void {
|
||||
if (this._shape.getOpenedMenuTableId() === tableId) {
|
||||
this._closeFloatingControls();
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this._tableManager.getTableById(unitId, tableId);
|
||||
const anchor = this._shape.getAnchorRegion(tableId);
|
||||
if (!table || !anchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._closeFloatingControls();
|
||||
this._shape.setOpenedMenuTableId(tableId);
|
||||
const extraProps = {
|
||||
anchorWidth: anchor.width,
|
||||
tableName: table.getDisplayName(),
|
||||
labels: {
|
||||
rename: this._localeService.t<LocaleKey>('sheets-table-ui.rename'),
|
||||
'update-range': this._localeService.t<LocaleKey>('sheets-table-ui.updateRange'),
|
||||
'set-theme': this._localeService.t<LocaleKey>('sheets-table-ui.setTheme'),
|
||||
delete: this._localeService.t<LocaleKey>('sheets-table-ui.removeTable'),
|
||||
},
|
||||
onSelect: (action: SheetTableMenuAction) => this._handleTableMenuAction(action, unitId, subUnitId, tableId),
|
||||
onClose: () => this._closeFloatingControls(),
|
||||
};
|
||||
|
||||
this._dialogService.open({
|
||||
id: SHEET_TABLE_MOBILE_MENU_DIALOG_ID,
|
||||
title: { title: table.getDisplayName() },
|
||||
children: {
|
||||
label: {
|
||||
name: SHEET_TABLE_MOBILE_MENU,
|
||||
props: { menu: extraProps },
|
||||
},
|
||||
},
|
||||
onClose: () => this._closeFloatingControls(),
|
||||
});
|
||||
this._menuPopup = toDisposable(() => this._dialogService.close(SHEET_TABLE_MOBILE_MENU_DIALOG_ID));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDisposable, Nullable } from '@univerjs/core';
|
||||
import { Disposable, IContextService, Inject } from '@univerjs/core';
|
||||
import { SheetCanvasPopManagerService } from '@univerjs/sheets-ui';
|
||||
import { IDialogService } from '@univerjs/ui';
|
||||
import { distinctUntilChanged, startWith } from 'rxjs';
|
||||
import { SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, UNIVER_SHEET_TABLE_FILTER_PANEL_ID } from '../const';
|
||||
|
||||
export interface ITableFilterPanelInfo {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableId: string;
|
||||
column: number;
|
||||
row: number;
|
||||
}
|
||||
export class SheetsTableComponentController extends Disposable {
|
||||
private _popupDisposable?: Nullable<IDisposable>;
|
||||
private _currentTableFilterInfo: Nullable<ITableFilterPanelInfo> = null;
|
||||
constructor(
|
||||
@IContextService protected readonly _contextService: IContextService,
|
||||
@Inject(SheetCanvasPopManagerService) private _sheetCanvasPopupService: SheetCanvasPopManagerService,
|
||||
@Inject(IDialogService) protected readonly _dialogService: IDialogService
|
||||
|
||||
) {
|
||||
super();
|
||||
this._initUIPopup();
|
||||
}
|
||||
|
||||
public setCurrentTableFilterInfo(info: ITableFilterPanelInfo): void {
|
||||
this._currentTableFilterInfo = info;
|
||||
}
|
||||
|
||||
public openOrToggleFilterPanel(info: ITableFilterPanelInfo): void {
|
||||
const opened = this._contextService.getContextValue(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY);
|
||||
|
||||
if (opened && this._isSameFilterPanelInfo(this._currentTableFilterInfo, info)) {
|
||||
this.closeFilterPanel();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setCurrentTableFilterInfo(info);
|
||||
|
||||
if (opened) {
|
||||
this._popupDisposable?.dispose();
|
||||
this._popupDisposable = null;
|
||||
this._openFilterPopup();
|
||||
return;
|
||||
}
|
||||
|
||||
this._contextService.setContextValue(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, true);
|
||||
}
|
||||
|
||||
public clearCurrentTableFilterInfo(): void {
|
||||
this._currentTableFilterInfo = null;
|
||||
}
|
||||
|
||||
public getCurrentTableFilterInfo(): Nullable<ITableFilterPanelInfo> {
|
||||
return this._currentTableFilterInfo;
|
||||
}
|
||||
|
||||
private _initUIPopup() {
|
||||
this.disposeWithMe(this._contextService.subscribeContextValue$(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY)
|
||||
|
||||
.pipe(startWith(undefined), distinctUntilChanged())
|
||||
.subscribe((open) => {
|
||||
if (open) {
|
||||
this._openFilterPopup();
|
||||
} else if (open === false) {
|
||||
this._closeFilterPopup();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public closeFilterPanel(): void {
|
||||
this._contextService.setContextValue(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, false);
|
||||
}
|
||||
|
||||
protected _openFilterPopup(): void {
|
||||
const currentFilterModel = this._currentTableFilterInfo;
|
||||
if (!currentFilterModel) {
|
||||
throw new Error('[SheetsFilterUIController]: no filter model when opening filter popup!');
|
||||
}
|
||||
|
||||
const { row: startRow, column: col } = currentFilterModel;
|
||||
this._popupDisposable = this._sheetCanvasPopupService.attachPopupToCell(startRow, col, {
|
||||
componentKey: SHEETS_TABLE_FILTER_PANEL_OPENED_KEY,
|
||||
direction: 'horizontal',
|
||||
onClickOutside: () => {
|
||||
this._dialogService.close(UNIVER_SHEET_TABLE_FILTER_PANEL_ID);
|
||||
this._contextService.setContextValue(SHEETS_TABLE_FILTER_PANEL_OPENED_KEY, false);
|
||||
},
|
||||
offset: [5, 0],
|
||||
portal: true,
|
||||
});
|
||||
}
|
||||
|
||||
private _closeFilterPopup(): void {
|
||||
this._dialogService.close(UNIVER_SHEET_TABLE_FILTER_PANEL_ID);
|
||||
this._popupDisposable?.dispose();
|
||||
this._popupDisposable = null;
|
||||
this.clearCurrentTableFilterInfo();
|
||||
}
|
||||
|
||||
private _isSameFilterPanelInfo(a: Nullable<ITableFilterPanelInfo>, b: ITableFilterPanelInfo): boolean {
|
||||
return Boolean(a && a.unitId === b.unitId && a.subUnitId === b.subUnitId && a.tableId === b.tableId && a.column === b.column && a.row === b.row);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,600 @@
|
||||
/**
|
||||
* 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 { EventState, IDisposable, IRange, Workbook } from '@univerjs/core';
|
||||
import type {
|
||||
IMouseEvent,
|
||||
IPointerEvent,
|
||||
IRenderContext,
|
||||
IRenderModule,
|
||||
SpreadsheetSkeleton,
|
||||
} from '@univerjs/engine-render';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import type { SheetTableMenuAction } from '../views/components/SheetTableMenu';
|
||||
import type { ITableControlHitRegion } from '../views/widgets/table-controls-util';
|
||||
import {
|
||||
CommandType,
|
||||
Disposable,
|
||||
fromCallback,
|
||||
ICommandService,
|
||||
IContextService,
|
||||
Inject,
|
||||
Injector,
|
||||
IPermissionService,
|
||||
LocaleService,
|
||||
toDisposable,
|
||||
} from '@univerjs/core';
|
||||
import { CURSOR_TYPE } from '@univerjs/engine-render';
|
||||
import {
|
||||
SelectRangeCommand,
|
||||
SheetRangeThemeModel,
|
||||
SheetsSelectionsService,
|
||||
WorkbookEditablePermission,
|
||||
WorkbookPermissionService,
|
||||
} from '@univerjs/sheets';
|
||||
import {
|
||||
DeleteSheetTableCommand,
|
||||
SetSheetTableCommand,
|
||||
SheetTableInsertColumnAtCommand,
|
||||
SheetTableInsertRowAtCommand,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import {
|
||||
ExpandSelectionCommand,
|
||||
getTransformCoord,
|
||||
ISheetSelectionRenderService,
|
||||
MoveSelectionCommand,
|
||||
MoveSelectionEnterAndTabCommand,
|
||||
SelectAllCommand,
|
||||
SetScrollOperation,
|
||||
SetZoomRatioOperation,
|
||||
SHEET_VIEW_KEY,
|
||||
SheetCanvasPopManagerService,
|
||||
SheetSkeletonManagerService,
|
||||
} from '@univerjs/sheets-ui';
|
||||
import { IDialogService, ISidebarService } from '@univerjs/ui';
|
||||
import { filter, merge } from 'rxjs';
|
||||
import { openRangeSelector } from '../commands/operations/open-table-selector.operation';
|
||||
import {
|
||||
SHEET_TABLE_MENU,
|
||||
SHEET_TABLE_RENAME_DIALOG,
|
||||
SHEET_TABLE_RENAME_DIALOG_ID,
|
||||
SHEET_TABLE_THEME_PANEL,
|
||||
SHEET_TABLE_THEME_PANEL_ID,
|
||||
} from '../const';
|
||||
import { TABLE_CONTROL_INSERT_BUTTON_SIZE, TABLE_CONTROL_TOP_GAP_SIZE } from '../views/widgets/table-controls-util';
|
||||
import { SheetTableControlsShape } from '../views/widgets/table-controls.shape';
|
||||
import { SheetTableThemeUIController } from './sheet-table-theme-ui.controller';
|
||||
|
||||
const TABLE_CONTROLS_LAYER_INDEX = 5002;
|
||||
const TABLE_CONTROL_GAP_ROW = 0;
|
||||
const TABLE_RENDER_REFRESH_COMMANDS = new Set([
|
||||
SetScrollOperation.id,
|
||||
SetZoomRatioOperation.id,
|
||||
]);
|
||||
const SELECTION_ONLY_COMMANDS = new Set([
|
||||
MoveSelectionCommand.id,
|
||||
MoveSelectionEnterAndTabCommand.id,
|
||||
ExpandSelectionCommand.id,
|
||||
SelectAllCommand.id,
|
||||
SelectRangeCommand.id,
|
||||
]);
|
||||
|
||||
type TopGapSnapshot = { size: number; color?: string; stripeColor?: string } | null;
|
||||
|
||||
function isSameTopGap(left: TopGapSnapshot, right: TopGapSnapshot): boolean {
|
||||
if (left === null || right === null) {
|
||||
return left === right;
|
||||
}
|
||||
|
||||
return left.size === right.size && left.color === right.color && left.stripeColor === right.stripeColor;
|
||||
}
|
||||
|
||||
export class SheetTableControlsRenderController extends Disposable implements IRenderModule {
|
||||
protected readonly _shape: SheetTableControlsShape;
|
||||
private readonly _topGapBaseBySkeleton = new WeakMap<SpreadsheetSkeleton, TopGapSnapshot>();
|
||||
protected _menuPopup: IDisposable | null = null;
|
||||
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@Inject(Injector) private readonly _injector: Injector,
|
||||
@Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(TableManager) protected readonly _tableManager: TableManager,
|
||||
@Inject(SheetRangeThemeModel) private readonly _rangeThemeModel: SheetRangeThemeModel,
|
||||
@Inject(WorkbookPermissionService) private readonly _workbookPermissionService: WorkbookPermissionService,
|
||||
@Inject(IPermissionService) private readonly _permissionService: IPermissionService,
|
||||
@Inject(SheetsSelectionsService) private readonly _sheetsSelectionsService: SheetsSelectionsService,
|
||||
@ISheetSelectionRenderService private readonly _selectionRenderService: ISheetSelectionRenderService,
|
||||
@Inject(SheetTableThemeUIController) private readonly _sheetTableThemeUIController: SheetTableThemeUIController,
|
||||
@Inject(LocaleService) protected readonly _localeService: LocaleService,
|
||||
@IContextService private readonly _contextService: IContextService,
|
||||
@IDialogService protected readonly _dialogService: IDialogService,
|
||||
@ISidebarService private readonly _sidebarService: ISidebarService,
|
||||
@Inject(SheetCanvasPopManagerService) protected readonly _sheetCanvasPopupService: SheetCanvasPopManagerService
|
||||
) {
|
||||
super();
|
||||
this._shape = new SheetTableControlsShape(
|
||||
'SheetTableControlsShape',
|
||||
() => this._sheetSkeletonManagerService.getCurrentSkeleton() || null
|
||||
);
|
||||
this._initShape();
|
||||
this._initRefresh();
|
||||
this._refresh();
|
||||
}
|
||||
|
||||
private _initShape(): void {
|
||||
this._context.scene.addObjects([this._shape], TABLE_CONTROLS_LAYER_INDEX);
|
||||
this.disposeWithMe(toDisposable(() => {
|
||||
this._closeFloatingControls();
|
||||
this._context.scene.removeObjects([this._shape]);
|
||||
}));
|
||||
|
||||
this.disposeWithMe(this._shape.onPointerMove$.subscribeEvent((evt: IPointerEvent | IMouseEvent, state) => {
|
||||
this._handlePointerMove(evt, state);
|
||||
}));
|
||||
this.disposeWithMe(this._shape.onPointerLeave$.subscribeEvent((_evt: IPointerEvent | IMouseEvent, state) => {
|
||||
this._handlePointerLeave(state);
|
||||
}));
|
||||
this.disposeWithMe(this._shape.onPointerDown$.subscribeEvent((evt: IPointerEvent | IMouseEvent, state) => {
|
||||
this._handlePointerDown(evt, state);
|
||||
}));
|
||||
|
||||
this.disposeWithMe(this._context.components.get(SHEET_VIEW_KEY.MAIN)?.onPointerMove$.subscribeEvent((evt: IPointerEvent | IMouseEvent) => {
|
||||
const point = this._getLocalPoint(evt);
|
||||
const insertRegion = this._getInsertRegionFromPoint(point.x, point.y);
|
||||
this._shape.setHoveredInsertRegion(insertRegion);
|
||||
}) ?? toDisposable(() => {}));
|
||||
}
|
||||
|
||||
private _initRefresh(): void {
|
||||
const commandExecuted$ = fromCallback(this._commandService.onCommandExecuted.bind(this._commandService))
|
||||
.pipe(filter(([command]) => {
|
||||
if (command.id.startsWith('doc.') || SELECTION_ONLY_COMMANDS.has(command.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (command.type === CommandType.OPERATION && TABLE_RENDER_REFRESH_COMMANDS.has(command.id)) {
|
||||
this._closeFloatingControls();
|
||||
return true;
|
||||
}
|
||||
|
||||
return command.type === CommandType.MUTATION || command.type === CommandType.COMMAND;
|
||||
}));
|
||||
|
||||
this.disposeWithMe(merge(
|
||||
this._context.unit.activeSheet$,
|
||||
this._sheetSkeletonManagerService.currentSkeleton$,
|
||||
this._tableManager.tableAdd$,
|
||||
this._tableManager.tableDelete$,
|
||||
this._tableManager.tableNameChanged$,
|
||||
this._tableManager.tableRangeChanged$,
|
||||
this._tableManager.tableThemeChanged$,
|
||||
this._sheetTableThemeUIController.refreshTable$,
|
||||
this._workbookPermissionService.unitPermissionInitStateChange$,
|
||||
this._permissionService.permissionPointUpdate$,
|
||||
commandExecuted$
|
||||
).subscribe(() => {
|
||||
this._closeFloatingControls();
|
||||
this._refresh();
|
||||
}));
|
||||
|
||||
this.disposeWithMe(this._sheetsSelectionsService.selectionChanged$.subscribe(() => {
|
||||
this._closeFloatingControls();
|
||||
this._refresh(false);
|
||||
}));
|
||||
}
|
||||
|
||||
private _refresh(invalidateScene = true): void {
|
||||
const skeleton = this._sheetSkeletonManagerService.getCurrentSkeleton();
|
||||
const worksheet = this._context.unit.getActiveSheet();
|
||||
|
||||
if (!skeleton || !worksheet || !this._canEditWorkbook()) {
|
||||
this._shape.setItems([]);
|
||||
this._shape.refreshBounds();
|
||||
this._shape.makeDirty(true);
|
||||
if (invalidateScene) {
|
||||
this._context.scene.makeDirty();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this._syncTopTableGap(skeleton);
|
||||
const unitId = this._context.unit.getUnitId();
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
const items = this._tableManager.getTablesBySubunitId(unitId, subUnitId).map((table) => {
|
||||
const rangeTheme = this._rangeThemeModel.getRangeThemeStyle(unitId, table.getTableStyleId());
|
||||
return {
|
||||
tableId: table.getId(),
|
||||
tableName: table.getDisplayName(),
|
||||
range: table.getRange(),
|
||||
fill: rangeTheme?.getHeaderRowStyle()?.bg?.rgb ?? 'rgb(53,91,183)',
|
||||
text: rangeTheme?.getHeaderRowStyle()?.cl?.rgb ?? 'rgb(255,255,255)',
|
||||
};
|
||||
});
|
||||
|
||||
this._shape.setItems(items);
|
||||
this._shape.refreshBounds();
|
||||
this._shape.makeDirty(true);
|
||||
if (invalidateScene) {
|
||||
this._context.scene.makeDirty();
|
||||
}
|
||||
}
|
||||
|
||||
private _canEditWorkbook(): boolean {
|
||||
const unitId = this._context.unit.getUnitId();
|
||||
const workbookEditPermission = this._permissionService.getPermissionPoint(new WorkbookEditablePermission(unitId).id)?.value;
|
||||
return workbookEditPermission !== false;
|
||||
}
|
||||
|
||||
private _handlePointerMove(evt: IPointerEvent | IMouseEvent, state: EventState): void {
|
||||
const point = this._getLocalPoint(evt);
|
||||
const hit = this._shape.hitTest(point.x, point.y);
|
||||
const insertRegion = this._isInsertHit(hit) ? hit : hit ? null : this._getInsertRegionFromPoint(point.x, point.y);
|
||||
const activeHit = hit ?? insertRegion;
|
||||
|
||||
this._shape.setHoveredRegion(this._isInsertHit(hit) ? null : hit);
|
||||
this._shape.setHoveredInsertRegion(insertRegion);
|
||||
|
||||
if (activeHit) {
|
||||
state.stopPropagation();
|
||||
this._context.scene.setCursor(CURSOR_TYPE.POINTER);
|
||||
} else {
|
||||
this._context.scene.resetCursor();
|
||||
}
|
||||
}
|
||||
|
||||
private _isInsertHit(hit: ITableControlHitRegion | null): hit is ITableControlHitRegion {
|
||||
return hit?.type === 'insert-row' || hit?.type === 'insert-column';
|
||||
}
|
||||
|
||||
private _handlePointerLeave(state: EventState): void {
|
||||
state.stopPropagation();
|
||||
this._shape.setHoveredRegion(null);
|
||||
this._shape.setHoveredInsertRegion(null);
|
||||
this._context.scene.resetCursor();
|
||||
}
|
||||
|
||||
private _handlePointerDown(evt: IPointerEvent | IMouseEvent, state: EventState): void {
|
||||
if (evt.button === 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const point = this._getLocalPoint(evt);
|
||||
const hit = this._shape.hitTest(point.x, point.y) ?? this._getInsertRegionFromPoint(point.x, point.y);
|
||||
if (!hit) {
|
||||
this._closeFloatingControls();
|
||||
return;
|
||||
}
|
||||
|
||||
state.stopPropagation();
|
||||
evt.stopPropagation();
|
||||
evt.preventDefault();
|
||||
this._handleHit(hit);
|
||||
}
|
||||
|
||||
private _handleHit(hit: ITableControlHitRegion): void {
|
||||
const worksheet = this._context.unit.getActiveSheet();
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unitId = this._context.unit.getUnitId();
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
|
||||
if (hit.type === 'anchor-menu-toggle' || hit.type === 'anchor-main') {
|
||||
this._toggleTableMenu(unitId, subUnitId, hit.tableId);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hit.type === 'insert-row') {
|
||||
this._commandService.executeCommand(SheetTableInsertRowAtCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId: hit.tableId,
|
||||
index: hit.index,
|
||||
count: 1,
|
||||
});
|
||||
this._closeFloatingControls();
|
||||
return;
|
||||
}
|
||||
|
||||
if (hit.type === 'insert-column') {
|
||||
this._commandService.executeCommand(SheetTableInsertColumnAtCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId: hit.tableId,
|
||||
index: hit.index,
|
||||
count: 1,
|
||||
});
|
||||
this._closeFloatingControls();
|
||||
}
|
||||
}
|
||||
|
||||
protected _toggleTableMenu(unitId: string, subUnitId: string, tableId: string): void {
|
||||
if (this._shape.getOpenedMenuTableId() === tableId) {
|
||||
this._closeFloatingControls();
|
||||
return;
|
||||
}
|
||||
|
||||
const table = this._tableManager.getTableById(unitId, tableId);
|
||||
const anchor = this._shape.getAnchorRegion(tableId);
|
||||
if (!table || !anchor) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._closeFloatingControls();
|
||||
this._shape.setOpenedMenuTableId(tableId);
|
||||
const range = table.getRange();
|
||||
const extraProps = {
|
||||
anchorWidth: anchor.width,
|
||||
tableName: table.getDisplayName(),
|
||||
labels: {
|
||||
rename: this._localeService.t<LocaleKey>('sheets-table-ui.rename'),
|
||||
'update-range': this._localeService.t<LocaleKey>('sheets-table-ui.updateRange'),
|
||||
'set-theme': this._localeService.t<LocaleKey>('sheets-table-ui.setTheme'),
|
||||
delete: this._localeService.t<LocaleKey>('sheets-table-ui.removeTable'),
|
||||
},
|
||||
onSelect: (action: SheetTableMenuAction) => this._handleTableMenuAction(action, unitId, subUnitId, tableId),
|
||||
onClose: () => this._closeFloatingControls(),
|
||||
};
|
||||
|
||||
this._menuPopup = this._sheetCanvasPopupService.attachPopupByPosition(
|
||||
{
|
||||
left: anchor.left,
|
||||
right: anchor.left + anchor.width,
|
||||
top: anchor.top,
|
||||
bottom: anchor.top + anchor.height,
|
||||
},
|
||||
{
|
||||
componentKey: SHEET_TABLE_MENU,
|
||||
direction: 'bottom-left',
|
||||
extraProps,
|
||||
},
|
||||
{
|
||||
unitId,
|
||||
subUnitId,
|
||||
row: range.startRow,
|
||||
col: range.startColumn,
|
||||
}
|
||||
) ?? null;
|
||||
|
||||
if (!this._menuPopup) {
|
||||
this._shape.setOpenedMenuTableId(null);
|
||||
}
|
||||
}
|
||||
|
||||
protected _handleTableMenuAction(action: SheetTableMenuAction, unitId: string, subUnitId: string, tableId: string): void | Promise<void> {
|
||||
this._closeFloatingControls();
|
||||
|
||||
switch (action) {
|
||||
case 'rename':
|
||||
this._openRenameDialog(unitId, tableId);
|
||||
break;
|
||||
case 'update-range':
|
||||
return this._openRangeSelector(unitId, subUnitId, tableId);
|
||||
case 'set-theme':
|
||||
this._openThemePanel(unitId, subUnitId, tableId);
|
||||
break;
|
||||
case 'delete':
|
||||
this._commandService.executeCommand(DeleteSheetTableCommand.id, {
|
||||
tableId,
|
||||
subUnitId,
|
||||
unitId,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _openRenameDialog(unitId: string, tableId: string): void {
|
||||
this._dialogService.open({
|
||||
id: SHEET_TABLE_RENAME_DIALOG_ID,
|
||||
title: { title: this._localeService.t<LocaleKey>('sheets-table-ui.rename') },
|
||||
draggable: true,
|
||||
mask: true,
|
||||
children: {
|
||||
label: {
|
||||
name: SHEET_TABLE_RENAME_DIALOG,
|
||||
props: {
|
||||
unitId,
|
||||
tableId,
|
||||
onClose: () => this._dialogService.close(SHEET_TABLE_RENAME_DIALOG_ID),
|
||||
},
|
||||
},
|
||||
},
|
||||
width: 360,
|
||||
onClose: () => this._dialogService.close(SHEET_TABLE_RENAME_DIALOG_ID),
|
||||
});
|
||||
}
|
||||
|
||||
private async _openRangeSelector(unitId: string, subUnitId: string, tableId: string): Promise<void> {
|
||||
const table = this._tableManager.getTableById(unitId, tableId);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = await openRangeSelector(this._injector, unitId, subUnitId, table.getRange(), tableId);
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._commandService.executeCommand(SetSheetTableCommand.id, {
|
||||
tableId,
|
||||
unitId,
|
||||
updateRange: {
|
||||
newRange: selection.range,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private _openThemePanel(unitId: string, subUnitId: string, tableId: string): void {
|
||||
const table = this._tableManager.getTableById(unitId, tableId);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._sidebarService.open({
|
||||
id: SHEET_TABLE_THEME_PANEL_ID,
|
||||
header: { title: this._localeService.t<LocaleKey>('sheets-table-ui.tableStyle') },
|
||||
children: {
|
||||
label: {
|
||||
name: SHEET_TABLE_THEME_PANEL,
|
||||
props: {
|
||||
oldConfig: table.getTableConfig(),
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
},
|
||||
},
|
||||
},
|
||||
width: 330,
|
||||
});
|
||||
}
|
||||
|
||||
private _getInsertRegionFromPoint(x: number, y: number): ITableControlHitRegion | null {
|
||||
const skeleton = this._sheetSkeletonManagerService.getCurrentSkeleton();
|
||||
const worksheet = this._context.unit.getActiveSheet();
|
||||
if (!skeleton || !worksheet) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const unitId = this._context.unit.getUnitId();
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
const tables = this._tableManager.getTablesBySubunitId(unitId, subUnitId);
|
||||
|
||||
for (const table of tables) {
|
||||
const range = table.getRange();
|
||||
const tableBounds = this._getRangeBounds(skeleton, range);
|
||||
if (x < tableBounds.left || x > tableBounds.right || y < tableBounds.top || y > tableBounds.bottom) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const headerBounds = this._getRangeBounds(skeleton, {
|
||||
...range,
|
||||
endRow: range.startRow,
|
||||
});
|
||||
|
||||
if (y > headerBounds.bottom) {
|
||||
for (let row = range.startRow + 1; row <= range.endRow; row++) {
|
||||
const cell = skeleton.getNoMergeCellWithCoordByIndex(row, range.startColumn);
|
||||
if (y >= cell.startY && y <= cell.endY) {
|
||||
return {
|
||||
type: 'insert-row',
|
||||
tableId: table.getId(),
|
||||
index: row + 1,
|
||||
left: tableBounds.left - TABLE_CONTROL_INSERT_BUTTON_SIZE / 2,
|
||||
top: cell.endY - TABLE_CONTROL_INSERT_BUTTON_SIZE / 2,
|
||||
width: TABLE_CONTROL_INSERT_BUTTON_SIZE,
|
||||
height: TABLE_CONTROL_INSERT_BUTTON_SIZE,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private _getRangeBounds(skeleton: SpreadsheetSkeleton, range: IRange): { left: number; top: number; right: number; bottom: number } {
|
||||
const startCell = skeleton.getNoMergeCellWithCoordByIndex(range.startRow, range.startColumn);
|
||||
const endCell = skeleton.getNoMergeCellWithCoordByIndex(range.endRow, range.endColumn);
|
||||
return {
|
||||
left: startCell.startX,
|
||||
top: startCell.startY,
|
||||
right: endCell.endX,
|
||||
bottom: endCell.endY,
|
||||
};
|
||||
}
|
||||
|
||||
private _syncTopTableGap(skeleton: SpreadsheetSkeleton): void {
|
||||
const worksheet = this._context.unit.getActiveSheet();
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unitId = this._context.unit.getUnitId();
|
||||
const subUnitId = worksheet.getSheetId();
|
||||
const hasTopTable = this._tableManager
|
||||
.getTablesBySubunitId(unitId, subUnitId)
|
||||
.some((table) => table.getRange().startRow === 0);
|
||||
const current = skeleton.gapConfig;
|
||||
const rowGaps = { ...current.rowGaps };
|
||||
const previousTopGap = rowGaps[TABLE_CONTROL_GAP_ROW] ? { ...rowGaps[TABLE_CONTROL_GAP_ROW] } : null;
|
||||
let shouldSync = false;
|
||||
|
||||
if (hasTopTable) {
|
||||
if (!this._topGapBaseBySkeleton.has(skeleton)) {
|
||||
this._topGapBaseBySkeleton.set(
|
||||
skeleton,
|
||||
rowGaps[TABLE_CONTROL_GAP_ROW] ? { ...rowGaps[TABLE_CONTROL_GAP_ROW] } : null
|
||||
);
|
||||
}
|
||||
|
||||
const baseGap = this._topGapBaseBySkeleton.get(skeleton);
|
||||
rowGaps[TABLE_CONTROL_GAP_ROW] = {
|
||||
...(baseGap ?? rowGaps[TABLE_CONTROL_GAP_ROW]),
|
||||
size: (baseGap?.size ?? 0) + TABLE_CONTROL_TOP_GAP_SIZE,
|
||||
};
|
||||
shouldSync = true;
|
||||
} else if (this._topGapBaseBySkeleton.has(skeleton)) {
|
||||
const baseGap = this._topGapBaseBySkeleton.get(skeleton);
|
||||
if (baseGap) {
|
||||
rowGaps[TABLE_CONTROL_GAP_ROW] = { ...baseGap };
|
||||
} else {
|
||||
delete rowGaps[TABLE_CONTROL_GAP_ROW];
|
||||
}
|
||||
this._topGapBaseBySkeleton.delete(skeleton);
|
||||
shouldSync = true;
|
||||
}
|
||||
|
||||
if (!shouldSync) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTopGap = rowGaps[TABLE_CONTROL_GAP_ROW] ? { ...rowGaps[TABLE_CONTROL_GAP_ROW] } : null;
|
||||
if (isSameTopGap(previousTopGap, nextTopGap)) {
|
||||
return;
|
||||
}
|
||||
|
||||
skeleton.setGapConfig({ ...current, rowGaps });
|
||||
this._refreshSelections();
|
||||
}
|
||||
|
||||
private _refreshSelections(): void {
|
||||
this._selectionRenderService.resetSelectionsByModelData(this._sheetsSelectionsService.getCurrentSelections());
|
||||
}
|
||||
|
||||
protected _closeFloatingControls(): void {
|
||||
const menuPopup = this._menuPopup;
|
||||
this._menuPopup = null;
|
||||
menuPopup?.dispose();
|
||||
this._shape.setOpenedMenuTableId(null);
|
||||
this._shape.setHoveredInsertRegion(null);
|
||||
this._shape.setHoveredRegion(null);
|
||||
}
|
||||
|
||||
private _getLocalPoint(evt: IPointerEvent | IMouseEvent): { x: number; y: number } {
|
||||
const skeleton = this._sheetSkeletonManagerService.getCurrentSkeleton();
|
||||
if (skeleton) {
|
||||
return getTransformCoord(evt.offsetX, evt.offsetY, this._context.scene, skeleton);
|
||||
}
|
||||
|
||||
return {
|
||||
x: evt.offsetX,
|
||||
y: evt.offsetY,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* 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, IDisposable, IRange, Workbook } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule, SpreadsheetSkeleton } from '@univerjs/engine-render';
|
||||
import type { ITableRangeWithState } from '@univerjs/sheets-table';
|
||||
import type { ISheetsTableFilterButtonShapeProps } from '../views/widgets/table-filter-button.shape';
|
||||
import { ICommandService, Inject, Injector, InterceptorEffectEnum, RxDisposable, VerticalAlign } from '@univerjs/core';
|
||||
import { INTERCEPTOR_POINT, SetVerticalTextAlignCommand, SheetInterceptorService, SheetRangeThemeModel } from '@univerjs/sheets';
|
||||
import { TableManager } from '@univerjs/sheets-table';
|
||||
import { getCoordByCell, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { map, merge, of, startWith, switchMap, takeUntil } from 'rxjs';
|
||||
import { FILTER_ICON_PADDING, FILTER_ICON_SIZE, SheetsTableFilterButtonShape } from '../views/widgets/table-filter-button.shape';
|
||||
|
||||
const SHEETS_FILTER_BUTTON_Z_INDEX = 5000;
|
||||
|
||||
interface ISheetsTableFilterRenderParams {
|
||||
unitId: string;
|
||||
worksheetId: string;
|
||||
tableFilterRanges: ITableRangeWithState[];
|
||||
skeleton: SpreadsheetSkeleton;
|
||||
}
|
||||
|
||||
const computeIconTop = (
|
||||
startY: number,
|
||||
endY: number,
|
||||
cellHeight: number,
|
||||
verticalAlign?: VerticalAlign
|
||||
) => {
|
||||
switch (verticalAlign) {
|
||||
case VerticalAlign.TOP:
|
||||
return startY + FILTER_ICON_PADDING;
|
||||
case VerticalAlign.MIDDLE:
|
||||
return startY + Math.max(0, (cellHeight - FILTER_ICON_SIZE) / 2);
|
||||
case VerticalAlign.BOTTOM:
|
||||
default:
|
||||
return endY - FILTER_ICON_SIZE - FILTER_ICON_PADDING;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Show selected range in filter.
|
||||
*/
|
||||
export class SheetsTableFilterButtonRenderController extends RxDisposable implements IRenderModule {
|
||||
private _buttonRenderDisposable: IDisposable | null = null;
|
||||
private _tableFilterButtonShapes: SheetsTableFilterButtonShape[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@Inject(Injector) private readonly _injector: Injector,
|
||||
@Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService,
|
||||
@Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(TableManager) private _tableManager: TableManager,
|
||||
@Inject(SheetRangeThemeModel) private readonly _rangeThemeModel: SheetRangeThemeModel,
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
this._initRenderer();
|
||||
this._initCommandExecuted();
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
this._disposeRendering();
|
||||
}
|
||||
|
||||
private _initRenderer(): void {
|
||||
// Subscribe to skeleton change and filter model change.
|
||||
const tableManager = this._tableManager;
|
||||
this._sheetSkeletonManagerService.currentSkeleton$.pipe(
|
||||
switchMap((skeletonParams) => {
|
||||
if (!skeletonParams) return of(null);
|
||||
|
||||
const { unit: workbook, unitId } = this._context;
|
||||
const worksheetId = workbook.getActiveSheet()?.getSheetId() || '';
|
||||
const getParams = (): ISheetsTableFilterRenderParams => ({
|
||||
unitId,
|
||||
worksheetId,
|
||||
tableFilterRanges: this._tableManager.getSheetFilterRangeWithState(workbook.getUnitId(), worksheetId),
|
||||
skeleton: skeletonParams.skeleton,
|
||||
});
|
||||
|
||||
return merge(
|
||||
tableManager.tableAdd$,
|
||||
tableManager.tableNameChanged$,
|
||||
tableManager.tableRangeChanged$,
|
||||
tableManager.tableThemeChanged$,
|
||||
tableManager.tableDelete$,
|
||||
tableManager.tableFilterChanged$
|
||||
).pipe(
|
||||
map(() => getParams()),
|
||||
startWith(getParams())
|
||||
);
|
||||
}),
|
||||
takeUntil(this.dispose$)
|
||||
).subscribe((renderParams) => {
|
||||
this._disposeRendering();
|
||||
if (!renderParams || !renderParams.tableFilterRanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderButtons(renderParams as Required<ISheetsTableFilterRenderParams>);
|
||||
});
|
||||
}
|
||||
|
||||
private _initCommandExecuted() {
|
||||
this.disposeWithMe(
|
||||
this._commandService.onCommandExecuted((command: ICommandInfo) => {
|
||||
if (command.id !== SetVerticalTextAlignCommand.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { unit: workbook, unitId } = this._context;
|
||||
const worksheetId = workbook.getActiveSheet()?.getSheetId() || '';
|
||||
const skeleton = this._sheetSkeletonManagerService.getCurrentSkeleton();
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
const renderParams: ISheetsTableFilterRenderParams = {
|
||||
unitId,
|
||||
worksheetId,
|
||||
tableFilterRanges: this._tableManager.getSheetFilterRangeWithState(workbook.getUnitId(), worksheetId),
|
||||
skeleton,
|
||||
};
|
||||
|
||||
this._disposeRendering();
|
||||
if (!renderParams || !renderParams.tableFilterRanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._renderButtons(renderParams as Required<ISheetsTableFilterRenderParams>);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private _renderButtons(params: Required<ISheetsTableFilterRenderParams>): void {
|
||||
const { tableFilterRanges, unitId, skeleton, worksheetId } = params;
|
||||
const { unit: workbook, scene } = this._context;
|
||||
const worksheet = workbook.getSheetBySheetId(worksheetId);
|
||||
if (!worksheet) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const { range, states, tableId } of tableFilterRanges) {
|
||||
const { startRow, startColumn, endColumn } = range;
|
||||
const table = this._tableManager.getTableById(unitId, tableId);
|
||||
const headerStyle = table ? this._rangeThemeModel.getRangeThemeStyle(unitId, table.getTableStyleId())?.getHeaderRowStyle() : null;
|
||||
const iconColor = headerStyle?.cl?.rgb ?? '#fff';
|
||||
const hoverIconColor = headerStyle?.bg?.rgb ?? '#202124';
|
||||
this._interceptCellContent(unitId, worksheetId, range);
|
||||
for (let col = startColumn; col <= endColumn; col++) {
|
||||
const key = `sheets-table-filter-button-${startRow}-${col}`;
|
||||
const startPosition = getCoordByCell(startRow, col, scene, skeleton);
|
||||
const cellStyle = worksheet.getCellStyle(startRow, col);
|
||||
const verticalAlign = cellStyle?.vt || VerticalAlign.BOTTOM;
|
||||
const { startX, startY, endX, endY } = startPosition;
|
||||
const cellWidth = endX - startX;
|
||||
const cellHeight = endY - startY;
|
||||
if (cellHeight <= FILTER_ICON_PADDING || cellWidth <= FILTER_ICON_PADDING) {
|
||||
continue;
|
||||
}
|
||||
const state = states[col - startColumn];
|
||||
const iconStartX = endX - FILTER_ICON_SIZE - FILTER_ICON_PADDING;
|
||||
const iconStartY = computeIconTop(startY, endY, cellHeight, verticalAlign);
|
||||
const props: ISheetsTableFilterButtonShapeProps = {
|
||||
left: iconStartX,
|
||||
top: iconStartY,
|
||||
height: FILTER_ICON_SIZE,
|
||||
width: FILTER_ICON_SIZE,
|
||||
zIndex: SHEETS_FILTER_BUTTON_Z_INDEX,
|
||||
iconColor,
|
||||
hoverBackground: iconColor,
|
||||
hoverIconColor,
|
||||
cellHeight,
|
||||
cellWidth,
|
||||
filterParams: { unitId, subUnitId: worksheetId, row: startRow, col, buttonState: state, tableId },
|
||||
};
|
||||
const buttonShape = this._injector.createInstance(SheetsTableFilterButtonShape, key, props);
|
||||
this._tableFilterButtonShapes.push(buttonShape);
|
||||
}
|
||||
}
|
||||
|
||||
scene.addObjects(this._tableFilterButtonShapes);
|
||||
scene.makeDirty();
|
||||
}
|
||||
|
||||
private _interceptCellContent(workbookId: string, worksheetId: string, range: IRange): void {
|
||||
const { startRow, startColumn, endColumn } = range;
|
||||
this._buttonRenderDisposable = this._sheetInterceptorService.intercept(INTERCEPTOR_POINT.CELL_CONTENT, {
|
||||
effect: InterceptorEffectEnum.Style,
|
||||
handler: (cell, pos, next) => {
|
||||
const { row, col, unitId, subUnitId } = pos;
|
||||
if (
|
||||
unitId !== workbookId ||
|
||||
subUnitId !== worksheetId ||
|
||||
row !== startRow ||
|
||||
col < startColumn ||
|
||||
col > endColumn
|
||||
) {
|
||||
return next(cell);
|
||||
}
|
||||
|
||||
if (!cell || cell === pos.rawData) {
|
||||
cell = { ...pos.rawData };
|
||||
}
|
||||
|
||||
cell.fontRenderExtension = {
|
||||
...cell?.fontRenderExtension,
|
||||
rightOffset: FILTER_ICON_SIZE + FILTER_ICON_PADDING + 2,
|
||||
};
|
||||
|
||||
return next(cell);
|
||||
},
|
||||
priority: 10,
|
||||
});
|
||||
}
|
||||
|
||||
private _disposeRendering(): void {
|
||||
this._tableFilterButtonShapes.forEach((s) => s.dispose());
|
||||
this._buttonRenderDisposable?.dispose();
|
||||
|
||||
this._buttonRenderDisposable = null;
|
||||
this._tableFilterButtonShapes = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Workbook } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule } from '@univerjs/engine-render';
|
||||
import { Inject, Injector, RxDisposable } from '@univerjs/core';
|
||||
import { TableManager } from '@univerjs/sheets-table';
|
||||
import { SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { merge } from 'rxjs';
|
||||
import { SheetTableThemeUIController } from './sheet-table-theme-ui.controller';
|
||||
|
||||
/**
|
||||
* Show selected range in filter.
|
||||
*/
|
||||
export class SheetsTableRenderController extends RxDisposable implements IRenderModule {
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@Inject(Injector) private readonly _injector: Injector,
|
||||
@Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService,
|
||||
@Inject(TableManager) private _tableManager: TableManager,
|
||||
@Inject(SheetTableThemeUIController) private readonly _sheetTableThemeUIController: SheetTableThemeUIController
|
||||
) {
|
||||
super();
|
||||
this._initListener();
|
||||
}
|
||||
|
||||
private _dirtySkeleton() {
|
||||
this._context.mainComponent?.makeDirty();
|
||||
const currentParam = this._sheetSkeletonManagerService.getCurrentParam();
|
||||
if (currentParam) {
|
||||
const param = { ...currentParam, dirty: true };
|
||||
this._sheetSkeletonManagerService.reCalculate(param);
|
||||
}
|
||||
}
|
||||
|
||||
private _initListener(): void {
|
||||
const tableManager = this._tableManager;
|
||||
const dirtySkeleton = this._dirtySkeleton.bind(this);
|
||||
this.disposeWithMe(
|
||||
merge(
|
||||
tableManager.tableAdd$,
|
||||
tableManager.tableDelete$,
|
||||
tableManager.tableNameChanged$,
|
||||
tableManager.tableRangeChanged$,
|
||||
tableManager.tableThemeChanged$,
|
||||
tableManager.tableFilterChanged$,
|
||||
tableManager.tableInitStatus$,
|
||||
this._sheetTableThemeUIController.refreshTable$
|
||||
).subscribe(
|
||||
dirtySkeleton
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* 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 { Disposable, Inject, IUniverInstanceService, Rectangle } from '@univerjs/core';
|
||||
import { getPrimaryForRange, getSheetCommandTarget, SetSelectionsOperation, SheetInterceptorService } from '@univerjs/sheets';
|
||||
import { TableManager } from '@univerjs/sheets-table';
|
||||
import { SelectAllCommand } from '@univerjs/sheets-ui';
|
||||
|
||||
export class SheetTableSelectionController extends Disposable {
|
||||
constructor(
|
||||
@Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService,
|
||||
@Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@Inject(TableManager) private readonly _tableManager: TableManager
|
||||
) {
|
||||
super();
|
||||
this._initSelectionChange();
|
||||
}
|
||||
|
||||
private _initSelectionChange() {
|
||||
this.disposeWithMe(
|
||||
this._sheetInterceptorService.interceptCommand({
|
||||
getMutations: (command) => {
|
||||
if (command.id === SelectAllCommand.id) {
|
||||
const target = getSheetCommandTarget(this._univerInstanceService);
|
||||
if (!target) {
|
||||
return { redos: [], undos: [] };
|
||||
}
|
||||
const params = command.params as { range: IRange };
|
||||
const { range } = params;
|
||||
const { unitId, subUnitId, worksheet } = target;
|
||||
const subTables = this._tableManager.getTablesBySubunitId(unitId, subUnitId);
|
||||
const overlapTable = subTables.find((table) => {
|
||||
const tableRange = table.getRange();
|
||||
return Rectangle.contains(tableRange, range);
|
||||
});
|
||||
|
||||
if (overlapTable) {
|
||||
const tableRange = overlapTable.getRange();
|
||||
const tableRangeWithoutHeader = {
|
||||
...tableRange,
|
||||
startRow: tableRange.startRow + 1,
|
||||
};
|
||||
|
||||
if (Rectangle.equals(tableRange, range)) {
|
||||
return { undos: [], redos: [] };
|
||||
} else if (Rectangle.equals(tableRangeWithoutHeader, range)) {
|
||||
return {
|
||||
undos: [],
|
||||
redos: [
|
||||
{
|
||||
id: SetSelectionsOperation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
selections: [
|
||||
{
|
||||
range: tableRange,
|
||||
primary: getPrimaryForRange(tableRange, worksheet),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
undos: [],
|
||||
redos: [
|
||||
{
|
||||
id: SetSelectionsOperation.id,
|
||||
params: {
|
||||
unitId,
|
||||
subUnitId,
|
||||
selections: [
|
||||
{
|
||||
range: tableRangeWithoutHeader,
|
||||
primary: getPrimaryForRange(tableRangeWithoutHeader, worksheet),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return { redos: [], undos: [] };
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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 { ISetRangeThemeMutationParams } from '@univerjs/sheets';
|
||||
import { Disposable, ICommandService, Inject } from '@univerjs/core';
|
||||
import { SetRangeThemeMutation } from '@univerjs/sheets';
|
||||
import { SHEET_TABLE_CUSTOM_THEME_PREFIX } from '@univerjs/sheets-table';
|
||||
import { Subject } from 'rxjs';
|
||||
|
||||
export class SheetTableThemeUIController extends Disposable {
|
||||
private _refreshTable = new Subject<number>();
|
||||
public refreshTable$ = this._refreshTable.asObservable();
|
||||
|
||||
constructor(
|
||||
@Inject(ICommandService) private _commandService: ICommandService
|
||||
) {
|
||||
super();
|
||||
this._initListener();
|
||||
}
|
||||
|
||||
private _initListener() {
|
||||
this.disposeWithMe(
|
||||
this._commandService.onCommandExecuted((command) => {
|
||||
if (command.id === SetRangeThemeMutation.id) {
|
||||
const params = command.params as ISetRangeThemeMutationParams;
|
||||
const { styleName } = params;
|
||||
if (styleName.startsWith(SHEET_TABLE_CUSTOM_THEME_PREFIX)) {
|
||||
this._refreshTable.next(Math.random());
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
2
packages/sheets-table-ui/src/global.css
Normal file
2
packages/sheets-table-ui/src/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
22
packages/sheets-table-ui/src/index.ts
Normal file
22
packages/sheets-table-ui/src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './global.css';
|
||||
|
||||
export type { IUniverSheetsTableUIConfig } from './config/config';
|
||||
export { menuSchema as SheetsTableUIMenuSchema } from './menu/schema';
|
||||
export { UniverSheetsTableMobileUIPlugin } from './mobile-plugin';
|
||||
export { UniverSheetsTableUIPlugin } from './plugin';
|
||||
162
packages/sheets-table-ui/src/locale/ar-SA.ts
Normal file
162
packages/sheets-table-ui/src/locale/ar-SA.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'جدول',
|
||||
selectRange: 'حدد نطاق الجدول',
|
||||
rename: 'إعادة تسمية الجدول',
|
||||
renamePlaceholder: 'أدخل اسم الجدول',
|
||||
updateRange: 'تحديث نطاق الجدول',
|
||||
tableRangeWithMergeError: 'لا يمكن أن يتداخل نطاق الجدول مع الخلايا المدمجة',
|
||||
tableRangeWithOtherTableError: 'لا يمكن أن يتداخل نطاق الجدول مع جداول أخرى',
|
||||
tableRangeSingleRowError: 'لا يمكن أن يكون نطاق الجدول صفًا واحدًا',
|
||||
updateError: 'لا يمكن تعيين نطاق الجدول إلى منطقة لا تتداخل مع النطاق الأصلي ولا تقع في نفس الصف',
|
||||
tableStyle: 'نمط الجدول',
|
||||
defaultStyle: 'النمط الافتراضي',
|
||||
customStyle: 'نمط مخصص',
|
||||
customTooMore: 'عدد السمات المخصصة يتجاوز الحد الأقصى، يُرجى حذف بعض السمات غير الضرورية وإضافتها مرة أخرى',
|
||||
setTheme: 'تعيين سمة الجدول',
|
||||
removeTable: 'إزالة الجدول',
|
||||
cancel: 'إلغاء',
|
||||
confirm: 'تأكيد',
|
||||
header: 'رأس',
|
||||
footer: 'تذييل',
|
||||
firstLine: 'السطر الأول',
|
||||
secondLine: 'السطر الثاني',
|
||||
tableNameError: 'لا يمكن أن يحتوي اسم الجدول على مسافات، ولا يمكن أن يبدأ برقم، ولا يمكن أن يكون مطابقًا لاسم جدول موجود',
|
||||
columnMenu: {
|
||||
'insert-left': 'إدراج عمود جدول إلى اليسار',
|
||||
'insert-right': 'إدراج عمود جدول إلى اليمين',
|
||||
delete: 'حذف عمود الجدول',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'تصاعدي',
|
||||
'sort-desc': 'تنازلي',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'إدراج جدول',
|
||||
row: 'إدراج صف جدول',
|
||||
col: 'إدراج عمود جدول',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'إزالة الجدول',
|
||||
row: 'إزالة صف الجدول',
|
||||
col: 'إزالة عمود الجدول',
|
||||
},
|
||||
condition: {
|
||||
string: 'نص',
|
||||
number: 'رقم',
|
||||
date: 'تاريخ',
|
||||
|
||||
empty: '(فارغ)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'يساوي',
|
||||
notEqual: 'لا يساوي',
|
||||
contains: 'يحتوي على',
|
||||
notContains: 'لا يحتوي على',
|
||||
startsWith: 'يبدأ بـ',
|
||||
endsWith: 'ينتهي بـ',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'يساوي',
|
||||
notEqual: 'لا يساوي',
|
||||
greaterThan: 'أكبر من',
|
||||
greaterThanOrEqual: 'أكبر من أو يساوي',
|
||||
lessThan: 'أقل من',
|
||||
lessThanOrEqual: 'أقل من أو يساوي',
|
||||
between: 'بين',
|
||||
notBetween: 'ليست بين',
|
||||
above: 'أعلى',
|
||||
below: 'أدنى',
|
||||
topN: 'أعلى {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'يساوي',
|
||||
notEqual: 'لا يساوي',
|
||||
after: 'بعد',
|
||||
afterOrEqual: 'بعد أو يساوي',
|
||||
before: 'قبل',
|
||||
beforeOrEqual: 'قبل أو يساوي',
|
||||
between: 'بين',
|
||||
notBetween: 'ليست بين',
|
||||
today: 'اليوم',
|
||||
yesterday: 'أمس',
|
||||
tomorrow: 'غدًا',
|
||||
thisWeek: 'هذا الأسبوع',
|
||||
lastWeek: 'الأسبوع الماضي',
|
||||
nextWeek: 'الأسبوع القادم',
|
||||
thisMonth: 'هذا الشهر',
|
||||
lastMonth: 'الشهر الماضي',
|
||||
nextMonth: 'الشهر القادم',
|
||||
thisQuarter: 'هذا الربع',
|
||||
lastQuarter: 'الربع الماضي',
|
||||
nextQuarter: 'الربع القادم',
|
||||
thisYear: 'هذه السنة',
|
||||
nextYear: 'السنة القادمة',
|
||||
lastYear: 'السنة الماضية',
|
||||
quarter: 'حسب الربع',
|
||||
month: 'حسب الشهر',
|
||||
q1: 'الربع الأول',
|
||||
q2: 'الربع الثاني',
|
||||
q3: 'الربع الثالث',
|
||||
q4: 'الربع الرابع',
|
||||
m1: 'يناير',
|
||||
m2: 'فبراير',
|
||||
m3: 'مارس',
|
||||
m4: 'أبريل',
|
||||
m5: 'مايو',
|
||||
m6: 'يونيو',
|
||||
m7: 'يوليو',
|
||||
m8: 'أغسطس',
|
||||
m9: 'سبتمبر',
|
||||
m10: 'أكتوبر',
|
||||
m11: 'نوفمبر',
|
||||
m12: 'ديسمبر',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'تصفية',
|
||||
'by-values': 'حسب القيم',
|
||||
'by-colors': 'حسب الألوان',
|
||||
'by-conditions': 'حسب الشروط',
|
||||
back: 'رجوع',
|
||||
resize: 'تغيير الحجم',
|
||||
'filter-by-cell-fill-color': 'لون تعبئة الخلية',
|
||||
'filter-by-cell-text-color': 'لون نص الخلية',
|
||||
'filter-by-color-none': 'يحتوي هذا العمود على لون واحد فقط',
|
||||
'clear-filter': 'مسح التصفية',
|
||||
cancel: 'إلغاء',
|
||||
confirm: 'تأكيد',
|
||||
'search-placeholder': 'استخدم المسافة لفصل الكلمات المفتاحية',
|
||||
'input-values-placeholder': 'إدخال القيم',
|
||||
'select-all': 'تحديد الكل',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/ca-ES.ts
Normal file
162
packages/sheets-table-ui/src/locale/ca-ES.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Taula',
|
||||
selectRange: 'Selecciona el rang de la taula',
|
||||
rename: 'Reanomena la taula',
|
||||
renamePlaceholder: 'Introdueix el nom de la taula',
|
||||
updateRange: 'Actualitza el rang de la taula',
|
||||
tableRangeWithMergeError: 'El rang de la taula no es pot superposar amb cel·les combinades',
|
||||
tableRangeWithOtherTableError: 'El rang de la taula no es pot superposar amb altres taules',
|
||||
tableRangeSingleRowError: 'El rang de la taula no pot ser una única fila',
|
||||
updateError: 'No es pot establir el rang de la taula en una àrea que no se superposi amb l\'original i no estigui a la mateixa fila',
|
||||
tableStyle: 'Estil de taula',
|
||||
defaultStyle: 'Estil per defecte',
|
||||
customStyle: 'Estil personalitzat',
|
||||
customTooMore: 'El nombre de temes personalitzats supera el límit màxim; si us plau, suprimiu alguns temes innecessaris i torneu-los a afegir',
|
||||
setTheme: 'Estableix el tema de la taula',
|
||||
removeTable: 'Elimina la taula',
|
||||
cancel: 'Cancel·lar',
|
||||
confirm: 'Confirmar',
|
||||
header: 'Capçalera',
|
||||
footer: 'Peu de pàgina',
|
||||
firstLine: 'Primera línia',
|
||||
secondLine: 'Segona línia',
|
||||
tableNameError: 'El nom de la taula no pot contenir espais, no pot començar amb un número i no pot ser idèntic a un nom de taula existent',
|
||||
columnMenu: {
|
||||
'insert-left': 'Insereix una columna de taula a l’esquerra',
|
||||
'insert-right': 'Insereix una columna de taula a la dreta',
|
||||
delete: 'Suprimeix la columna de la taula',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Ascendent',
|
||||
'sort-desc': 'Descendent',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Insereix una taula',
|
||||
row: 'Insereix una fila de taula',
|
||||
col: 'Insereix una columna de taula',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Elimina la taula',
|
||||
row: 'Elimina una fila de taula',
|
||||
col: 'Elimina una columna de taula',
|
||||
},
|
||||
condition: {
|
||||
string: 'Text',
|
||||
number: 'Nombre',
|
||||
date: 'Data',
|
||||
|
||||
empty: '(Buit)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'No és igual a',
|
||||
contains: 'Conté',
|
||||
notContains: 'No conté',
|
||||
startsWith: 'Comença per',
|
||||
endsWith: 'Acaba en',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'No és igual a',
|
||||
greaterThan: 'Més gran que',
|
||||
greaterThanOrEqual: 'Més gran o igual que',
|
||||
lessThan: 'Més petit que',
|
||||
lessThanOrEqual: 'Més petit o igual que',
|
||||
between: 'Entre',
|
||||
notBetween: 'No està entre',
|
||||
above: 'Per sobre',
|
||||
below: 'Per sota',
|
||||
topN: '{0} superiors',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'No és igual a',
|
||||
after: 'Després de',
|
||||
afterOrEqual: 'Després o igual a',
|
||||
before: 'Abans de',
|
||||
beforeOrEqual: 'Abans o igual a',
|
||||
between: 'Entre',
|
||||
notBetween: 'No està entre',
|
||||
today: 'Avui',
|
||||
yesterday: 'Ahir',
|
||||
tomorrow: 'Demà',
|
||||
thisWeek: 'Aquesta setmana',
|
||||
lastWeek: 'La setmana passada',
|
||||
nextWeek: 'La setmana que ve',
|
||||
thisMonth: 'Aquest mes',
|
||||
lastMonth: 'El mes passat',
|
||||
nextMonth: 'El mes que ve',
|
||||
thisQuarter: 'Aquest trimestre',
|
||||
lastQuarter: 'L\'últim trimestre',
|
||||
nextQuarter: 'El proper trimestre',
|
||||
thisYear: 'Aquest any',
|
||||
nextYear: 'L\'any que ve',
|
||||
lastYear: 'L\'any passat',
|
||||
quarter: 'Per trimestre',
|
||||
month: 'Per mes',
|
||||
q1: 'Primer trimestre',
|
||||
q2: 'Segon trimestre',
|
||||
q3: 'Tercer trimestre',
|
||||
q4: 'Quart trimestre',
|
||||
m1: 'Gener',
|
||||
m2: 'Febrer',
|
||||
m3: 'Març',
|
||||
m4: 'Abril',
|
||||
m5: 'Maig',
|
||||
m6: 'Juny',
|
||||
m7: 'Juliol',
|
||||
m8: 'Agost',
|
||||
m9: 'Setembre',
|
||||
m10: 'Octubre',
|
||||
m11: 'Novembre',
|
||||
m12: 'Desembre',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filtre',
|
||||
'by-values': 'Per valors',
|
||||
'by-colors': 'Per colors',
|
||||
'by-conditions': 'Per condicions',
|
||||
back: 'Enrere',
|
||||
resize: 'Canvia la mida',
|
||||
'filter-by-cell-fill-color': 'Color de farciment de la cel·la',
|
||||
'filter-by-cell-text-color': 'Color del text de la cel·la',
|
||||
'filter-by-color-none': 'Aquesta columna només conté un color',
|
||||
'clear-filter': 'Neteja el filtre',
|
||||
cancel: 'Cancel·la',
|
||||
confirm: 'Confirma',
|
||||
'search-placeholder': 'Utilitza espai per separar paraules clau',
|
||||
'input-values-placeholder': 'Introdueix valors',
|
||||
'select-all': 'Selecciona-ho tot',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/de-DE.ts
Normal file
162
packages/sheets-table-ui/src/locale/de-DE.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabelle',
|
||||
selectRange: 'Tabellenbereich auswählen',
|
||||
rename: 'Tabelle umbenennen',
|
||||
renamePlaceholder: 'Tabellennamen eingeben',
|
||||
updateRange: 'Tabellenbereich aktualisieren',
|
||||
tableRangeWithMergeError: 'Tabellenbereich darf sich nicht mit verbundenen Zellen überschneiden',
|
||||
tableRangeWithOtherTableError: 'Tabellenbereich darf sich nicht mit anderen Tabellen überschneiden',
|
||||
tableRangeSingleRowError: 'Tabellenbereich darf nicht nur eine Zeile sein',
|
||||
updateError: 'Tabellenbereich kann nicht auf einen Bereich festgelegt werden, der sich nicht mit dem Original überschneidet und nicht in derselben Zeile liegt',
|
||||
tableStyle: 'Tabellenstil',
|
||||
defaultStyle: 'Standardstil',
|
||||
customStyle: 'Benutzerdefinierter Stil',
|
||||
customTooMore: 'Die Anzahl der benutzerdefinierten Designs überschreitet das Maximum, bitte löschen Sie einige unnötige Designs und fügen Sie sie erneut hinzu',
|
||||
setTheme: 'Tabellenthema festlegen',
|
||||
removeTable: 'Tabelle entfernen',
|
||||
cancel: 'Abbrechen',
|
||||
confirm: 'Bestätigen',
|
||||
header: 'Kopfzeile',
|
||||
footer: 'Fußzeile',
|
||||
firstLine: 'Erste Zeile',
|
||||
secondLine: 'Zweite Zeile',
|
||||
tableNameError: 'Tabellenname darf keine Leerzeichen enthalten, darf nicht mit einer Zahl beginnen und darf nicht mit einem bestehenden Tabellennamen identisch sein',
|
||||
columnMenu: {
|
||||
'insert-left': '1 Tabellenspalte links einfügen',
|
||||
'insert-right': '1 Tabellenspalte rechts einfügen',
|
||||
delete: 'Tabellenspalte löschen',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Aufsteigend',
|
||||
'sort-desc': 'Absteigend',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Tabelle einfügen',
|
||||
row: 'Tabellenzeile einfügen',
|
||||
col: 'Tabellenspalte einfügen',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Tabelle entfernen',
|
||||
row: 'Tabellenzeile entfernen',
|
||||
col: 'Tabellenspalte entfernen',
|
||||
},
|
||||
condition: {
|
||||
string: 'Text',
|
||||
number: 'Zahl',
|
||||
date: 'Datum',
|
||||
|
||||
empty: '(Leer)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Gleich',
|
||||
notEqual: 'Ungleich',
|
||||
contains: 'Enthält',
|
||||
notContains: 'Enthält nicht',
|
||||
startsWith: 'Beginnt mit',
|
||||
endsWith: 'Endet mit',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Gleich',
|
||||
notEqual: 'Ungleich',
|
||||
greaterThan: 'Größer als',
|
||||
greaterThanOrEqual: 'Größer oder gleich',
|
||||
lessThan: 'Kleiner als',
|
||||
lessThanOrEqual: 'Kleiner oder gleich',
|
||||
between: 'Zwischen',
|
||||
notBetween: 'Nicht zwischen',
|
||||
above: 'Über',
|
||||
below: 'Unter',
|
||||
topN: 'Oben {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Gleich',
|
||||
notEqual: 'Ungleich',
|
||||
after: 'Nach',
|
||||
afterOrEqual: 'Nach oder gleich',
|
||||
before: 'Vor',
|
||||
beforeOrEqual: 'Vor oder gleich',
|
||||
between: 'Zwischen',
|
||||
notBetween: 'Nicht zwischen',
|
||||
today: 'Heute',
|
||||
yesterday: 'Gestern',
|
||||
tomorrow: 'Morgen',
|
||||
thisWeek: 'Diese Woche',
|
||||
lastWeek: 'Letzte Woche',
|
||||
nextWeek: 'Nächste Woche',
|
||||
thisMonth: 'Dieser Monat',
|
||||
lastMonth: 'Letzter Monat',
|
||||
nextMonth: 'Nächster Monat',
|
||||
thisQuarter: 'Dieses Quartal',
|
||||
lastQuarter: 'Letztes Quartal',
|
||||
nextQuarter: 'Nächstes Quartal',
|
||||
thisYear: 'Dieses Jahr',
|
||||
nextYear: 'Nächstes Jahr',
|
||||
lastYear: 'Letztes Jahr',
|
||||
quarter: 'Nach Quartal',
|
||||
month: 'Nach Monat',
|
||||
q1: 'Erstes Quartal',
|
||||
q2: 'Zweites Quartal',
|
||||
q3: 'Drittes Quartal',
|
||||
q4: 'Viertes Quartal',
|
||||
m1: 'Januar',
|
||||
m2: 'Februar',
|
||||
m3: 'März',
|
||||
m4: 'April',
|
||||
m5: 'Mai',
|
||||
m6: 'Juni',
|
||||
m7: 'Juli',
|
||||
m8: 'August',
|
||||
m9: 'September',
|
||||
m10: 'Oktober',
|
||||
m11: 'November',
|
||||
m12: 'Dezember',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filter',
|
||||
'by-values': 'Nach Werten',
|
||||
'by-colors': 'Nach Farben',
|
||||
'by-conditions': 'Nach Bedingungen',
|
||||
back: 'Zurück',
|
||||
resize: 'Größe ändern',
|
||||
'filter-by-cell-fill-color': 'Zellfüllfarbe',
|
||||
'filter-by-cell-text-color': 'Zelltextfarbe',
|
||||
'filter-by-color-none': 'Diese Spalte enthält nur eine Farbe',
|
||||
'clear-filter': 'Filter löschen',
|
||||
cancel: 'Abbrechen',
|
||||
confirm: 'Bestätigen',
|
||||
'search-placeholder': 'Leerzeichen zum Trennen von Schlüsselwörtern verwenden',
|
||||
'input-values-placeholder': 'Werte eingeben',
|
||||
'select-all': 'Alle auswählen',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
160
packages/sheets-table-ui/src/locale/en-US.ts
Normal file
160
packages/sheets-table-ui/src/locale/en-US.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Table',
|
||||
selectRange: 'Select Table Range',
|
||||
rename: 'Rename Table',
|
||||
renamePlaceholder: 'Enter table name',
|
||||
updateRange: 'Update Table Range',
|
||||
tableRangeWithMergeError: 'Table range cannot overlap with merged cells',
|
||||
tableRangeWithOtherTableError: 'Table range cannot overlap with other tables',
|
||||
tableRangeSingleRowError: 'Table range cannot be a single row',
|
||||
updateError: 'Cannot set table range to an area that does not overlap with the original and is not in the same row',
|
||||
tableStyle: 'Table Style',
|
||||
defaultStyle: 'Default Style',
|
||||
customStyle: 'Custom Style',
|
||||
customTooMore: 'The number of custom themes exceeds the maximum limit, please delete some unnecessary themes and add them again',
|
||||
setTheme: 'Set Table Theme',
|
||||
removeTable: 'Remove Table',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
header: 'Header',
|
||||
footer: 'Footer',
|
||||
firstLine: 'First Line',
|
||||
secondLine: 'Second Line',
|
||||
tableNameError: 'Table name cannot contain spaces, cannot start with a number, and cannot be identical to an existing table name',
|
||||
columnMenu: {
|
||||
'insert-left': 'Insert 1 table column left',
|
||||
'insert-right': 'Insert 1 table column right',
|
||||
delete: 'Delete table column',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Ascending',
|
||||
'sort-desc': 'Descending',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Insert Table',
|
||||
row: 'Insert Table Row',
|
||||
col: 'Insert Table Column',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Remove Table',
|
||||
row: 'Remove Table Row',
|
||||
col: 'Remove Table Column',
|
||||
},
|
||||
condition: {
|
||||
string: 'Text',
|
||||
number: 'Number',
|
||||
date: 'Date',
|
||||
|
||||
empty: '(Empty)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Equal to',
|
||||
notEqual: 'Not equal to',
|
||||
contains: 'Contains',
|
||||
notContains: 'Does not contain',
|
||||
startsWith: 'Starts with',
|
||||
endsWith: 'Ends with',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Equal to',
|
||||
notEqual: 'Not equal to',
|
||||
greaterThan: 'Greater than',
|
||||
greaterThanOrEqual: 'Greater than or equal to',
|
||||
lessThan: 'Less than',
|
||||
lessThanOrEqual: 'Less than or equal to',
|
||||
between: 'Between',
|
||||
notBetween: 'Not between',
|
||||
above: 'Above',
|
||||
below: 'Below',
|
||||
topN: 'Top {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Equal to',
|
||||
notEqual: 'Not equal to',
|
||||
after: 'After',
|
||||
afterOrEqual: 'After or equal to',
|
||||
before: 'Before',
|
||||
beforeOrEqual: 'Before or equal to',
|
||||
between: 'Between',
|
||||
notBetween: 'Not between',
|
||||
today: 'Today',
|
||||
yesterday: 'Yesterday',
|
||||
tomorrow: 'Tomorrow',
|
||||
thisWeek: 'This week',
|
||||
lastWeek: 'Last week',
|
||||
nextWeek: 'Next week',
|
||||
thisMonth: 'This month',
|
||||
lastMonth: 'Last month',
|
||||
nextMonth: 'Next month',
|
||||
thisQuarter: 'This quarter',
|
||||
lastQuarter: 'Last quarter',
|
||||
nextQuarter: 'Next quarter',
|
||||
thisYear: 'This year',
|
||||
nextYear: 'Next year',
|
||||
lastYear: 'Last year',
|
||||
quarter: 'By quarter',
|
||||
month: 'By month',
|
||||
q1: 'First quarter',
|
||||
q2: 'Second quarter',
|
||||
q3: 'Third quarter',
|
||||
q4: 'Fourth quarter',
|
||||
m1: 'January',
|
||||
m2: 'February',
|
||||
m3: 'March',
|
||||
m4: 'April',
|
||||
m5: 'May',
|
||||
m6: 'June',
|
||||
m7: 'July',
|
||||
m8: 'August',
|
||||
m9: 'September',
|
||||
m10: 'October',
|
||||
m11: 'November',
|
||||
m12: 'December',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filter',
|
||||
'by-values': 'By Values',
|
||||
'by-colors': 'By Colors',
|
||||
'by-conditions': 'By Conditions',
|
||||
back: 'Back',
|
||||
resize: 'Resize',
|
||||
'filter-by-cell-fill-color': 'Cell fill color',
|
||||
'filter-by-cell-text-color': 'Cell text color',
|
||||
'filter-by-color-none': 'This column contains only one color',
|
||||
'clear-filter': 'Clear Filter',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
'search-placeholder': 'Use space to separate keywords',
|
||||
'input-values-placeholder': 'Input Values',
|
||||
'select-all': 'Select All',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/es-ES.ts
Normal file
162
packages/sheets-table-ui/src/locale/es-ES.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabla',
|
||||
selectRange: 'Seleccionar rango de tabla',
|
||||
rename: 'Renombrar tabla',
|
||||
renamePlaceholder: 'Introduce el nombre de la tabla',
|
||||
updateRange: 'Actualizar rango de tabla',
|
||||
tableRangeWithMergeError: 'El rango de la tabla no puede superponerse con celdas combinadas',
|
||||
tableRangeWithOtherTableError: 'El rango de la tabla no puede superponerse con otras tablas',
|
||||
tableRangeSingleRowError: 'El rango de la tabla no puede ser una sola fila',
|
||||
updateError: 'No se puede establecer el rango de la tabla en un área que no se superponga con la original y no esté en la misma fila',
|
||||
tableStyle: 'Estilo de tabla',
|
||||
defaultStyle: 'Estilo predeterminado',
|
||||
customStyle: 'Estilo personalizado',
|
||||
customTooMore: 'El número de temas personalizados excede el límite máximo, por favor, elimine algunos temas innecesarios y vuelva a añadirlos',
|
||||
setTheme: 'Establecer tema de tabla',
|
||||
removeTable: 'Eliminar tabla',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
header: 'Encabezado',
|
||||
footer: 'Pie de página',
|
||||
firstLine: 'Primera línea',
|
||||
secondLine: 'Segunda línea',
|
||||
tableNameError: 'El nombre de la tabla no puede contener espacios, no puede comenzar con un número y no puede ser idéntico a un nombre de tabla existente',
|
||||
columnMenu: {
|
||||
'insert-left': 'Insertar 1 columna a la izquierda',
|
||||
'insert-right': 'Insertar 1 columna a la derecha',
|
||||
delete: 'Eliminar columna de la tabla',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Ascendente',
|
||||
'sort-desc': 'Descendente',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Insertar tabla',
|
||||
row: 'Insertar fila de tabla',
|
||||
col: 'Insertar columna de tabla',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Eliminar tabla',
|
||||
row: 'Eliminar fila de tabla',
|
||||
col: 'Eliminar columna de tabla',
|
||||
},
|
||||
condition: {
|
||||
string: 'Texto',
|
||||
number: 'Número',
|
||||
date: 'Fecha',
|
||||
|
||||
empty: '(Vacío)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'No es igual a',
|
||||
contains: 'Contiene',
|
||||
notContains: 'No contiene',
|
||||
startsWith: 'Empieza por',
|
||||
endsWith: 'Termina en',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'No es igual a',
|
||||
greaterThan: 'Mayor que',
|
||||
greaterThanOrEqual: 'Mayor o igual que',
|
||||
lessThan: 'Menor que',
|
||||
lessThanOrEqual: 'Menor o igual que',
|
||||
between: 'Entre',
|
||||
notBetween: 'No está entre',
|
||||
above: 'Por encima',
|
||||
below: 'Por debajo',
|
||||
topN: '{0} superiores',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'No es igual a',
|
||||
after: 'Después de',
|
||||
afterOrEqual: 'Después o igual a',
|
||||
before: 'Antes de',
|
||||
beforeOrEqual: 'Antes o igual a',
|
||||
between: 'Entre',
|
||||
notBetween: 'No está entre',
|
||||
today: 'Hoy',
|
||||
yesterday: 'Ayer',
|
||||
tomorrow: 'Mañana',
|
||||
thisWeek: 'Esta semana',
|
||||
lastWeek: 'La semana pasada',
|
||||
nextWeek: 'La próxima semana',
|
||||
thisMonth: 'Este mes',
|
||||
lastMonth: 'El mes pasado',
|
||||
nextMonth: 'El próximo mes',
|
||||
thisQuarter: 'Este trimestre',
|
||||
lastQuarter: 'El trimestre pasado',
|
||||
nextQuarter: 'El próximo trimestre',
|
||||
thisYear: 'Este año',
|
||||
nextYear: 'El próximo año',
|
||||
lastYear: 'El año pasado',
|
||||
quarter: 'Por trimestre',
|
||||
month: 'Por mes',
|
||||
q1: 'Primer trimestre',
|
||||
q2: 'Segundo trimestre',
|
||||
q3: 'Tercer trimestre',
|
||||
q4: 'Cuarto trimestre',
|
||||
m1: 'Enero',
|
||||
m2: 'Febrero',
|
||||
m3: 'Marzo',
|
||||
m4: 'Abril',
|
||||
m5: 'Mayo',
|
||||
m6: 'Junio',
|
||||
m7: 'Julio',
|
||||
m8: 'Agosto',
|
||||
m9: 'Septiembre',
|
||||
m10: 'Octubre',
|
||||
m11: 'Noviembre',
|
||||
m12: 'Diciembre',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filtro',
|
||||
'by-values': 'Por valores',
|
||||
'by-colors': 'Por colores',
|
||||
'by-conditions': 'Por condiciones',
|
||||
back: 'Atrás',
|
||||
resize: 'Cambiar tamaño',
|
||||
'filter-by-cell-fill-color': 'Color de relleno de celda',
|
||||
'filter-by-cell-text-color': 'Color del texto de celda',
|
||||
'filter-by-color-none': 'Esta columna contiene un solo color',
|
||||
'clear-filter': 'Borrar filtro',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
'search-placeholder': 'Usa espacio para separar palabras clave',
|
||||
'input-values-placeholder': 'Introducir valores',
|
||||
'select-all': 'Seleccionar todo',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
164
packages/sheets-table-ui/src/locale/fa-IR.ts
Normal file
164
packages/sheets-table-ui/src/locale/fa-IR.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'جدول',
|
||||
selectRange: 'انتخاب محدوده جدول',
|
||||
rename: 'تغییر نام جدول',
|
||||
renamePlaceholder: 'نام جدول را وارد کنید',
|
||||
updateRange: 'بهروزرسانی محدوده جدول',
|
||||
tableRangeWithMergeError: 'محدوده جدول نمیتواند با سلولهای ادغامشده همپوشانی داشته باشد',
|
||||
tableRangeWithOtherTableError: 'محدوده جدول نمیتواند با جداول دیگر همپوشانی داشته باشد',
|
||||
tableRangeSingleRowError: 'محدوده جدول نمیتواند یک ردیف منفرد باشد',
|
||||
updateError: 'نمیتوان محدوده جدول را به منطقهای که با اصل همپوشانی ندارد و در یک ردیف نیست تنظیم کرد',
|
||||
tableStyle: 'سبک جدول',
|
||||
defaultStyle: 'سبک پیشفرض',
|
||||
customStyle: 'سبک سفارشی',
|
||||
customTooMore: 'تعداد تمهای سفارشی از حد مجاز فراتر رفته است، لطفاً برخی از تمهای غیرضروری را حذف کرده و دوباره اضافه کنید',
|
||||
setTheme: 'تنظیم قالب جدول',
|
||||
removeTable: 'حذف جدول',
|
||||
cancel: 'لغو',
|
||||
confirm: 'تأیید',
|
||||
header: 'سربرگ',
|
||||
footer: 'پاورقی',
|
||||
firstLine: 'خط اول',
|
||||
secondLine: 'خط دوم',
|
||||
tableNameError: 'نام جدول نمیتواند شامل فاصله باشد، نمیتواند با عدد شروع شود و نمیتواند با نام جدولهای موجود تکراری باشد',
|
||||
columnMenu: {
|
||||
'insert-left': 'درج ۱ ستون جدول در سمت چپ',
|
||||
'insert-right': 'درج ۱ ستون جدول در سمت راست',
|
||||
delete: 'حذف ستون جدول',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'صعودی',
|
||||
'sort-desc': 'نزولی',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'درج جدول',
|
||||
row: 'درج ردیف جدول',
|
||||
col: 'درج ستون جدول',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'حذف جدول',
|
||||
row: 'حذف ردیف جدول',
|
||||
col: 'حذف ستون جدول',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: 'متن',
|
||||
number: 'عدد',
|
||||
date: 'تاریخ',
|
||||
|
||||
empty: '(خالی)',
|
||||
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'برابر است با',
|
||||
notEqual: 'برابر نیست با',
|
||||
contains: 'شامل است',
|
||||
notContains: 'شامل نیست',
|
||||
startsWith: 'شروع میشود با',
|
||||
endsWith: 'پایان مییابد با',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'برابر است با',
|
||||
notEqual: 'برابر نیست با',
|
||||
greaterThan: 'بزرگتر از',
|
||||
greaterThanOrEqual: 'بزرگتر یا مساوی با',
|
||||
lessThan: 'کوچکتر از',
|
||||
lessThanOrEqual: 'کوچکتر یا مساوی با',
|
||||
between: 'بین',
|
||||
notBetween: 'خارج از محدوده',
|
||||
above: 'بالاتر از',
|
||||
below: 'پایینتر از',
|
||||
topN: 'برترین {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'برابر است با',
|
||||
notEqual: 'برابر نیست با',
|
||||
after: 'بعد از',
|
||||
afterOrEqual: 'بعد یا برابر با',
|
||||
before: 'قبل از',
|
||||
beforeOrEqual: 'قبل یا برابر با',
|
||||
between: 'بین',
|
||||
notBetween: 'خارج از محدوده',
|
||||
today: 'امروز',
|
||||
yesterday: 'دیروز',
|
||||
tomorrow: 'فردا',
|
||||
thisWeek: 'این هفته',
|
||||
lastWeek: 'هفته گذشته',
|
||||
nextWeek: 'هفته آینده',
|
||||
thisMonth: 'این ماه',
|
||||
lastMonth: 'ماه گذشته',
|
||||
nextMonth: 'ماه آینده',
|
||||
thisQuarter: 'این سهماهه',
|
||||
lastQuarter: 'سهماهه گذشته',
|
||||
nextQuarter: 'سهماهه آینده',
|
||||
thisYear: 'امسال',
|
||||
nextYear: 'سال آینده',
|
||||
lastYear: 'سال گذشته',
|
||||
quarter: 'بر اساس سهماهه',
|
||||
month: 'بر اساس ماه',
|
||||
q1: 'سهماهه اول',
|
||||
q2: 'سهماهه دوم',
|
||||
q3: 'سهماهه سوم',
|
||||
q4: 'سهماهه چهارم',
|
||||
m1: 'ژانویه',
|
||||
m2: 'فوریه',
|
||||
m3: 'مارس',
|
||||
m4: 'آوریل',
|
||||
m5: 'مه',
|
||||
m6: 'ژوئن',
|
||||
m7: 'ژوئیه',
|
||||
m8: 'اوت',
|
||||
m9: 'سپتامبر',
|
||||
m10: 'اکتبر',
|
||||
m11: 'نوامبر',
|
||||
m12: 'دسامبر',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'فیلتر',
|
||||
'by-values': 'بر اساس مقادیر',
|
||||
'by-colors': 'بر اساس رنگها',
|
||||
'by-conditions': 'بر اساس شرایط',
|
||||
back: 'بازگشت',
|
||||
resize: 'تغییر اندازه',
|
||||
'filter-by-cell-fill-color': 'رنگ پسزمینه سلول',
|
||||
'filter-by-cell-text-color': 'رنگ متن سلول',
|
||||
'filter-by-color-none': 'این ستون فقط یک رنگ دارد',
|
||||
'clear-filter': 'پاک کردن فیلتر',
|
||||
cancel: 'انصراف',
|
||||
confirm: 'تایید',
|
||||
'search-placeholder': 'برای جداسازی کلیدواژهها از فاصله استفاده کنید',
|
||||
'input-values-placeholder': 'ورود مقادیر',
|
||||
'select-all': 'انتخاب همه',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
163
packages/sheets-table-ui/src/locale/fr-FR.ts
Normal file
163
packages/sheets-table-ui/src/locale/fr-FR.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tableau',
|
||||
selectRange: 'Sélectionner la plage du tableau',
|
||||
rename: 'Renommer le tableau',
|
||||
renamePlaceholder: 'Saisir le nom du tableau',
|
||||
updateRange: 'Mettre à jour la plage du tableau',
|
||||
tableRangeWithMergeError: 'La plage du tableau ne peut pas chevaucher des cellules fusionnées',
|
||||
tableRangeWithOtherTableError: 'La plage du tableau ne peut pas chevaucher d\'autres tableaux',
|
||||
tableRangeSingleRowError: 'La plage du tableau ne peut pas être une seule ligne',
|
||||
updateError: 'Impossible de définir la plage du tableau sur une zone qui ne chevauche pas l\'original et n\'est pas sur la même ligne',
|
||||
tableStyle: 'Style du tableau',
|
||||
defaultStyle: 'Style par défaut',
|
||||
customStyle: 'Style personnalisé',
|
||||
customTooMore: 'Le nombre de thèmes personnalisés dépasse la limite maximale, veuillez supprimer certains thèmes inutiles et les ajouter à nouveau',
|
||||
setTheme: 'Définir le thème du tableau',
|
||||
removeTable: 'Supprimer le tableau',
|
||||
cancel: 'Annuler',
|
||||
confirm: 'Confirmer',
|
||||
header: 'En-tête',
|
||||
footer: 'Pied de page',
|
||||
firstLine: 'Première ligne',
|
||||
secondLine: 'Deuxième ligne',
|
||||
tableNameError: 'Le nom du tableau ne peut pas contenir d\'espaces, ne peut pas commencer par un chiffre et ne peut pas être identique à un nom de tableau existant',
|
||||
columnMenu: {
|
||||
'insert-left': 'Insérer 1 colonne à gauche',
|
||||
'insert-right': 'Insérer 1 colonne à droite',
|
||||
delete: 'Supprimer la colonne du tableau',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Croissant',
|
||||
'sort-desc': 'Décroissant',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Insérer un tableau',
|
||||
row: 'Insérer une ligne de tableau',
|
||||
col: 'Insérer une colonne de tableau',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Supprimer le tableau',
|
||||
row: 'Supprimer une ligne de tableau',
|
||||
col: 'Supprimer une colonne de tableau',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: 'Texte',
|
||||
number: 'Nombre',
|
||||
date: 'Date',
|
||||
|
||||
empty: '(Vide)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Égal à',
|
||||
notEqual: 'Différent de',
|
||||
contains: 'Contient',
|
||||
notContains: 'Ne contient pas',
|
||||
startsWith: 'Commence par',
|
||||
endsWith: 'Se termine par',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Égal à',
|
||||
notEqual: 'Différent de',
|
||||
greaterThan: 'Supérieur à',
|
||||
greaterThanOrEqual: 'Supérieur ou égal à',
|
||||
lessThan: 'Inférieur à',
|
||||
lessThanOrEqual: 'Inférieur ou égal à',
|
||||
between: 'Entre',
|
||||
notBetween: 'Pas entre',
|
||||
above: 'Supérieur à',
|
||||
below: 'Inférieur à',
|
||||
topN: 'Top {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Égal à',
|
||||
notEqual: 'Différent de',
|
||||
after: 'Après',
|
||||
afterOrEqual: 'Après ou égal à',
|
||||
before: 'Avant',
|
||||
beforeOrEqual: 'Avant ou égal à',
|
||||
between: 'Entre',
|
||||
notBetween: 'Pas entre',
|
||||
today: "Aujourd'hui",
|
||||
yesterday: 'Hier',
|
||||
tomorrow: 'Demain',
|
||||
thisWeek: 'Cette semaine',
|
||||
lastWeek: 'La semaine dernière',
|
||||
nextWeek: 'La semaine prochaine',
|
||||
thisMonth: 'Ce mois-ci',
|
||||
lastMonth: 'Le mois dernier',
|
||||
nextMonth: 'Le mois prochain',
|
||||
thisQuarter: 'Ce trimestre',
|
||||
lastQuarter: 'Le trimestre dernier',
|
||||
nextQuarter: 'Le trimestre prochain',
|
||||
thisYear: 'Cette année',
|
||||
nextYear: "L'année prochaine",
|
||||
lastYear: "L'année dernière",
|
||||
quarter: 'Par trimestre',
|
||||
month: 'Par mois',
|
||||
q1: 'Premier trimestre',
|
||||
q2: 'Deuxième trimestre',
|
||||
q3: 'Troisième trimestre',
|
||||
q4: 'Quatrième trimestre',
|
||||
m1: 'Janvier',
|
||||
m2: 'Février',
|
||||
m3: 'Mars',
|
||||
m4: 'Avril',
|
||||
m5: 'Mai',
|
||||
m6: 'Juin',
|
||||
m7: 'Juillet',
|
||||
m8: 'Août',
|
||||
m9: 'Septembre',
|
||||
m10: 'Octobre',
|
||||
m11: 'Novembre',
|
||||
m12: 'Décembre',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filtre',
|
||||
'by-values': 'Par valeurs',
|
||||
'by-colors': 'Par couleurs',
|
||||
'by-conditions': 'Par conditions',
|
||||
back: 'Retour',
|
||||
resize: 'Redimensionner',
|
||||
'filter-by-cell-fill-color': 'Couleur de remplissage de cellule',
|
||||
'filter-by-cell-text-color': 'Couleur du texte de cellule',
|
||||
'filter-by-color-none': 'Cette colonne ne contient qu’une seule couleur',
|
||||
'clear-filter': 'Effacer le filtre',
|
||||
cancel: 'Annuler',
|
||||
confirm: 'Confirmer',
|
||||
'search-placeholder': 'Utilisez un espace pour séparer les mots-clés',
|
||||
'input-values-placeholder': 'Saisir des valeurs',
|
||||
'select-all': 'Tout sélectionner',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/id-ID.ts
Normal file
162
packages/sheets-table-ui/src/locale/id-ID.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabel',
|
||||
selectRange: 'Pilih Rentang Tabel',
|
||||
rename: 'Ubah Nama Tabel',
|
||||
renamePlaceholder: 'Masukkan nama tabel',
|
||||
updateRange: 'Perbarui Rentang Tabel',
|
||||
tableRangeWithMergeError: 'Rentang tabel tidak boleh tumpang tindih dengan sel yang digabungkan',
|
||||
tableRangeWithOtherTableError: 'Rentang tabel tidak boleh tumpang tindih dengan tabel lain',
|
||||
tableRangeSingleRowError: 'Rentang tabel tidak boleh hanya satu baris',
|
||||
updateError: 'Tidak dapat mengatur rentang tabel ke area yang tidak tumpang tindih dengan aslinya dan tidak berada di baris yang sama',
|
||||
tableStyle: 'Gaya Tabel',
|
||||
defaultStyle: 'Gaya Bawaan',
|
||||
customStyle: 'Gaya Kustom',
|
||||
customTooMore: 'Jumlah tema kustom melebihi batas maksimum, silakan hapus beberapa tema yang tidak diperlukan dan tambahkan lagi',
|
||||
setTheme: 'Atur Tema Tabel',
|
||||
removeTable: 'Hapus Tabel',
|
||||
cancel: 'Batal',
|
||||
confirm: 'Konfirmasi',
|
||||
header: 'Header',
|
||||
footer: 'Footer',
|
||||
firstLine: 'Baris Pertama',
|
||||
secondLine: 'Baris Kedua',
|
||||
tableNameError: 'Nama tabel tidak boleh mengandung spasi, tidak boleh diawali dengan angka, dan tidak boleh sama dengan nama tabel yang sudah ada',
|
||||
columnMenu: {
|
||||
'insert-left': 'Sisipkan 1 kolom tabel di kiri',
|
||||
'insert-right': 'Sisipkan 1 kolom tabel di kanan',
|
||||
delete: 'Hapus kolom tabel',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Menaik',
|
||||
'sort-desc': 'Menurun',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Sisipkan Tabel',
|
||||
row: 'Sisipkan Baris Tabel',
|
||||
col: 'Sisipkan Kolom Tabel',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Hapus Tabel',
|
||||
row: 'Hapus Baris Tabel',
|
||||
col: 'Hapus Kolom Tabel',
|
||||
},
|
||||
condition: {
|
||||
string: 'Teks',
|
||||
number: 'Angka',
|
||||
date: 'Tanggal',
|
||||
|
||||
empty: '(Kosong)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Sama dengan',
|
||||
notEqual: 'Tidak sama dengan',
|
||||
contains: 'Mengandung',
|
||||
notContains: 'Tidak mengandung',
|
||||
startsWith: 'Dimulai dengan',
|
||||
endsWith: 'Diakhiri dengan',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Sama dengan',
|
||||
notEqual: 'Tidak sama dengan',
|
||||
greaterThan: 'Lebih besar dari',
|
||||
greaterThanOrEqual: 'Lebih besar dari atau sama dengan',
|
||||
lessThan: 'Kurang dari',
|
||||
lessThanOrEqual: 'Kurang dari atau sama dengan',
|
||||
between: 'Di antara',
|
||||
notBetween: 'Tidak di antara',
|
||||
above: 'Di atas',
|
||||
below: 'Di bawah',
|
||||
topN: 'Teratas {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Sama dengan',
|
||||
notEqual: 'Tidak sama dengan',
|
||||
after: 'Setelah',
|
||||
afterOrEqual: 'Setelah atau sama dengan',
|
||||
before: 'Sebelum',
|
||||
beforeOrEqual: 'Sebelum atau sama dengan',
|
||||
between: 'Di antara',
|
||||
notBetween: 'Tidak di antara',
|
||||
today: 'Hari ini',
|
||||
yesterday: 'Kemarin',
|
||||
tomorrow: 'Besok',
|
||||
thisWeek: 'Minggu ini',
|
||||
lastWeek: 'Minggu lalu',
|
||||
nextWeek: 'Minggu depan',
|
||||
thisMonth: 'Bulan ini',
|
||||
lastMonth: 'Bulan lalu',
|
||||
nextMonth: 'Bulan depan',
|
||||
thisQuarter: 'Kuartal ini',
|
||||
lastQuarter: 'Kuartal lalu',
|
||||
nextQuarter: 'Kuartal depan',
|
||||
thisYear: 'Tahun ini',
|
||||
nextYear: 'Tahun depan',
|
||||
lastYear: 'Tahun lalu',
|
||||
quarter: 'Berdasarkan kuartal',
|
||||
month: 'Berdasarkan bulan',
|
||||
q1: 'Kuartal pertama',
|
||||
q2: 'Kuartal kedua',
|
||||
q3: 'Kuartal ketiga',
|
||||
q4: 'Kuartal keempat',
|
||||
m1: 'Januari',
|
||||
m2: 'Februari',
|
||||
m3: 'Maret',
|
||||
m4: 'April',
|
||||
m5: 'Mei',
|
||||
m6: 'Juni',
|
||||
m7: 'Juli',
|
||||
m8: 'Agustus',
|
||||
m9: 'September',
|
||||
m10: 'Oktober',
|
||||
m11: 'November',
|
||||
m12: 'Desember',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filter',
|
||||
'by-values': 'Berdasarkan Nilai',
|
||||
'by-colors': 'Berdasarkan Warna',
|
||||
'by-conditions': 'Berdasarkan Kondisi',
|
||||
back: 'Kembali',
|
||||
resize: 'Ubah ukuran',
|
||||
'filter-by-cell-fill-color': 'Warna isian sel',
|
||||
'filter-by-cell-text-color': 'Warna teks sel',
|
||||
'filter-by-color-none': 'Kolom ini hanya berisi satu warna',
|
||||
'clear-filter': 'Hapus Filter',
|
||||
cancel: 'Batal',
|
||||
confirm: 'Konfirmasi',
|
||||
'search-placeholder': 'Gunakan spasi untuk memisahkan kata kunci',
|
||||
'input-values-placeholder': 'Masukkan Nilai',
|
||||
'select-all': 'Pilih Semua',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/it-IT.ts
Normal file
162
packages/sheets-table-ui/src/locale/it-IT.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabella',
|
||||
selectRange: 'Seleziona Intervallo Tabella',
|
||||
rename: 'Rinomina Tabella',
|
||||
renamePlaceholder: 'Inserisci nome tabella',
|
||||
updateRange: 'Aggiorna Intervallo Tabella',
|
||||
tableRangeWithMergeError: 'L\'intervallo tabella non può sovrapporsi a celle unite',
|
||||
tableRangeWithOtherTableError: 'L\'intervallo tabella non può sovrapporsi ad altre tabelle',
|
||||
tableRangeSingleRowError: 'L\'intervallo tabella non può essere una singola riga',
|
||||
updateError: 'Impossibile impostare l\'intervallo tabella su un\'area che non si sovrappone all\'originale e non si trova nella stessa riga',
|
||||
tableStyle: 'Stile Tabella',
|
||||
defaultStyle: 'Stile Predefinito',
|
||||
customStyle: 'Stile Personalizzato',
|
||||
customTooMore: 'Il numero di temi personalizzati supera il limite massimo, elimina alcuni temi non necessari e aggiungili nuovamente',
|
||||
setTheme: 'Imposta Tema Tabella',
|
||||
removeTable: 'Rimuovi Tabella',
|
||||
cancel: 'Annulla',
|
||||
confirm: 'Conferma',
|
||||
header: 'Intestazione',
|
||||
footer: 'Piè di Pagina',
|
||||
firstLine: 'Prima Riga',
|
||||
secondLine: 'Seconda Riga',
|
||||
tableNameError: 'Il nome tabella non può contenere spazi, non può iniziare con un numero e non può essere identico a un nome tabella esistente',
|
||||
columnMenu: {
|
||||
'insert-left': 'Inserisci 1 colonna tabella a sinistra',
|
||||
'insert-right': 'Inserisci 1 colonna tabella a destra',
|
||||
delete: 'Elimina colonna tabella',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Crescente',
|
||||
'sort-desc': 'Decrescente',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Inserisci Tabella',
|
||||
row: 'Inserisci Riga Tabella',
|
||||
col: 'Inserisci Colonna Tabella',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Rimuovi Tabella',
|
||||
row: 'Rimuovi Riga Tabella',
|
||||
col: 'Rimuovi Colonna Tabella',
|
||||
},
|
||||
condition: {
|
||||
string: 'Testo',
|
||||
number: 'Numero',
|
||||
date: 'Data',
|
||||
|
||||
empty: '(Vuoto)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Uguale a',
|
||||
notEqual: 'Diverso da',
|
||||
contains: 'Contiene',
|
||||
notContains: 'Non contiene',
|
||||
startsWith: 'Inizia con',
|
||||
endsWith: 'Termina con',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Uguale a',
|
||||
notEqual: 'Diverso da',
|
||||
greaterThan: 'Maggiore di',
|
||||
greaterThanOrEqual: 'Maggiore o uguale a',
|
||||
lessThan: 'Minore di',
|
||||
lessThanOrEqual: 'Minore o uguale a',
|
||||
between: 'Compreso tra',
|
||||
notBetween: 'Non compreso tra',
|
||||
above: 'Sopra',
|
||||
below: 'Sotto',
|
||||
topN: 'Superiore {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Uguale a',
|
||||
notEqual: 'Diverso da',
|
||||
after: 'Dopo',
|
||||
afterOrEqual: 'Dopo o uguale a',
|
||||
before: 'Prima',
|
||||
beforeOrEqual: 'Prima o uguale a',
|
||||
between: 'Compreso tra',
|
||||
notBetween: 'Non compreso tra',
|
||||
today: 'Oggi',
|
||||
yesterday: 'Ieri',
|
||||
tomorrow: 'Domani',
|
||||
thisWeek: 'Questa settimana',
|
||||
lastWeek: 'Settimana scorsa',
|
||||
nextWeek: 'Settimana prossima',
|
||||
thisMonth: 'Questo mese',
|
||||
lastMonth: 'Mese scorso',
|
||||
nextMonth: 'Mese prossimo',
|
||||
thisQuarter: 'Questo trimestre',
|
||||
lastQuarter: 'Trimestre scorso',
|
||||
nextQuarter: 'Trimestre prossimo',
|
||||
thisYear: 'Questo anno',
|
||||
nextYear: 'Anno prossimo',
|
||||
lastYear: 'Anno scorso',
|
||||
quarter: 'Per trimestre',
|
||||
month: 'Per mese',
|
||||
q1: 'Primo trimestre',
|
||||
q2: 'Secondo trimestre',
|
||||
q3: 'Terzo trimestre',
|
||||
q4: 'Quarto trimestre',
|
||||
m1: 'Gennaio',
|
||||
m2: 'Febbraio',
|
||||
m3: 'Marzo',
|
||||
m4: 'Aprile',
|
||||
m5: 'Maggio',
|
||||
m6: 'Giugno',
|
||||
m7: 'Luglio',
|
||||
m8: 'Agosto',
|
||||
m9: 'Settembre',
|
||||
m10: 'Ottobre',
|
||||
m11: 'Novembre',
|
||||
m12: 'Dicembre',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filtro',
|
||||
'by-values': 'Per Valori',
|
||||
'by-colors': 'Per Colori',
|
||||
'by-conditions': 'Per Condizioni',
|
||||
back: 'Indietro',
|
||||
resize: 'Ridimensiona',
|
||||
'filter-by-cell-fill-color': 'Colore di riempimento cella',
|
||||
'filter-by-cell-text-color': 'Colore testo cella',
|
||||
'filter-by-color-none': 'Questa colonna contiene un solo colore',
|
||||
'clear-filter': 'Cancella Filtro',
|
||||
cancel: 'Annulla',
|
||||
confirm: 'Conferma',
|
||||
'search-placeholder': 'Usa lo spazio per separare le parole chiave',
|
||||
'input-values-placeholder': 'Inserisci valori',
|
||||
'select-all': 'Seleziona Tutto',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/ja-JP.ts
Normal file
162
packages/sheets-table-ui/src/locale/ja-JP.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: '表',
|
||||
selectRange: '表の範囲を選択',
|
||||
rename: '表の名前を変更',
|
||||
renamePlaceholder: 'テーブル名を入力',
|
||||
updateRange: '表の範囲を更新',
|
||||
tableRangeWithMergeError: '表の範囲は結合セルと重複できません',
|
||||
tableRangeWithOtherTableError: '表の範囲は他の表と重複できません',
|
||||
tableRangeSingleRowError: '表の範囲は1行のみでは構成できません',
|
||||
updateError: '表の範囲は元の範囲と重複しない、または同じ行上にない場合に設定できます',
|
||||
tableStyle: '表のスタイル',
|
||||
defaultStyle: '既定のスタイル',
|
||||
customStyle: 'ユーザー設定のスタイル',
|
||||
customTooMore: 'ユーザー定義テーマの数が上限を超えています。いくつか削除してから再度追加してください',
|
||||
setTheme: '表のテーマを設定',
|
||||
removeTable: '表を削除',
|
||||
cancel: 'キャンセル',
|
||||
confirm: '確認',
|
||||
header: 'ヘッダー',
|
||||
footer: 'フッター',
|
||||
firstLine: '先頭行',
|
||||
secondLine: '2行目',
|
||||
tableNameError: '表の名前は空白を含めることはできず、数字で始めることはできず、既存の表名と重複できません',
|
||||
columnMenu: {
|
||||
'insert-left': '左に 1 列挿入',
|
||||
'insert-right': '右に 1 列挿入',
|
||||
delete: '列を削除',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': '昇順',
|
||||
'sort-desc': '降順',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: '表を挿入',
|
||||
row: '表の行を挿入',
|
||||
col: '表の列を挿入',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: '表を削除',
|
||||
row: '表の行を削除',
|
||||
col: '表の列を削除',
|
||||
},
|
||||
condition: {
|
||||
string: '文字列',
|
||||
number: '数値',
|
||||
date: '日付',
|
||||
|
||||
empty: '(空白)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: '等しい',
|
||||
notEqual: '等しくない',
|
||||
contains: '含む',
|
||||
notContains: '含まない',
|
||||
startsWith: '前方一致',
|
||||
endsWith: '後方一致',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: '等しい',
|
||||
notEqual: '等しくない',
|
||||
greaterThan: 'より大きい',
|
||||
greaterThanOrEqual: '以上',
|
||||
lessThan: 'より小さい',
|
||||
lessThanOrEqual: '以下',
|
||||
between: '範囲内',
|
||||
notBetween: '範囲外',
|
||||
above: 'より大きい',
|
||||
below: 'より小さい',
|
||||
topN: '上位 {0} 件',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: '同じ日付',
|
||||
notEqual: '異なる日付',
|
||||
after: '以降',
|
||||
afterOrEqual: '以降または同じ',
|
||||
before: '以前',
|
||||
beforeOrEqual: '以前または同じ',
|
||||
between: '範囲内',
|
||||
notBetween: '範囲外',
|
||||
today: '今日',
|
||||
yesterday: '昨日',
|
||||
tomorrow: '明日',
|
||||
thisWeek: '今週',
|
||||
lastWeek: '先週',
|
||||
nextWeek: '来週',
|
||||
thisMonth: '今月',
|
||||
lastMonth: '先月',
|
||||
nextMonth: '来月',
|
||||
thisQuarter: '今四半期',
|
||||
lastQuarter: '前四半期',
|
||||
nextQuarter: '次の四半期',
|
||||
thisYear: '今年',
|
||||
nextYear: '来年',
|
||||
lastYear: '昨年',
|
||||
quarter: '四半期',
|
||||
month: '月別',
|
||||
q1: '第1四半期',
|
||||
q2: '第2四半期',
|
||||
q3: '第3四半期',
|
||||
q4: '第4四半期',
|
||||
m1: '1月',
|
||||
m2: '2月',
|
||||
m3: '3月',
|
||||
m4: '4月',
|
||||
m5: '5月',
|
||||
m6: '6月',
|
||||
m7: '7月',
|
||||
m8: '8月',
|
||||
m9: '9月',
|
||||
m10: '10月',
|
||||
m11: '11月',
|
||||
m12: '12月',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'フィルタ',
|
||||
'by-values': '値でフィルタ',
|
||||
'by-colors': '色でフィルタ',
|
||||
'by-conditions': '条件でフィルタ',
|
||||
back: '戻る',
|
||||
resize: 'サイズ変更',
|
||||
'filter-by-cell-fill-color': 'セルの塗りつぶし色',
|
||||
'filter-by-cell-text-color': 'セルの文字色',
|
||||
'filter-by-color-none': 'この列には 1 色のみ含まれています',
|
||||
'clear-filter': 'フィルタをクリア',
|
||||
cancel: 'キャンセル',
|
||||
confirm: '確認',
|
||||
'search-placeholder': '検索語はスペースで区切る',
|
||||
'input-values-placeholder': '値を入力',
|
||||
'select-all': 'すべて選択',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/ko-KR.ts
Normal file
162
packages/sheets-table-ui/src/locale/ko-KR.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: '표',
|
||||
selectRange: '표 범위 선택',
|
||||
rename: '표 이름 바꾸기',
|
||||
renamePlaceholder: '테이블 이름 입력',
|
||||
updateRange: '표 범위 업데이트',
|
||||
tableRangeWithMergeError: '표 범위는 병합된 셀과 겹칠 수 없습니다',
|
||||
tableRangeWithOtherTableError: '표 범위는 다른 표와 겹칠 수 없습니다',
|
||||
tableRangeSingleRowError: '표 범위는 한 줄만으로 구성될 수 없습니다',
|
||||
updateError: '표 범위는 원래 범위와 겹치지 않거나 같은 행에 있지 않으면 설정할 수 없습니다',
|
||||
tableStyle: '표 스타일',
|
||||
defaultStyle: '기본 스타일',
|
||||
customStyle: '사용자 지정 스타일',
|
||||
customTooMore: '사용자 지정 테마 수가 최대치를 초과했습니다. 일부 테마를 삭제한 후 다시 추가해 주세요',
|
||||
setTheme: '표 테마 설정',
|
||||
removeTable: '표 제거',
|
||||
cancel: '취소',
|
||||
confirm: '확인',
|
||||
header: '머리글',
|
||||
footer: '바닥글',
|
||||
firstLine: '첫 번째 줄',
|
||||
secondLine: '두 번째 줄',
|
||||
tableNameError: '표 이름은 공백을 포함할 수 없으며 숫자로 시작할 수 없고 기존 표 이름과 중복될 수 없습니다',
|
||||
columnMenu: {
|
||||
'insert-left': '왼쪽에 열 1개 삽입',
|
||||
'insert-right': '오른쪽에 열 1개 삽입',
|
||||
delete: '테이블 열 삭제',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': '오름차순',
|
||||
'sort-desc': '내림차순',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: '표 삽입',
|
||||
row: '표 행 삽입',
|
||||
col: '표 열 삽입',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: '표 제거',
|
||||
row: '표 행 삭제',
|
||||
col: '표 열 삭제',
|
||||
},
|
||||
condition: {
|
||||
string: '텍스트',
|
||||
number: '숫자',
|
||||
date: '날짜',
|
||||
|
||||
empty: '(비어 있음)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: '같음',
|
||||
notEqual: '같지 않음',
|
||||
contains: '포함함',
|
||||
notContains: '포함하지 않음',
|
||||
startsWith: '시작 문자열',
|
||||
endsWith: '끝 문자열',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: '같음',
|
||||
notEqual: '같지 않음',
|
||||
greaterThan: '보다 큼',
|
||||
greaterThanOrEqual: '크거나 같음',
|
||||
lessThan: '보다 작음',
|
||||
lessThanOrEqual: '작거나 같음',
|
||||
between: '사이',
|
||||
notBetween: '사이 아님',
|
||||
above: '보다 큼',
|
||||
below: '보다 작음',
|
||||
topN: '상위 {0}개',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: '같은 날짜',
|
||||
notEqual: '같지 않은 날짜',
|
||||
after: '이후',
|
||||
afterOrEqual: '이후 또는 같음',
|
||||
before: '이전',
|
||||
beforeOrEqual: '이전 또는 같음',
|
||||
between: '사이',
|
||||
notBetween: '사이 아님',
|
||||
today: '오늘',
|
||||
yesterday: '어제',
|
||||
tomorrow: '내일',
|
||||
thisWeek: '이번 주',
|
||||
lastWeek: '지난주',
|
||||
nextWeek: '다음 주',
|
||||
thisMonth: '이번 달',
|
||||
lastMonth: '지난달',
|
||||
nextMonth: '다음 달',
|
||||
thisQuarter: '이번 분기',
|
||||
lastQuarter: '지난 분기',
|
||||
nextQuarter: '다음 분기',
|
||||
thisYear: '올해',
|
||||
nextYear: '내년',
|
||||
lastYear: '작년',
|
||||
quarter: '분기별',
|
||||
month: '월별',
|
||||
q1: '1분기',
|
||||
q2: '2분기',
|
||||
q3: '3분기',
|
||||
q4: '4분기',
|
||||
m1: '1월',
|
||||
m2: '2월',
|
||||
m3: '3월',
|
||||
m4: '4월',
|
||||
m5: '5월',
|
||||
m6: '6월',
|
||||
m7: '7월',
|
||||
m8: '8월',
|
||||
m9: '9월',
|
||||
m10: '10월',
|
||||
m11: '11월',
|
||||
m12: '12월',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: '필터',
|
||||
'by-values': '값으로 필터',
|
||||
'by-colors': '색으로 필터',
|
||||
'by-conditions': '조건으로 필터',
|
||||
back: '뒤로',
|
||||
resize: '크기 조절',
|
||||
'filter-by-cell-fill-color': '셀 채우기 색',
|
||||
'filter-by-cell-text-color': '셀 텍스트 색',
|
||||
'filter-by-color-none': '이 열에는 한 가지 색만 있습니다',
|
||||
'clear-filter': '필터 지우기',
|
||||
cancel: '취소',
|
||||
confirm: '확인',
|
||||
'search-placeholder': '검색어는 공백으로 구분',
|
||||
'input-values-placeholder': '값 입력',
|
||||
'select-all': '전체 선택',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/pl-PL.ts
Normal file
162
packages/sheets-table-ui/src/locale/pl-PL.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabela',
|
||||
selectRange: 'Zaznacz zakres tabeli',
|
||||
rename: 'Zmień nazwę tabeli',
|
||||
renamePlaceholder: 'Wprowadź nazwę tabeli',
|
||||
updateRange: 'Zaktualizuj zakres tabeli',
|
||||
tableRangeWithMergeError: 'Zakres tabeli nie może nakładać się na scalone komórki',
|
||||
tableRangeWithOtherTableError: 'Zakres tabeli nie może nakładać się na inne tabele',
|
||||
tableRangeSingleRowError: 'Zakres tabeli nie może być pojedynczym wierszem',
|
||||
updateError: 'Nie można ustawić zakresu tabeli na obszar, który nie nakłada się na pierwotny zakres i nie znajduje się w tym samym wierszu',
|
||||
tableStyle: 'Styl tabeli',
|
||||
defaultStyle: 'Styl domyślny',
|
||||
customStyle: 'Styl niestandardowy',
|
||||
customTooMore: 'Liczba motywów niestandardowych przekracza maksymalny limit. Usuń niektóre niepotrzebne motywy i dodaj je ponownie',
|
||||
setTheme: 'Ustaw motyw tabeli',
|
||||
removeTable: 'Usuń tabelę',
|
||||
cancel: 'Anuluj',
|
||||
confirm: 'Potwierdź',
|
||||
header: 'Nagłówek',
|
||||
footer: 'Stopka',
|
||||
firstLine: 'Pierwszy wiersz',
|
||||
secondLine: 'Drugi wiersz',
|
||||
tableNameError: 'Nazwa tabeli nie może zawierać spacji, nie może zaczynać się od cyfry i nie może być identyczna z istniejącą nazwą tabeli',
|
||||
columnMenu: {
|
||||
'insert-left': 'Wstaw 1 kolumnę tabeli z lewej',
|
||||
'insert-right': 'Wstaw 1 kolumnę tabeli z prawej',
|
||||
delete: 'Usuń kolumnę tabeli',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Rosnąco',
|
||||
'sort-desc': 'Malejąco',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Wstaw tabelę',
|
||||
row: 'Wstaw wiersz tabeli',
|
||||
col: 'Wstaw kolumnę tabeli',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Usuń tabelę',
|
||||
row: 'Usuń wiersz tabeli',
|
||||
col: 'Usuń kolumnę tabeli',
|
||||
},
|
||||
condition: {
|
||||
string: 'Tekst',
|
||||
number: 'Liczba',
|
||||
date: 'Data',
|
||||
|
||||
empty: '(Puste)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Równe',
|
||||
notEqual: 'Różne od',
|
||||
contains: 'Zawiera',
|
||||
notContains: 'Nie zawiera',
|
||||
startsWith: 'Zaczyna się od',
|
||||
endsWith: 'Kończy się na',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Równe',
|
||||
notEqual: 'Różne od',
|
||||
greaterThan: 'Większe niż',
|
||||
greaterThanOrEqual: 'Większe niż lub równe',
|
||||
lessThan: 'Mniejsze niż',
|
||||
lessThanOrEqual: 'Mniejsze niż lub równe',
|
||||
between: 'Między',
|
||||
notBetween: 'Nie między',
|
||||
above: 'Powyżej',
|
||||
below: 'Poniżej',
|
||||
topN: 'Górne {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Równe',
|
||||
notEqual: 'Różne od',
|
||||
after: 'Po',
|
||||
afterOrEqual: 'Po lub równe',
|
||||
before: 'Przed',
|
||||
beforeOrEqual: 'Przed lub równe',
|
||||
between: 'Między',
|
||||
notBetween: 'Nie między',
|
||||
today: 'Dzisiaj',
|
||||
yesterday: 'Wczoraj',
|
||||
tomorrow: 'Jutro',
|
||||
thisWeek: 'Ten tydzień',
|
||||
lastWeek: 'Poprzedni tydzień',
|
||||
nextWeek: 'Następny tydzień',
|
||||
thisMonth: 'Ten miesiąc',
|
||||
lastMonth: 'Poprzedni miesiąc',
|
||||
nextMonth: 'Następny miesiąc',
|
||||
thisQuarter: 'Ten kwartał',
|
||||
lastQuarter: 'Poprzedni kwartał',
|
||||
nextQuarter: 'Następny kwartał',
|
||||
thisYear: 'Ten rok',
|
||||
nextYear: 'Następny rok',
|
||||
lastYear: 'Poprzedni rok',
|
||||
quarter: 'Według kwartału',
|
||||
month: 'Według miesiąca',
|
||||
q1: 'Pierwszy kwartał',
|
||||
q2: 'Drugi kwartał',
|
||||
q3: 'Trzeci kwartał',
|
||||
q4: 'Czwarty kwartał',
|
||||
m1: 'Styczeń',
|
||||
m2: 'Luty',
|
||||
m3: 'Marzec',
|
||||
m4: 'Kwiecień',
|
||||
m5: 'Maj',
|
||||
m6: 'Czerwiec',
|
||||
m7: 'Lipiec',
|
||||
m8: 'Sierpień',
|
||||
m9: 'Wrzesień',
|
||||
m10: 'Październik',
|
||||
m11: 'Listopad',
|
||||
m12: 'Grudzień',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filtr',
|
||||
'by-values': 'Według wartości',
|
||||
'by-colors': 'Według kolorów',
|
||||
'by-conditions': 'Według warunków',
|
||||
back: 'Wstecz',
|
||||
resize: 'Zmień rozmiar',
|
||||
'filter-by-cell-fill-color': 'Kolor wypełnienia komórki',
|
||||
'filter-by-cell-text-color': 'Kolor tekstu komórki',
|
||||
'filter-by-color-none': 'Ta kolumna zawiera tylko jeden kolor',
|
||||
'clear-filter': 'Wyczyść filtr',
|
||||
cancel: 'Anuluj',
|
||||
confirm: 'Potwierdź',
|
||||
'search-placeholder': 'Użyj spacji do rozdzielenia słów kluczowych',
|
||||
'input-values-placeholder': 'Wprowadź wartości',
|
||||
'select-all': 'Zaznacz wszystko',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/pt-BR.ts
Normal file
162
packages/sheets-table-ui/src/locale/pt-BR.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabela',
|
||||
selectRange: 'Selecionar Intervalo da Tabela',
|
||||
rename: 'Renomear Tabela',
|
||||
renamePlaceholder: 'Digite o nome da tabela',
|
||||
updateRange: 'Atualizar Intervalo da Tabela',
|
||||
tableRangeWithMergeError: 'O intervalo da tabela não pode sobrepor células mescladas',
|
||||
tableRangeWithOtherTableError: 'O intervalo da tabela não pode sobrepor outras tabelas',
|
||||
tableRangeSingleRowError: 'O intervalo da tabela não pode ser uma única linha',
|
||||
updateError: 'Não é possível definir o intervalo da tabela para uma área que não se sobreponha à original e não esteja na mesma linha',
|
||||
tableStyle: 'Estilo da Tabela',
|
||||
defaultStyle: 'Estilo Padrão',
|
||||
customStyle: 'Estilo Personalizado',
|
||||
customTooMore: 'O número de temas personalizados excede o limite máximo. Exclua alguns temas desnecessários e adicione-os novamente',
|
||||
setTheme: 'Definir Tema da Tabela',
|
||||
removeTable: 'Remover Tabela',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
header: 'Cabeçalho',
|
||||
footer: 'Rodapé',
|
||||
firstLine: 'Primeira Linha',
|
||||
secondLine: 'Segunda Linha',
|
||||
tableNameError: 'O nome da tabela não pode conter espaços, não pode começar com um número e não pode ser idêntico ao nome de uma tabela existente',
|
||||
columnMenu: {
|
||||
'insert-left': 'Inserir 1 coluna à esquerda',
|
||||
'insert-right': 'Inserir 1 coluna à direita',
|
||||
delete: 'Excluir coluna da tabela',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Crescente',
|
||||
'sort-desc': 'Decrescente',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Inserir Tabela',
|
||||
row: 'Inserir Linha na Tabela',
|
||||
col: 'Inserir Coluna na Tabela',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Remover Tabela',
|
||||
row: 'Remover Linha da Tabela',
|
||||
col: 'Remover Coluna da Tabela',
|
||||
},
|
||||
condition: {
|
||||
string: 'Texto',
|
||||
number: 'Número',
|
||||
date: 'Data',
|
||||
|
||||
empty: '(Vazio)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'Diferente de',
|
||||
contains: 'Contém',
|
||||
notContains: 'Não contém',
|
||||
startsWith: 'Começa com',
|
||||
endsWith: 'Termina com',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'Diferente de',
|
||||
greaterThan: 'Maior que',
|
||||
greaterThanOrEqual: 'Maior ou igual a',
|
||||
lessThan: 'Menor que',
|
||||
lessThanOrEqual: 'Menor ou igual a',
|
||||
between: 'Entre',
|
||||
notBetween: 'Não está entre',
|
||||
above: 'Acima',
|
||||
below: 'Abaixo',
|
||||
topN: 'Superior {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Igual a',
|
||||
notEqual: 'Diferente de',
|
||||
after: 'Depois',
|
||||
afterOrEqual: 'Depois ou igual a',
|
||||
before: 'Antes',
|
||||
beforeOrEqual: 'Antes ou igual a',
|
||||
between: 'Entre',
|
||||
notBetween: 'Não está entre',
|
||||
today: 'Hoje',
|
||||
yesterday: 'Ontem',
|
||||
tomorrow: 'Amanhã',
|
||||
thisWeek: 'Esta semana',
|
||||
lastWeek: 'Semana passada',
|
||||
nextWeek: 'Próxima semana',
|
||||
thisMonth: 'Este mês',
|
||||
lastMonth: 'Mês passado',
|
||||
nextMonth: 'Próximo mês',
|
||||
thisQuarter: 'Este trimestre',
|
||||
lastQuarter: 'Trimestre passado',
|
||||
nextQuarter: 'Próximo trimestre',
|
||||
thisYear: 'Este ano',
|
||||
nextYear: 'Próximo ano',
|
||||
lastYear: 'Ano passado',
|
||||
quarter: 'Por trimestre',
|
||||
month: 'Por mês',
|
||||
q1: 'Primeiro trimestre',
|
||||
q2: 'Segundo trimestre',
|
||||
q3: 'Terceiro trimestre',
|
||||
q4: 'Quarto trimestre',
|
||||
m1: 'Janeiro',
|
||||
m2: 'Fevereiro',
|
||||
m3: 'Março',
|
||||
m4: 'Abril',
|
||||
m5: 'Maio',
|
||||
m6: 'Junho',
|
||||
m7: 'Julho',
|
||||
m8: 'Agosto',
|
||||
m9: 'Setembro',
|
||||
m10: 'Outubro',
|
||||
m11: 'Novembro',
|
||||
m12: 'Dezembro',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filtro',
|
||||
'by-values': 'Por Valores',
|
||||
'by-colors': 'Por Cores',
|
||||
'by-conditions': 'Por Condições',
|
||||
back: 'Voltar',
|
||||
resize: 'Redimensionar',
|
||||
'filter-by-cell-fill-color': 'Cor de preenchimento da célula',
|
||||
'filter-by-cell-text-color': 'Cor do texto da célula',
|
||||
'filter-by-color-none': 'Esta coluna contém apenas uma cor',
|
||||
'clear-filter': 'Limpar Filtro',
|
||||
cancel: 'Cancelar',
|
||||
confirm: 'Confirmar',
|
||||
'search-placeholder': 'Use espaço para separar palavras-chave',
|
||||
'input-values-placeholder': 'Inserir Valores',
|
||||
'select-all': 'Selecionar Tudo',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
164
packages/sheets-table-ui/src/locale/ru-RU.ts
Normal file
164
packages/sheets-table-ui/src/locale/ru-RU.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Таблица',
|
||||
selectRange: 'Выбрать диапазон таблицы',
|
||||
rename: 'Переименовать таблицу',
|
||||
renamePlaceholder: 'Введите имя таблицы',
|
||||
updateRange: 'Обновить диапазон таблицы',
|
||||
tableRangeWithMergeError: 'Диапазон таблицы не может перекрываться с объединенными ячейками',
|
||||
tableRangeWithOtherTableError: 'Диапазон таблицы не может перекрываться с другими таблицами',
|
||||
tableRangeSingleRowError: 'Диапазон таблицы не может быть одной строкой',
|
||||
updateError: 'Невозможно установить диапазон таблицы в область, которая не перекрывается с исходной и не находится в той же строке',
|
||||
tableStyle: 'Стиль таблицы',
|
||||
defaultStyle: 'Стиль по умолчанию',
|
||||
customStyle: 'Пользовательский стиль',
|
||||
customTooMore: 'Количество пользовательских тем превышает максимальный лимит, пожалуйста, удалите некоторые ненужные темы и добавьте снова',
|
||||
setTheme: 'Установить тему таблицы',
|
||||
removeTable: 'Удалить таблицу',
|
||||
cancel: 'Отмена',
|
||||
confirm: 'Подтвердить',
|
||||
header: 'Заголовок',
|
||||
footer: 'Нижний колонтитул',
|
||||
firstLine: 'Первая строка',
|
||||
secondLine: 'Вторая строка',
|
||||
tableNameError: 'Имя таблицы не может содержать пробелы, не может начинаться с цифры и не может совпадать с именем существующей таблицы',
|
||||
columnMenu: {
|
||||
'insert-left': 'Вставить 1 столбец слева',
|
||||
'insert-right': 'Вставить 1 столбец справа',
|
||||
delete: 'Удалить столбец таблицы',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'По возрастанию',
|
||||
'sort-desc': 'По убыванию',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Вставить таблицу',
|
||||
row: 'Вставить строку таблицы',
|
||||
col: 'Вставить столбец таблицы',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Удалить таблицу',
|
||||
row: 'Удалить строку таблицы',
|
||||
col: 'Удалить столбец таблицы',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: 'Текст',
|
||||
number: 'Число',
|
||||
date: 'Дата',
|
||||
|
||||
empty: '(Пусто)',
|
||||
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Равно',
|
||||
notEqual: 'Не равно',
|
||||
contains: 'Содержит',
|
||||
notContains: 'Не содержит',
|
||||
startsWith: 'Начинается с',
|
||||
endsWith: 'Заканчивается на',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Равно',
|
||||
notEqual: 'Не равно',
|
||||
greaterThan: 'Больше',
|
||||
greaterThanOrEqual: 'Больше или равно',
|
||||
lessThan: 'Меньше',
|
||||
lessThanOrEqual: 'Меньше или равно',
|
||||
between: 'Между',
|
||||
notBetween: 'Не между',
|
||||
above: 'Больше',
|
||||
below: 'Меньше',
|
||||
topN: 'Топ {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Равно',
|
||||
notEqual: 'Не равно',
|
||||
after: 'После',
|
||||
afterOrEqual: 'После или равно',
|
||||
before: 'До',
|
||||
beforeOrEqual: 'До или равно',
|
||||
between: 'Между',
|
||||
notBetween: 'Не между',
|
||||
today: 'Сегодня',
|
||||
yesterday: 'Вчера',
|
||||
tomorrow: 'Завтра',
|
||||
thisWeek: 'На этой неделе',
|
||||
lastWeek: 'На прошлой неделе',
|
||||
nextWeek: 'На следующей неделе',
|
||||
thisMonth: 'В этом месяце',
|
||||
lastMonth: 'В прошлом месяце',
|
||||
nextMonth: 'В следующем месяце',
|
||||
thisQuarter: 'В этом квартале',
|
||||
lastQuarter: 'В прошлом квартале',
|
||||
nextQuarter: 'В следующем квартале',
|
||||
thisYear: 'В этом году',
|
||||
nextYear: 'В следующем году',
|
||||
lastYear: 'В прошлом году',
|
||||
quarter: 'По кварталам',
|
||||
month: 'По месяцам',
|
||||
q1: 'Первый квартал',
|
||||
q2: 'Второй квартал',
|
||||
q3: 'Третий квартал',
|
||||
q4: 'Четвертый квартал',
|
||||
m1: 'Январь',
|
||||
m2: 'Февраль',
|
||||
m3: 'Март',
|
||||
m4: 'Апрель',
|
||||
m5: 'Май',
|
||||
m6: 'Июнь',
|
||||
m7: 'Июль',
|
||||
m8: 'Август',
|
||||
m9: 'Сентябрь',
|
||||
m10: 'Октябрь',
|
||||
m11: 'Ноябрь',
|
||||
m12: 'Декабрь',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Фильтр',
|
||||
'by-values': 'По значениям',
|
||||
'by-colors': 'По цветам',
|
||||
'by-conditions': 'По условиям',
|
||||
back: 'Назад',
|
||||
resize: 'Изменить размер',
|
||||
'filter-by-cell-fill-color': 'Цвет заливки ячейки',
|
||||
'filter-by-cell-text-color': 'Цвет текста ячейки',
|
||||
'filter-by-color-none': 'Этот столбец содержит только один цвет',
|
||||
'clear-filter': 'Очистить фильтр',
|
||||
cancel: 'Отмена',
|
||||
confirm: 'Подтвердить',
|
||||
'search-placeholder': 'Используйте пробел для разделения ключевых слов',
|
||||
'input-values-placeholder': 'Введите значения',
|
||||
'select-all': 'Выбрать все',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
162
packages/sheets-table-ui/src/locale/sk-SK.ts
Normal file
162
packages/sheets-table-ui/src/locale/sk-SK.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Tabuľka',
|
||||
selectRange: 'Vyberte rozsah tabuľky',
|
||||
rename: 'Premenovať tabuľku',
|
||||
renamePlaceholder: 'Zadajte názov tabuľky',
|
||||
updateRange: 'Aktualizovať rozsah tabuľky',
|
||||
tableRangeWithMergeError: 'Rozsah tabuľky sa nemôže prekrývať so zlúčenými bunkami',
|
||||
tableRangeWithOtherTableError: 'Rozsah tabuľky sa nemôže prekrývať s inými tabuľkami',
|
||||
tableRangeSingleRowError: 'Rozsah tabuľky nemôže byť jeden riadok',
|
||||
updateError: 'Rozsah tabuľky nemožno nastaviť na oblasť, ktorá sa neprekrýva s pôvodnou a nie je v tom istom riadku',
|
||||
tableStyle: 'Štýl tabuľky',
|
||||
defaultStyle: 'Predvolený štýl',
|
||||
customStyle: 'Vlastný štýl',
|
||||
customTooMore: 'Počet vlastných tém presahuje maximálny limit, odstráňte niektoré nepotrebné témy a pridajte ich znova',
|
||||
setTheme: 'Nastaviť tému tabuľky',
|
||||
removeTable: 'Odstrániť tabuľku',
|
||||
cancel: 'Zrušiť',
|
||||
confirm: 'Potvrdiť',
|
||||
header: 'Hlavička',
|
||||
footer: 'Päta',
|
||||
firstLine: 'Prvý riadok',
|
||||
secondLine: 'Druhý riadok',
|
||||
tableNameError: 'Názov tabuľky nesmie obsahovať medzery, nesmie začínať číslom a nesmie byť zhodný s existujúcim názvom tabuľky',
|
||||
columnMenu: {
|
||||
'insert-left': 'Vložiť 1 stĺpec tabuľky vľavo',
|
||||
'insert-right': 'Vložiť 1 stĺpec tabuľky vpravo',
|
||||
delete: 'Odstrániť stĺpec tabuľky',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Vzostupne',
|
||||
'sort-desc': 'Zostupne',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Vložiť tabuľku',
|
||||
row: 'Vložiť riadok tabuľky',
|
||||
col: 'Vložiť stĺpec tabuľky',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Odstrániť tabuľku',
|
||||
row: 'Odstrániť riadok tabuľky',
|
||||
col: 'Odstrániť stĺpec tabuľky',
|
||||
},
|
||||
condition: {
|
||||
string: 'Text',
|
||||
number: 'Číslo',
|
||||
date: 'Dátum',
|
||||
|
||||
empty: '(Prázdne)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Rovné',
|
||||
notEqual: 'Nerovné',
|
||||
contains: 'Obsahuje',
|
||||
notContains: 'Neobsahuje',
|
||||
startsWith: 'Začína na',
|
||||
endsWith: 'Končí na',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Rovné',
|
||||
notEqual: 'Nerovné',
|
||||
greaterThan: 'Väčšie ako',
|
||||
greaterThanOrEqual: 'Väčšie alebo rovné',
|
||||
lessThan: 'Menšie ako',
|
||||
lessThanOrEqual: 'Menšie alebo rovné',
|
||||
between: 'Medzi',
|
||||
notBetween: 'Nie medzi',
|
||||
above: 'Nad',
|
||||
below: 'Pod',
|
||||
topN: 'Horných {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Rovné',
|
||||
notEqual: 'Nerovné',
|
||||
after: 'Po',
|
||||
afterOrEqual: 'Po alebo v',
|
||||
before: 'Pred',
|
||||
beforeOrEqual: 'Pred alebo v',
|
||||
between: 'Medzi',
|
||||
notBetween: 'Nie medzi',
|
||||
today: 'Dnes',
|
||||
yesterday: 'Včera',
|
||||
tomorrow: 'Zajtra',
|
||||
thisWeek: 'Tento týždeň',
|
||||
lastWeek: 'Minulý týždeň',
|
||||
nextWeek: 'Budúci týždeň',
|
||||
thisMonth: 'Tento mesiac',
|
||||
lastMonth: 'Minulý mesiac',
|
||||
nextMonth: 'Budúci mesiac',
|
||||
thisQuarter: 'Tento štvrťrok',
|
||||
lastQuarter: 'Minulý štvrťrok',
|
||||
nextQuarter: 'Budúci štvrťrok',
|
||||
thisYear: 'Tento rok',
|
||||
nextYear: 'Budúci rok',
|
||||
lastYear: 'Minulý rok',
|
||||
quarter: 'Podľa štvrťroka',
|
||||
month: 'Podľa mesiaca',
|
||||
q1: 'Prvý štvrťrok',
|
||||
q2: 'Druhý štvrťrok',
|
||||
q3: 'Tretí štvrťrok',
|
||||
q4: 'Štvrtý štvrťrok',
|
||||
m1: 'Január',
|
||||
m2: 'Február',
|
||||
m3: 'Marec',
|
||||
m4: 'Apríl',
|
||||
m5: 'Máj',
|
||||
m6: 'Jún',
|
||||
m7: 'Júl',
|
||||
m8: 'August',
|
||||
m9: 'September',
|
||||
m10: 'Október',
|
||||
m11: 'November',
|
||||
m12: 'December',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Filter',
|
||||
'by-values': 'Podľa hodnôt',
|
||||
'by-colors': 'Podľa farieb',
|
||||
'by-conditions': 'Podľa podmienok',
|
||||
back: 'Späť',
|
||||
resize: 'Zmeniť veľkosť',
|
||||
'filter-by-cell-fill-color': 'Farba výplne bunky',
|
||||
'filter-by-cell-text-color': 'Farba textu bunky',
|
||||
'filter-by-color-none': 'Tento stĺpec obsahuje iba jednu farbu',
|
||||
'clear-filter': 'Vymazať filter',
|
||||
cancel: 'Zrušiť',
|
||||
confirm: 'Potvrdiť',
|
||||
'search-placeholder': 'Použite medzeru na oddelenie kľúčových slov',
|
||||
'input-values-placeholder': 'Zadajte hodnoty',
|
||||
'select-all': 'Vybrať všetko',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
20
packages/sheets-table-ui/src/locale/types.ts
Normal file
20
packages/sheets-table-ui/src/locale/types.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { LocaleLeafKeys } from '@univerjs/core';
|
||||
import type enUS from './en-US';
|
||||
|
||||
export type LocaleKey = LocaleLeafKeys<typeof enUS>;
|
||||
164
packages/sheets-table-ui/src/locale/vi-VN.ts
Normal file
164
packages/sheets-table-ui/src/locale/vi-VN.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: 'Bảng',
|
||||
selectRange: 'Chọn phạm vi bảng',
|
||||
rename: 'Đổi tên bảng',
|
||||
renamePlaceholder: 'Nhập tên bảng',
|
||||
updateRange: 'Cập nhật phạm vi bảng',
|
||||
tableRangeWithMergeError: 'Phạm vi bảng không thể chồng lấp với các ô đã được hợp nhất',
|
||||
tableRangeWithOtherTableError: 'Phạm vi bảng không thể chồng lấp với các bảng khác',
|
||||
tableRangeSingleRowError: 'Phạm vi bảng không thể là một hàng đơn lẻ',
|
||||
updateError: 'Không thể đặt phạm vi bảng vào khu vực không chồng lấp với bản gốc và không nằm trên cùng một hàng',
|
||||
tableStyle: 'Kiểu bảng',
|
||||
defaultStyle: 'Kiểu mặc định',
|
||||
customStyle: 'Kiểu tùy chỉnh',
|
||||
customTooMore: 'Số lượng chủ đề tùy chỉnh vượt quá giới hạn tối đa, vui lòng xóa một số chủ đề không cần thiết và thêm lại',
|
||||
setTheme: 'Thiết lập chủ đề bảng',
|
||||
removeTable: 'Xóa bảng',
|
||||
cancel: 'Hủy',
|
||||
confirm: 'Xác nhận',
|
||||
header: 'Đầu trang',
|
||||
footer: 'Chân trang',
|
||||
firstLine: 'Dòng đầu tiên',
|
||||
secondLine: 'Dòng thứ hai',
|
||||
tableNameError: 'Tên bảng không thể chứa khoảng trắng, không được bắt đầu bằng số và không được trùng với tên bảng đã có',
|
||||
columnMenu: {
|
||||
'insert-left': 'Chèn 1 cột bảng bên trái',
|
||||
'insert-right': 'Chèn 1 cột bảng bên phải',
|
||||
delete: 'Xóa cột bảng',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': 'Tăng dần',
|
||||
'sort-desc': 'Giảm dần',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: 'Chèn bảng',
|
||||
row: 'Chèn hàng bảng',
|
||||
col: 'Chèn cột bảng',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: 'Xóa bảng',
|
||||
row: 'Xóa hàng bảng',
|
||||
col: 'Xóa cột bảng',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: 'Văn bản',
|
||||
number: 'Số',
|
||||
date: 'Ngày',
|
||||
|
||||
empty: '(Trống)',
|
||||
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: 'Bằng',
|
||||
notEqual: 'Không bằng',
|
||||
contains: 'Chứa',
|
||||
notContains: 'Không chứa',
|
||||
startsWith: 'Bắt đầu với',
|
||||
endsWith: 'Kết thúc với',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: 'Bằng',
|
||||
notEqual: 'Không bằng',
|
||||
greaterThan: 'Lớn hơn',
|
||||
greaterThanOrEqual: 'Lớn hơn hoặc bằng',
|
||||
lessThan: 'Nhỏ hơn',
|
||||
lessThanOrEqual: 'Nhỏ hơn hoặc bằng',
|
||||
between: 'Trong khoảng',
|
||||
notBetween: 'Không trong khoảng',
|
||||
above: 'Lớn hơn',
|
||||
below: 'Nhỏ hơn',
|
||||
topN: 'Trên cùng {0}',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: 'Bằng',
|
||||
notEqual: 'Không bằng',
|
||||
after: 'Sau',
|
||||
afterOrEqual: 'Sau hoặc bằng',
|
||||
before: 'Trước',
|
||||
beforeOrEqual: 'Trước hoặc bằng',
|
||||
between: 'Trong khoảng',
|
||||
notBetween: 'Không trong khoảng',
|
||||
today: 'Hôm nay',
|
||||
yesterday: 'Hôm qua',
|
||||
tomorrow: 'Ngày mai',
|
||||
thisWeek: 'Tuần này',
|
||||
lastWeek: 'Tuần trước',
|
||||
nextWeek: 'Tuần sau',
|
||||
thisMonth: 'Tháng này',
|
||||
lastMonth: 'Tháng trước',
|
||||
nextMonth: 'Tháng sau',
|
||||
thisQuarter: 'Quý này',
|
||||
lastQuarter: 'Quý trước',
|
||||
nextQuarter: 'Quý sau',
|
||||
thisYear: 'Năm nay',
|
||||
nextYear: 'Năm sau',
|
||||
lastYear: 'Năm trước',
|
||||
quarter: 'Theo quý',
|
||||
month: 'Theo tháng',
|
||||
q1: 'Quý 1',
|
||||
q2: 'Quý 2',
|
||||
q3: 'Quý 3',
|
||||
q4: 'Quý 4',
|
||||
m1: 'Tháng 1',
|
||||
m2: 'Tháng 2',
|
||||
m3: 'Tháng 3',
|
||||
m4: 'Tháng 4',
|
||||
m5: 'Tháng 5',
|
||||
m6: 'Tháng 6',
|
||||
m7: 'Tháng 7',
|
||||
m8: 'Tháng 8',
|
||||
m9: 'Tháng 9',
|
||||
m10: 'Tháng 10',
|
||||
m11: 'Tháng 11',
|
||||
m12: 'Tháng 12',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: 'Bộ lọc',
|
||||
'by-values': 'Theo giá trị',
|
||||
'by-colors': 'Theo màu',
|
||||
'by-conditions': 'Theo điều kiện',
|
||||
back: 'Quay lại',
|
||||
resize: 'Thay đổi kích thước',
|
||||
'filter-by-cell-fill-color': 'Màu nền ô',
|
||||
'filter-by-cell-text-color': 'Màu chữ ô',
|
||||
'filter-by-color-none': 'Cột này chỉ chứa một màu',
|
||||
'clear-filter': 'Xóa lọc',
|
||||
cancel: 'Hủy bỏ',
|
||||
confirm: 'Xác nhận',
|
||||
'search-placeholder': 'Sử dụng khoảng trắng để tách các từ khóa',
|
||||
'input-values-placeholder': 'Vui lòng nhập',
|
||||
'select-all': 'Chọn tất cả',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
165
packages/sheets-table-ui/src/locale/zh-CN.ts
Normal file
165
packages/sheets-table-ui/src/locale/zh-CN.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: '表格',
|
||||
selectRange: '选择表格范围',
|
||||
rename: '重命名表格',
|
||||
renamePlaceholder: '输入表格名称',
|
||||
updateRange: '更新表格范围',
|
||||
tableRangeWithMergeError: '表格范围不能与合并单元格重叠',
|
||||
tableRangeWithOtherTableError: '表格范围不能与其他表格重叠',
|
||||
tableRangeSingleRowError: '表格范围不能是单独的一行',
|
||||
updateError: '不能将表格范围设置到和原先不重合且不在同一行的范围',
|
||||
tableStyle: '表格样式',
|
||||
defaultStyle: '默认样式',
|
||||
customStyle: '自定义样式',
|
||||
customTooMore: '自定义主题数量超出最大限制,请删除一些无用的主题后再次添加',
|
||||
setTheme: '设置表格主题',
|
||||
removeTable: '删除表格',
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
header: '页眉',
|
||||
footer: '页脚',
|
||||
firstLine: '第一行',
|
||||
secondLine: '第二行',
|
||||
tableNameError: '表格名称不能包含空格, 不能以数字开头,不能和已有表格名称重复',
|
||||
columnMenu: {
|
||||
'insert-left': '向左插入 1 个表格列',
|
||||
'insert-right': '向右插入 1 个表格列',
|
||||
delete: '删除表格列',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': '升序',
|
||||
'sort-desc': '降序',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: '表格插入',
|
||||
row: '插入表格行',
|
||||
col: '插入表格列',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: '表格删除',
|
||||
row: '删除表格行',
|
||||
col: '删除表格列',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: '文本',
|
||||
number: '数字',
|
||||
date: '日期',
|
||||
|
||||
empty: '(空白)',
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: '等于',
|
||||
notEqual: '不等于',
|
||||
contains: '包含',
|
||||
notContains: '不包含',
|
||||
startsWith: '开始于',
|
||||
endsWith: '结束于',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: '等于',
|
||||
notEqual: '不等于',
|
||||
greaterThan: '大于',
|
||||
greaterThanOrEqual: '大于等于',
|
||||
lessThan: '小于',
|
||||
lessThanOrEqual: '小于等于',
|
||||
between: '介于',
|
||||
notBetween: '不介于',
|
||||
above: '大于',
|
||||
below: '小于',
|
||||
topN: '前{0}个',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: '等于',
|
||||
notEqual: '不等于',
|
||||
after: '晚于',
|
||||
afterOrEqual: '晚于等于',
|
||||
before: '早于',
|
||||
beforeOrEqual: '早于等于',
|
||||
between: '介于',
|
||||
notBetween: '不介于',
|
||||
today: '今天',
|
||||
yesterday: '昨天',
|
||||
tomorrow: '明天',
|
||||
thisWeek: '本周',
|
||||
lastWeek: '上周',
|
||||
nextWeek: '下周',
|
||||
thisMonth: '本月',
|
||||
lastMonth: '上月',
|
||||
nextMonth: '下月',
|
||||
thisQuarter: '本季度',
|
||||
lastQuarter: '上季度',
|
||||
nextQuarter: '下季度',
|
||||
thisYear: '今年',
|
||||
nextYear: '明年',
|
||||
lastYear: '去年',
|
||||
quarter: '按季度',
|
||||
month: '按月份',
|
||||
|
||||
q1: '第一季度',
|
||||
q2: '第二季度',
|
||||
q3: '第三季度',
|
||||
q4: '第四季度',
|
||||
|
||||
m1: '一月',
|
||||
m2: '二月',
|
||||
m3: '三月',
|
||||
m4: '四月',
|
||||
m5: '五月',
|
||||
m6: '六月',
|
||||
m7: '七月',
|
||||
m8: '八月',
|
||||
m9: '九月',
|
||||
m10: '十月',
|
||||
m11: '十一月',
|
||||
m12: '十二月',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: '筛选',
|
||||
'by-values': '按值',
|
||||
'by-colors': '按颜色',
|
||||
'by-conditions': '按条件',
|
||||
back: '返回',
|
||||
resize: '调整大小',
|
||||
'filter-by-cell-fill-color': '单元格填充颜色',
|
||||
'filter-by-cell-text-color': '单元格文本颜色',
|
||||
'filter-by-color-none': '此列仅包含一种颜色',
|
||||
'clear-filter': '清除筛选',
|
||||
cancel: '取消',
|
||||
confirm: '确认',
|
||||
'search-placeholder': '使用空格分隔关键字',
|
||||
'input-values-placeholder': '请输入',
|
||||
'select-all': '全选',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
164
packages/sheets-table-ui/src/locale/zh-HK.ts
Normal file
164
packages/sheets-table-ui/src/locale/zh-HK.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: '表格',
|
||||
selectRange: '選擇表格範圍',
|
||||
rename: '重新命名表格',
|
||||
renamePlaceholder: '輸入表格名稱',
|
||||
updateRange: '更新表格範圍',
|
||||
tableRangeWithMergeError: '表格範圍不能與合併儲存格重疊',
|
||||
tableRangeWithOtherTableError: '表格範圍不能與其他表格重疊',
|
||||
tableRangeSingleRowError: '表格範圍不能只有一列',
|
||||
updateError: '不能將表格範圍設定為與原範圍不重疊且不在同一列的範圍',
|
||||
tableStyle: '表格樣式',
|
||||
defaultStyle: '預設樣式',
|
||||
customStyle: '自訂樣式',
|
||||
customTooMore: '自訂主題數量超出上限,請刪除一些不需要的主題後再新增',
|
||||
setTheme: '設定表格主題',
|
||||
removeTable: '刪除表格',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
header: '頁首',
|
||||
footer: '頁尾',
|
||||
firstLine: '第一列',
|
||||
secondLine: '第二列',
|
||||
tableNameError: '表格名稱不能包含空格、以數字開頭或與現有表格名稱重複',
|
||||
columnMenu: {
|
||||
'insert-left': '向左插入 1 個表格欄',
|
||||
'insert-right': '向右插入 1 個表格欄',
|
||||
delete: '刪除表格欄',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': '升序',
|
||||
'sort-desc': '降序',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: '表格插入',
|
||||
row: '插入表格列',
|
||||
col: '插入表格欄',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: '表格刪除',
|
||||
row: '刪除表格列',
|
||||
col: '刪除表格欄',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: '文字',
|
||||
number: '數字',
|
||||
date: '日期',
|
||||
|
||||
empty: '(空白)',
|
||||
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: '等於',
|
||||
notEqual: '不等於',
|
||||
contains: '包含',
|
||||
notContains: '不包含',
|
||||
startsWith: '開始於',
|
||||
endsWith: '結束於',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: '等於',
|
||||
notEqual: '不等於',
|
||||
greaterThan: '大於',
|
||||
greaterThanOrEqual: '大於等於',
|
||||
lessThan: '小於',
|
||||
lessThanOrEqual: '小於等於',
|
||||
between: '介於',
|
||||
notBetween: '不介於',
|
||||
above: '大於',
|
||||
below: '小於',
|
||||
topN: '前{0}個',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: '等於',
|
||||
notEqual: '不等於',
|
||||
after: '晚於',
|
||||
afterOrEqual: '晚於等於',
|
||||
before: '早於',
|
||||
beforeOrEqual: '早於等於',
|
||||
between: '介於',
|
||||
notBetween: '不介於',
|
||||
today: '今天',
|
||||
yesterday: '昨天',
|
||||
tomorrow: '明天',
|
||||
thisWeek: '本週',
|
||||
lastWeek: '上週',
|
||||
nextWeek: '下週',
|
||||
thisMonth: '本月',
|
||||
lastMonth: '上月',
|
||||
nextMonth: '下月',
|
||||
thisQuarter: '本季度',
|
||||
lastQuarter: '上季度',
|
||||
nextQuarter: '下季度',
|
||||
thisYear: '今年',
|
||||
nextYear: '明年',
|
||||
lastYear: '去年',
|
||||
quarter: '按季度',
|
||||
month: '按月份',
|
||||
q1: '第一季度',
|
||||
q2: '第二季度',
|
||||
q3: '第三季度',
|
||||
q4: '第四季度',
|
||||
m1: '一月',
|
||||
m2: '二月',
|
||||
m3: '三月',
|
||||
m4: '四月',
|
||||
m5: '五月',
|
||||
m6: '六月',
|
||||
m7: '七月',
|
||||
m8: '八月',
|
||||
m9: '九月',
|
||||
m10: '十月',
|
||||
m11: '十一月',
|
||||
m12: '十二月',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: '篩選',
|
||||
'by-values': '按值',
|
||||
'by-colors': '按顏色',
|
||||
'by-conditions': '按條件',
|
||||
back: '返回',
|
||||
resize: '調整大小',
|
||||
'filter-by-cell-fill-color': '儲存格填滿顏色',
|
||||
'filter-by-cell-text-color': '儲存格文字顏色',
|
||||
'filter-by-color-none': '此欄僅包含一種顏色',
|
||||
'clear-filter': '清除篩選',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
'search-placeholder': '使用空格分隔關鍵字',
|
||||
'input-values-placeholder': '請輸入',
|
||||
'select-all': '全選',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
164
packages/sheets-table-ui/src/locale/zh-TW.ts
Normal file
164
packages/sheets-table-ui/src/locale/zh-TW.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
/**
|
||||
* 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-table-ui': {
|
||||
title: '表格',
|
||||
selectRange: '選擇表格範圍',
|
||||
rename: '重新命名表格',
|
||||
renamePlaceholder: '輸入表格名稱',
|
||||
updateRange: '更新表格範圍',
|
||||
tableRangeWithMergeError: '表格範圍不能與合併儲存格重疊',
|
||||
tableRangeWithOtherTableError: '表格範圍不能與其他表格重疊',
|
||||
tableRangeSingleRowError: '表格範圍不能只有一列',
|
||||
updateError: '不能將表格範圍設定為與原範圍不重疊且不在同一列的範圍',
|
||||
tableStyle: '表格樣式',
|
||||
defaultStyle: '預設樣式',
|
||||
customStyle: '自訂樣式',
|
||||
customTooMore: '自訂主題數量超出上限,請刪除一些不需要的主題後再新增',
|
||||
setTheme: '設定表格主題',
|
||||
removeTable: '刪除表格',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
header: '頁首',
|
||||
footer: '頁尾',
|
||||
firstLine: '第一列',
|
||||
secondLine: '第二列',
|
||||
tableNameError: '表格名稱不能包含空格、以數字開頭或與現有表格名稱重複',
|
||||
columnMenu: {
|
||||
'insert-left': '向左插入 1 個表格欄',
|
||||
'insert-right': '向右插入 1 個表格欄',
|
||||
delete: '刪除表格欄',
|
||||
},
|
||||
|
||||
sort: {
|
||||
'sort-asc': '升序',
|
||||
'sort-desc': '降序',
|
||||
},
|
||||
|
||||
insert: {
|
||||
main: '表格插入',
|
||||
row: '插入表格列',
|
||||
col: '插入表格欄',
|
||||
},
|
||||
|
||||
remove: {
|
||||
main: '表格刪除',
|
||||
row: '刪除表格列',
|
||||
col: '刪除表格欄',
|
||||
},
|
||||
|
||||
condition: {
|
||||
string: '文字',
|
||||
number: '數字',
|
||||
date: '日期',
|
||||
|
||||
empty: '(空白)',
|
||||
|
||||
},
|
||||
string: {
|
||||
compare: {
|
||||
equal: '等於',
|
||||
notEqual: '不等於',
|
||||
contains: '包含',
|
||||
notContains: '不包含',
|
||||
startsWith: '開始於',
|
||||
endsWith: '結束於',
|
||||
},
|
||||
},
|
||||
number: {
|
||||
compare: {
|
||||
equal: '等於',
|
||||
notEqual: '不等於',
|
||||
greaterThan: '大於',
|
||||
greaterThanOrEqual: '大於等於',
|
||||
lessThan: '小於',
|
||||
lessThanOrEqual: '小於等於',
|
||||
between: '介於',
|
||||
notBetween: '不介於',
|
||||
above: '大於',
|
||||
below: '小於',
|
||||
topN: '前{0}個',
|
||||
},
|
||||
},
|
||||
date: {
|
||||
compare: {
|
||||
equal: '等於',
|
||||
notEqual: '不等於',
|
||||
after: '晚於',
|
||||
afterOrEqual: '晚於等於',
|
||||
before: '早於',
|
||||
beforeOrEqual: '早於等於',
|
||||
between: '介於',
|
||||
notBetween: '不介於',
|
||||
today: '今天',
|
||||
yesterday: '昨天',
|
||||
tomorrow: '明天',
|
||||
thisWeek: '本週',
|
||||
lastWeek: '上週',
|
||||
nextWeek: '下週',
|
||||
thisMonth: '本月',
|
||||
lastMonth: '上月',
|
||||
nextMonth: '下月',
|
||||
thisQuarter: '本季度',
|
||||
lastQuarter: '上季度',
|
||||
nextQuarter: '下季度',
|
||||
thisYear: '今年',
|
||||
nextYear: '明年',
|
||||
lastYear: '去年',
|
||||
quarter: '按季度',
|
||||
month: '按月份',
|
||||
q1: '第一季度',
|
||||
q2: '第二季度',
|
||||
q3: '第三季度',
|
||||
q4: '第四季度',
|
||||
m1: '一月',
|
||||
m2: '二月',
|
||||
m3: '三月',
|
||||
m4: '四月',
|
||||
m5: '五月',
|
||||
m6: '六月',
|
||||
m7: '七月',
|
||||
m8: '八月',
|
||||
m9: '九月',
|
||||
m10: '十月',
|
||||
m11: '十一月',
|
||||
m12: '十二月',
|
||||
},
|
||||
},
|
||||
filter: {
|
||||
title: '篩選',
|
||||
'by-values': '按值',
|
||||
'by-colors': '按顏色',
|
||||
'by-conditions': '按條件',
|
||||
back: '返回',
|
||||
resize: '調整大小',
|
||||
'filter-by-cell-fill-color': '儲存格填滿顏色',
|
||||
'filter-by-cell-text-color': '儲存格文字顏色',
|
||||
'filter-by-color-none': '此欄僅包含一種顏色',
|
||||
'clear-filter': '清除篩選',
|
||||
cancel: '取消',
|
||||
confirm: '確認',
|
||||
'search-placeholder': '使用空格分隔關鍵字',
|
||||
'input-values-placeholder': '請輸入',
|
||||
'select-all': '全選',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
175
packages/sheets-table-ui/src/menu/menu.ts
Normal file
175
packages/sheets-table-ui/src/menu/menu.ts
Normal file
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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, Workbook } from '@univerjs/core';
|
||||
import type { IMenuItem, IMenuSelectorItem } from '@univerjs/ui';
|
||||
import type { Observable } from 'rxjs';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import {
|
||||
SheetsTableController,
|
||||
SheetTableInsertColCommand,
|
||||
SheetTableInsertRowCommand,
|
||||
SheetTableRemoveColCommand,
|
||||
SheetTableRemoveRowCommand,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { getCurrentRangeDisable$ } from '@univerjs/sheets-ui';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
import { of, startWith, switchMap } from 'rxjs';
|
||||
import { OpenTableSelectorOperation } from '../commands/operations/open-table-selector.operation';
|
||||
|
||||
export const SHEET_TABLE_CONTEXT_INSERT_MENU_ID = 'sheet.table.context-insert_menu-id';
|
||||
export const SHEET_TABLE_CONTEXT_REMOVE_MENU_ID = 'sheet.table.context-remove_menu-id';
|
||||
|
||||
export function sheetTableToolbarInsertMenuFactory(accessor: IAccessor): IMenuItem<LocaleKey> {
|
||||
return {
|
||||
id: OpenTableSelectorOperation.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
icon: 'TableIcon',
|
||||
tooltip: 'sheets-table-ui.title',
|
||||
title: 'sheets-table-ui.title',
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
disabled$: getCurrentRangeDisable$(accessor, {}, true),
|
||||
};
|
||||
}
|
||||
|
||||
export function SheetTableInsertContextMenuFactory(accessor: IAccessor): IMenuSelectorItem<LocaleKey> {
|
||||
return {
|
||||
id: SHEET_TABLE_CONTEXT_INSERT_MENU_ID,
|
||||
type: MenuItemType.SUBITEMS,
|
||||
icon: 'InsertDoubleIcon',
|
||||
title: 'sheets-table-ui.insert.main',
|
||||
hidden$: getSheetTableRowColOperationHidden$(accessor),
|
||||
};
|
||||
}
|
||||
|
||||
export function SheetTableRemoveContextMenuFactory(accessor: IAccessor): IMenuSelectorItem<LocaleKey> {
|
||||
return {
|
||||
id: SHEET_TABLE_CONTEXT_REMOVE_MENU_ID,
|
||||
type: MenuItemType.SUBITEMS,
|
||||
icon: 'ReduceDoubleIcon',
|
||||
title: 'sheets-table-ui.remove.main',
|
||||
hidden$: getSheetTableRowColOperationHidden$(accessor),
|
||||
};
|
||||
}
|
||||
|
||||
export function SheetTableInsertRowMenuFactory(accessor: IAccessor) {
|
||||
return {
|
||||
id: SheetTableInsertRowCommand.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
title: 'sheets-table-ui.insert.row',
|
||||
hidden$: getSheetTableHeaderOperationHidden$(accessor),
|
||||
};
|
||||
}
|
||||
|
||||
export function SheetTableInsertColMenuFactory() {
|
||||
return {
|
||||
id: SheetTableInsertColCommand.id,
|
||||
title: 'sheets-table-ui.insert.col',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
}
|
||||
|
||||
export function SheetTableRemoveRowMenuFactory(accessor: IAccessor) {
|
||||
return {
|
||||
id: SheetTableRemoveRowCommand.id,
|
||||
type: MenuItemType.BUTTON,
|
||||
title: 'sheets-table-ui.remove.row',
|
||||
hidden$: getSheetTableHeaderOperationHidden$(accessor),
|
||||
};
|
||||
}
|
||||
|
||||
export function SheetTableRemoveColMenuFactory() {
|
||||
return {
|
||||
id: SheetTableRemoveColCommand.id,
|
||||
title: 'sheets-table-ui.remove.col',
|
||||
type: MenuItemType.BUTTON,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSheetTableRowColOperationHidden$(accessor: IAccessor): Observable<boolean> {
|
||||
const sheetsSelectionsService = accessor.get(SheetsSelectionsService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const workbook$ = univerInstanceService.getCurrentTypeOfUnit$<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
|
||||
return workbook$.pipe(
|
||||
switchMap((workbook) => {
|
||||
if (!workbook) return of(true);
|
||||
return workbook.activeSheet$.pipe(
|
||||
switchMap((sheet) => {
|
||||
if (!sheet) return of(true);
|
||||
return sheetsSelectionsService.selectionMoveEnd$.pipe(
|
||||
startWith(sheetsSelectionsService.getCurrentSelections()),
|
||||
switchMap((selections) => {
|
||||
if (!selections.length || selections.length > 1) return of(true);
|
||||
const selection = selections[0];
|
||||
const range = selection.range;
|
||||
const sheetsTableController = accessor.get(SheetsTableController);
|
||||
|
||||
const isInTable = sheetsTableController.getContainerTableWithRange(
|
||||
workbook.getUnitId(),
|
||||
sheet.getSheetId(),
|
||||
range
|
||||
);
|
||||
return of(!isInTable);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export function getSheetTableHeaderOperationHidden$(accessor: IAccessor): Observable<boolean> {
|
||||
const sheetsSelectionsService = accessor.get(SheetsSelectionsService);
|
||||
const univerInstanceService = accessor.get(IUniverInstanceService);
|
||||
const workbook$ = univerInstanceService.getCurrentTypeOfUnit$<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
|
||||
return workbook$.pipe(
|
||||
switchMap((workbook) => {
|
||||
if (!workbook) return of(true);
|
||||
return workbook.activeSheet$.pipe(
|
||||
switchMap((sheet) => {
|
||||
if (!sheet) return of(true);
|
||||
return sheetsSelectionsService.selectionMoveEnd$.pipe(
|
||||
startWith(sheetsSelectionsService.getCurrentSelections()),
|
||||
switchMap((selections) => {
|
||||
if (!selections.length || selections.length > 1) return of(true);
|
||||
const selection = selections[0];
|
||||
const range = selection.range;
|
||||
const sheetsTableController = accessor.get(SheetsTableController);
|
||||
|
||||
const isInTable = sheetsTableController.getContainerTableWithRange(
|
||||
workbook.getUnitId(),
|
||||
sheet.getSheetId(),
|
||||
range
|
||||
);
|
||||
if (!isInTable) {
|
||||
return of(true);
|
||||
}
|
||||
const tableRange = isInTable.getRange();
|
||||
if (range.startRow === tableRange.startRow) {
|
||||
return of(true);
|
||||
}
|
||||
return of(false);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
59
packages/sheets-table-ui/src/menu/schema.ts
Normal file
59
packages/sheets-table-ui/src/menu/schema.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { MenuSchemaType } from '@univerjs/ui';
|
||||
import { SheetTableInsertColCommand, SheetTableInsertRowCommand, SheetTableRemoveColCommand, SheetTableRemoveRowCommand } from '@univerjs/sheets-table';
|
||||
import { ContextMenuGroup, ContextMenuPosition, RibbonDataGroup } from '@univerjs/ui';
|
||||
import { OpenTableSelectorOperation } from '../commands/operations/open-table-selector.operation';
|
||||
import { SHEET_TABLE_CONTEXT_INSERT_MENU_ID, SHEET_TABLE_CONTEXT_REMOVE_MENU_ID, SheetTableInsertColMenuFactory, SheetTableInsertContextMenuFactory, SheetTableInsertRowMenuFactory, SheetTableRemoveColMenuFactory, SheetTableRemoveContextMenuFactory, SheetTableRemoveRowMenuFactory, sheetTableToolbarInsertMenuFactory } from './menu';
|
||||
|
||||
export const menuSchema: MenuSchemaType = {
|
||||
[RibbonDataGroup.ORGANIZATION]: {
|
||||
[OpenTableSelectorOperation.id]: {
|
||||
order: 0,
|
||||
gridLayout: { row: 1, column: 1 },
|
||||
menuItemFactory: sheetTableToolbarInsertMenuFactory,
|
||||
},
|
||||
},
|
||||
[ContextMenuPosition.MAIN_AREA]: {
|
||||
[ContextMenuGroup.LAYOUT]: {
|
||||
[SHEET_TABLE_CONTEXT_INSERT_MENU_ID]: {
|
||||
order: 5,
|
||||
menuItemFactory: SheetTableInsertContextMenuFactory,
|
||||
[SheetTableInsertRowCommand.id]: {
|
||||
order: 1,
|
||||
menuItemFactory: SheetTableInsertRowMenuFactory,
|
||||
},
|
||||
[SheetTableInsertColCommand.id]: {
|
||||
order: 2,
|
||||
menuItemFactory: SheetTableInsertColMenuFactory,
|
||||
},
|
||||
},
|
||||
[SHEET_TABLE_CONTEXT_REMOVE_MENU_ID]: {
|
||||
order: 6,
|
||||
menuItemFactory: SheetTableRemoveContextMenuFactory,
|
||||
[SheetTableRemoveRowCommand.id]: {
|
||||
order: 1,
|
||||
menuItemFactory: SheetTableRemoveRowMenuFactory,
|
||||
},
|
||||
[SheetTableRemoveColCommand.id]: {
|
||||
order: 2,
|
||||
menuItemFactory: SheetTableRemoveColMenuFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* 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 { IMenuManagerService } from '@univerjs/ui';
|
||||
import { menuSchema } from './schema';
|
||||
|
||||
export class SheetTableMenuController extends Disposable {
|
||||
constructor(
|
||||
@Inject(IMenuManagerService) private _menuManagerService: IMenuManagerService
|
||||
) {
|
||||
super();
|
||||
this._initMenu();
|
||||
}
|
||||
|
||||
private _initMenu() {
|
||||
this._menuManagerService.mergeMenu(menuSchema);
|
||||
}
|
||||
}
|
||||
134
packages/sheets-table-ui/src/mobile-plugin.ts
Normal file
134
packages/sheets-table-ui/src/mobile-plugin.ts
Normal file
@@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
import type { IUniverSheetsTableUIConfig } from './config/config';
|
||||
import {
|
||||
DependentOn,
|
||||
ICommandService,
|
||||
IConfigService,
|
||||
Inject,
|
||||
Injector,
|
||||
merge,
|
||||
Plugin,
|
||||
registerDependencies,
|
||||
touchDependencies,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsTablePlugin } from '@univerjs/sheets-table';
|
||||
import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui';
|
||||
import { UniverMobileUIPlugin } from '@univerjs/ui';
|
||||
import pkg from '../package.json';
|
||||
import { OpenTableFilterPanelMobileOperation } from './commands/operations/mobile/open-table-filter-dialog.operation';
|
||||
import { OpenTableSelectorOperation } from './commands/operations/open-table-selector.operation';
|
||||
import { defaultPluginConfig, SHEETS_TABLE_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { PLUGIN_NAME } from './const';
|
||||
import { MobileComponentsController } from './controllers/mobile/components.controller';
|
||||
import { SheetsTableMobileComponentController } from './controllers/mobile/sheet-table-component.controller';
|
||||
import { SheetTableControlsMobileRenderController } from './controllers/mobile/sheet-table-controls-render.controller';
|
||||
import { SheetsTableFilterButtonRenderController } from './controllers/sheet-table-filter-button-render.controller';
|
||||
import { SheetsTableRenderController } from './controllers/sheet-table-render.controller';
|
||||
import { SheetTableSelectionController } from './controllers/sheet-table-selection.controller';
|
||||
import { SheetTableThemeUIController } from './controllers/sheet-table-theme-ui.controller';
|
||||
import { SheetTableMenuController } from './menu/sheet-table-menu.controller';
|
||||
import { SheetsTableUiService } from './services/sheets-table-ui.service';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverSheetsTablePlugin,
|
||||
UniverSheetsMobileUIPlugin,
|
||||
UniverMobileUIPlugin
|
||||
)
|
||||
export class UniverSheetsTableMobileUIPlugin extends Plugin {
|
||||
static override pluginName = PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsTableUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override readonly _injector: Injector,
|
||||
@Inject(ICommandService) private readonly _commandService: ICommandService,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { menu, ...rest } = merge({}, defaultPluginConfig, this._config);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
this._configService.setConfig(SHEETS_TABLE_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
|
||||
this._initRegisterCommand();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.add([MobileComponentsController]);
|
||||
this._injector.get(MobileComponentsController);
|
||||
|
||||
registerDependencies(this._injector, [
|
||||
[SheetsTableMobileComponentController],
|
||||
[SheetsTableUiService],
|
||||
[SheetTableMenuController],
|
||||
[SheetTableThemeUIController],
|
||||
[SheetTableSelectionController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsTableMobileComponentController],
|
||||
[SheetsTableUiService],
|
||||
[SheetTableMenuController],
|
||||
[SheetTableThemeUIController],
|
||||
[SheetTableSelectionController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
this._registerRenderModules();
|
||||
}
|
||||
|
||||
private _registerRenderModules(): void {
|
||||
const renderDependencies: Dependency[] = [];
|
||||
if (this._config.hideAnchor !== true) {
|
||||
renderDependencies.push([
|
||||
SheetTableControlsMobileRenderController,
|
||||
]);
|
||||
}
|
||||
renderDependencies.push(
|
||||
[SheetsTableFilterButtonRenderController],
|
||||
[SheetsTableRenderController]
|
||||
);
|
||||
|
||||
renderDependencies.forEach((dependency) => {
|
||||
this.disposeWithMe(
|
||||
this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, dependency)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private _initRegisterCommand(): void {
|
||||
[
|
||||
OpenTableFilterPanelMobileOperation,
|
||||
OpenTableSelectorOperation,
|
||||
].forEach((command) => this.disposeWithMe(this._commandService.registerCommand(command)));
|
||||
}
|
||||
}
|
||||
120
packages/sheets-table-ui/src/plugin.ts
Normal file
120
packages/sheets-table-ui/src/plugin.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* 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 { IUniverSheetsTableUIConfig } from './config/config';
|
||||
import { DependentOn, ICommandService, IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies, UniverInstanceType } from '@univerjs/core';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsTablePlugin } from '@univerjs/sheets-table';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
import pkg from '../package.json';
|
||||
import { OpenTableFilterPanelOperation } from './commands/operations/open-table-filter-dialog.opration';
|
||||
import { OpenTableSelectorOperation } from './commands/operations/open-table-selector.operation';
|
||||
import { defaultPluginConfig, SHEETS_TABLE_UI_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { PLUGIN_NAME } from './const';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { SheetsTableComponentController } from './controllers/sheet-table-component.controller';
|
||||
import { SheetTableControlsRenderController } from './controllers/sheet-table-controls-render.controller';
|
||||
import { SheetsTableFilterButtonRenderController } from './controllers/sheet-table-filter-button-render.controller';
|
||||
import { SheetsTableRenderController } from './controllers/sheet-table-render.controller';
|
||||
import { SheetTableSelectionController } from './controllers/sheet-table-selection.controller';
|
||||
import { SheetTableThemeUIController } from './controllers/sheet-table-theme-ui.controller';
|
||||
import { SheetTableMenuController } from './menu/sheet-table-menu.controller';
|
||||
import { SheetsTableUiService } from './services/sheets-table-ui.service';
|
||||
|
||||
@DependentOn(
|
||||
UniverRenderEnginePlugin,
|
||||
UniverSheetsPlugin,
|
||||
UniverSheetsTablePlugin,
|
||||
UniverSheetsUIPlugin
|
||||
)
|
||||
export class UniverSheetsTableUIPlugin extends Plugin {
|
||||
static override pluginName = PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsTableUIConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected override _injector: Injector,
|
||||
@Inject(ICommandService) private _commandService: ICommandService,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
// Manage the plugin configuration.
|
||||
const { menu, ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
this._configService.setConfig(SHEETS_TABLE_UI_PLUGIN_CONFIG_KEY, rest);
|
||||
|
||||
this._initRegisterCommand();
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
this._injector.add([ComponentsController]);
|
||||
this._injector.get(ComponentsController);
|
||||
registerDependencies(this._injector, [
|
||||
[SheetsTableComponentController],
|
||||
[SheetsTableUiService],
|
||||
[SheetTableMenuController],
|
||||
[SheetTableThemeUIController],
|
||||
[SheetTableSelectionController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[SheetsTableComponentController],
|
||||
[SheetsTableUiService],
|
||||
[SheetTableMenuController],
|
||||
[SheetTableThemeUIController],
|
||||
[SheetTableSelectionController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
this._registerRenderModules();
|
||||
}
|
||||
|
||||
private _registerRenderModules(): void {
|
||||
const renderDependencies: Dependency[] = [];
|
||||
if (this._config.hideAnchor !== true) {
|
||||
renderDependencies.push([SheetTableControlsRenderController]);
|
||||
}
|
||||
renderDependencies.push(
|
||||
[SheetsTableFilterButtonRenderController],
|
||||
[SheetsTableRenderController]
|
||||
);
|
||||
|
||||
renderDependencies.forEach((m) => {
|
||||
this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m));
|
||||
});
|
||||
}
|
||||
|
||||
private _initRegisterCommand() {
|
||||
[
|
||||
OpenTableFilterPanelOperation,
|
||||
OpenTableSelectorOperation,
|
||||
].forEach((m) => this._commandService.registerCommand(m));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* 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 {
|
||||
ICommandService,
|
||||
Inject,
|
||||
Injector,
|
||||
LocaleService,
|
||||
LocaleType,
|
||||
Plugin,
|
||||
ThemeColorType,
|
||||
Univer,
|
||||
UniverInstanceType,
|
||||
} from '@univerjs/core';
|
||||
import { SetRangeValuesMutation } from '@univerjs/sheets';
|
||||
import {
|
||||
SetSheetTableFilterCommand,
|
||||
SetSheetTableFilterMutation,
|
||||
SheetTableService,
|
||||
TABLE_FILTER_EMPTY_VALUE,
|
||||
TableColumnFilterTypeEnum,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { FilterByEnum } from '../../types';
|
||||
import { SheetsTableUiService } from '../sheets-table-ui.service';
|
||||
|
||||
interface ITestBed {
|
||||
univer: Univer;
|
||||
get: Injector['get'];
|
||||
workbook: Workbook;
|
||||
}
|
||||
|
||||
function createWorkbookData(): IWorkbookData {
|
||||
return {
|
||||
id: 'test',
|
||||
appVersion: '3.0.0-alpha',
|
||||
locale: LocaleType.EN_US,
|
||||
name: 'test',
|
||||
sheetOrder: ['sheet1'],
|
||||
sheets: {
|
||||
sheet1: {
|
||||
id: 'sheet1',
|
||||
name: 'Sheet1',
|
||||
rowCount: 20,
|
||||
columnCount: 20,
|
||||
cellData: {
|
||||
0: {
|
||||
0: { v: 'product' },
|
||||
1: { v: 'amount' },
|
||||
},
|
||||
1: {
|
||||
0: { v: 'book' },
|
||||
1: { v: 12, s: { bg: { rgb: '#ff0000' } } },
|
||||
},
|
||||
2: {
|
||||
0: { v: 'pen' },
|
||||
1: { v: 3, s: { bg: { rgb: '#0000ff' } } },
|
||||
},
|
||||
3: {
|
||||
1: { v: 8 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
};
|
||||
}
|
||||
|
||||
function createTestBed(): 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 dependencies: Dependency[] = [
|
||||
[TableManager],
|
||||
[SheetTableService],
|
||||
[SheetsTableUiService],
|
||||
];
|
||||
dependencies.forEach((dependency) => this._injector.add(dependency));
|
||||
}
|
||||
}
|
||||
|
||||
univer.registerPlugin(TestPlugin);
|
||||
const localeService = injector.get(LocaleService);
|
||||
localeService.load({
|
||||
[LocaleType.EN_US]: {
|
||||
'sheets-table-ui': {
|
||||
condition: {
|
||||
empty: '(Empty)',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
localeService.setLocale(LocaleType.EN_US);
|
||||
|
||||
const commandService = injector.get(ICommandService);
|
||||
[SetSheetTableFilterCommand, SetSheetTableFilterMutation, SetRangeValuesMutation].forEach((command) => {
|
||||
commandService.registerCommand(command);
|
||||
});
|
||||
|
||||
const workbook = univer.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, createWorkbookData());
|
||||
injector.get(SheetTableService).addTable(
|
||||
workbook.getUnitId(),
|
||||
'sheet1',
|
||||
'Orders',
|
||||
{ startRow: 0, endRow: 3, startColumn: 0, endColumn: 1 },
|
||||
['Product', 'Amount'],
|
||||
'table-orders',
|
||||
{
|
||||
filters: [
|
||||
{ filterType: TableColumnFilterTypeEnum.manual, values: ['book', TABLE_FILTER_EMPTY_VALUE] },
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
univer,
|
||||
get: injector.get.bind(injector),
|
||||
workbook,
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetsTableUiService', () => {
|
||||
let testBed: ITestBed | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
testBed?.univer.dispose();
|
||||
testBed = undefined;
|
||||
});
|
||||
|
||||
it('builds filter panel state from the selected table column', () => {
|
||||
testBed = createTestBed();
|
||||
const service = testBed.get(SheetsTableUiService);
|
||||
|
||||
expect(service.getTableFilterPanelInitProps(testBed.workbook.getUnitId(), 'sheet1', 'table-orders', 0)).toEqual({
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
tableFilter: { filterType: TableColumnFilterTypeEnum.manual, values: ['book', TABLE_FILTER_EMPTY_VALUE] },
|
||||
currentFilterBy: FilterByEnum.Items,
|
||||
tableId: 'table-orders',
|
||||
columnIndex: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('localizes checked manual filter values for blank cells', () => {
|
||||
testBed = createTestBed();
|
||||
const service = testBed.get(SheetsTableUiService);
|
||||
|
||||
expect(service.getTableFilterCheckedItems(testBed.workbook.getUnitId(), 'table-orders', 0)).toEqual(['book', '(Empty)']);
|
||||
});
|
||||
|
||||
it('builds color filter state and candidates after applying other table filters', async () => {
|
||||
testBed = createTestBed();
|
||||
const service = testBed.get(SheetsTableUiService);
|
||||
await testBed.get(ICommandService).executeCommand(SetRangeValuesMutation.id, {
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
cellValue: {
|
||||
1: {
|
||||
1: { v: 12, s: { bg: { rgb: '#ff0000' }, cl: { rgb: null } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
const table = testBed.get(TableManager).getTable(testBed.workbook.getUnitId(), 'table-orders')!;
|
||||
table.setTableFilterColumn(1, {
|
||||
filterType: TableColumnFilterTypeEnum.color,
|
||||
cellFillColors: ['rgb(255, 0, 0)'],
|
||||
});
|
||||
|
||||
expect(service.getTableFilterPanelInitProps(
|
||||
testBed.workbook.getUnitId(),
|
||||
'sheet1',
|
||||
'table-orders',
|
||||
1
|
||||
).currentFilterBy).toBe(FilterByEnum.Color);
|
||||
expect(service.getTableFilterColors(
|
||||
testBed.workbook.getUnitId(),
|
||||
'sheet1',
|
||||
'table-orders',
|
||||
1
|
||||
)).toEqual({
|
||||
cellFillColors: [
|
||||
{ color: 'rgb(255,0,0)', checked: true },
|
||||
{ color: null, checked: false },
|
||||
],
|
||||
cellTextColors: [
|
||||
{ color: 'rgb(0,0,0)', checked: false },
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it('includes theme colors in table filter candidates', async () => {
|
||||
testBed = createTestBed();
|
||||
const commandService = testBed.get(ICommandService);
|
||||
await commandService.executeCommand(SetRangeValuesMutation.id, {
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
cellValue: {
|
||||
1: {
|
||||
1: { v: 12, s: { bg: { th: ThemeColorType.ACCENT1 } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(testBed.get(SheetsTableUiService).getTableFilterColors(
|
||||
testBed.workbook.getUnitId(),
|
||||
'sheet1',
|
||||
'table-orders',
|
||||
1
|
||||
).cellFillColors).toEqual([
|
||||
{ color: 'rgb(68,114,196)', checked: false },
|
||||
{ color: null, checked: false },
|
||||
]);
|
||||
});
|
||||
|
||||
it('builds candidate values after applying filters from other table columns', () => {
|
||||
testBed = createTestBed();
|
||||
const service = testBed.get(SheetsTableUiService);
|
||||
|
||||
const items = service.getTableFilterItems(testBed.workbook.getUnitId(), 'sheet1', 'table-orders', 1);
|
||||
|
||||
expect(items.allItemsCount).toBe(2);
|
||||
expect(items.data).toEqual([
|
||||
{ title: 12, key: '1_1', leaf: true },
|
||||
{ title: 8, key: '1_3', leaf: true },
|
||||
]);
|
||||
expect(items.itemsCountMap.get(12 as unknown as string)).toBe(1);
|
||||
expect(items.itemsCountMap.get(8 as unknown as string)).toBe(1);
|
||||
});
|
||||
|
||||
it('refreshes cached candidates when table data changes inside the filter range', async () => {
|
||||
testBed = createTestBed();
|
||||
const service = testBed.get(SheetsTableUiService);
|
||||
const commandService = testBed.get(ICommandService);
|
||||
|
||||
expect(service.getTableFilterItems(testBed.workbook.getUnitId(), 'sheet1', 'table-orders', 1).data).toEqual([
|
||||
{ title: 12, key: '1_1', leaf: true },
|
||||
{ title: 8, key: '1_3', leaf: true },
|
||||
]);
|
||||
|
||||
await commandService.executeCommand(SetRangeValuesMutation.id, {
|
||||
unitId: testBed.workbook.getUnitId(),
|
||||
subUnitId: 'sheet1',
|
||||
cellValue: {
|
||||
1: {
|
||||
1: { v: 15 },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(service.getTableFilterItems(testBed.workbook.getUnitId(), 'sheet1', 'table-orders', 1).data).toEqual([
|
||||
{ title: 15, key: '1_1', leaf: true },
|
||||
{ title: 8, key: '1_3', leaf: true },
|
||||
]);
|
||||
});
|
||||
|
||||
it('updates the table through the filter command', async () => {
|
||||
testBed = createTestBed();
|
||||
const service = testBed.get(SheetsTableUiService);
|
||||
|
||||
service.setTableFilter(testBed.workbook.getUnitId(), 'table-orders', 0, {
|
||||
filterType: TableColumnFilterTypeEnum.manual,
|
||||
values: ['pen'],
|
||||
});
|
||||
await Promise.resolve();
|
||||
|
||||
const table = testBed.get(TableManager).getTable(testBed.workbook.getUnitId(), 'table-orders')!;
|
||||
expect(table.getTableFilterColumn(0)).toEqual({
|
||||
filterType: TableColumnFilterTypeEnum.manual,
|
||||
values: ['pen'],
|
||||
});
|
||||
});
|
||||
});
|
||||
283
packages/sheets-table-ui/src/services/sheets-table-ui.service.ts
Normal file
283
packages/sheets-table-ui/src/services/sheets-table-ui.service.ts
Normal file
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* 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 { Nullable, Workbook, Worksheet } from '@univerjs/core';
|
||||
import type { ISetRangeValuesMutationParams } from '@univerjs/sheets';
|
||||
import type { ISetSheetTableParams, ITableFilterItem } from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import type { IFilterByValueWithTreeItem, ITableFilterColorList, ITableFilterItemList } from '../types';
|
||||
import {
|
||||
cellToRange,
|
||||
ColorKit,
|
||||
DEFAULT_STYLES,
|
||||
Disposable,
|
||||
getColorStyle,
|
||||
ICommandService,
|
||||
Inject,
|
||||
IUniverInstanceService,
|
||||
LocaleService,
|
||||
ObjectMatrix,
|
||||
Rectangle,
|
||||
} from '@univerjs/core';
|
||||
import { SetRangeValuesMutation } from '@univerjs/sheets';
|
||||
import {
|
||||
isColorTableFilter,
|
||||
isConditionFilter,
|
||||
isManualTableFilter,
|
||||
SetSheetTableFilterCommand,
|
||||
SheetTableService,
|
||||
TABLE_FILTER_EMPTY_VALUE,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { FilterByEnum } from '../types';
|
||||
|
||||
interface ISheetTableFilterPanelProps {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableFilter: ITableFilterItem | undefined;
|
||||
currentFilterBy: FilterByEnum;
|
||||
tableId: string;
|
||||
columnIndex: number;
|
||||
}
|
||||
|
||||
export class SheetsTableUiService extends Disposable {
|
||||
private _itemsCache: Map<string, ITableFilterItemList> = new Map();
|
||||
constructor(
|
||||
@Inject(TableManager) private _tableManager: TableManager,
|
||||
@Inject(SheetTableService) private _sheetTableService: SheetTableService,
|
||||
@Inject(IUniverInstanceService) private readonly _univerInstanceService: IUniverInstanceService,
|
||||
@ICommandService private readonly _commandService: ICommandService,
|
||||
@Inject(LocaleService) private readonly _localeService: LocaleService
|
||||
) {
|
||||
super();
|
||||
this._registerTableFilterChangeEvent();
|
||||
}
|
||||
|
||||
private _registerTableFilterChangeEvent() {
|
||||
this._commandService.onCommandExecuted((command) => {
|
||||
if (command.id === SetRangeValuesMutation.id) {
|
||||
const { unitId, subUnitId, cellValue } = command.params as ISetRangeValuesMutationParams;
|
||||
const tables = this._tableManager.getTablesBySubunitId(unitId, subUnitId);
|
||||
if (!tables.length) {
|
||||
return;
|
||||
}
|
||||
const matrix = new ObjectMatrix(cellValue);
|
||||
matrix.forValue((row, col, _value) => {
|
||||
const cellRange = cellToRange(row, col);
|
||||
const overlapTable = tables.find((table) => {
|
||||
const tableRange = table.getTableFilterRange();
|
||||
return Rectangle.intersects(tableRange, cellRange);
|
||||
});
|
||||
if (overlapTable) {
|
||||
const colIndex = col - overlapTable.getRange().startColumn;
|
||||
this._itemsCache.delete(overlapTable.getId() + colIndex);
|
||||
}
|
||||
});
|
||||
} else if (command.id === SetSheetTableFilterCommand.id) {
|
||||
const { unitId, tableId } = command.params as ISetSheetTableParams;
|
||||
const table = this._tableManager.getTable(unitId, tableId);
|
||||
if (!table) {
|
||||
return;
|
||||
}
|
||||
const subUnitId = table.getSubunitId();
|
||||
const allSubTables = this._tableManager.getTablesBySubunitId(unitId, subUnitId);
|
||||
allSubTables.forEach((table) => {
|
||||
const range = table.getRange();
|
||||
for (let i = range.startColumn; i <= range.endColumn; i++) {
|
||||
this._itemsCache.delete(table.getId() + i);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getTableFilterPanelInitProps(unitId: string, subUnitId: string, tableId: string, column: number): ISheetTableFilterPanelProps {
|
||||
const table = this._tableManager.getTable(unitId, tableId)!;
|
||||
|
||||
const tableRange = table.getRange();
|
||||
const tableFilter = table.getTableFilterColumn(column - tableRange.startColumn);
|
||||
return {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableFilter,
|
||||
currentFilterBy: isConditionFilter(tableFilter)
|
||||
? FilterByEnum.Condition
|
||||
: isColorTableFilter(tableFilter) ? FilterByEnum.Color : FilterByEnum.Items,
|
||||
tableId,
|
||||
columnIndex: column - tableRange.startColumn,
|
||||
};
|
||||
}
|
||||
|
||||
getTableFilterCheckedItems(unitId: string, tableId: string, columnIndex: number): string[] {
|
||||
const table = this._tableManager.getTable(unitId, tableId);
|
||||
const checkedItems: string[] = [];
|
||||
if (table) {
|
||||
const filter = table.getTableFilterColumn(columnIndex);
|
||||
if (filter && isManualTableFilter(filter)) {
|
||||
checkedItems.push(...filter.values.map((value) => value === TABLE_FILTER_EMPTY_VALUE ? this._localeService.t<LocaleKey>('sheets-table-ui.condition.empty') : value));
|
||||
}
|
||||
}
|
||||
return checkedItems;
|
||||
}
|
||||
|
||||
async setTableFilter(
|
||||
unitId: string,
|
||||
tableId: string,
|
||||
columnIndex: number,
|
||||
tableFilter: ITableFilterItem | undefined
|
||||
): Promise<boolean> {
|
||||
const table = this._tableManager.getTable(unitId, tableId);
|
||||
if (!table) {
|
||||
return false;
|
||||
}
|
||||
const setTableFilterParams: ISetSheetTableParams = {
|
||||
unitId,
|
||||
tableId,
|
||||
column: columnIndex,
|
||||
tableFilter,
|
||||
};
|
||||
return this._commandService.executeCommand(SetSheetTableFilterCommand.id, setTableFilterParams);
|
||||
}
|
||||
|
||||
getTableFilterColors(
|
||||
unitId: string,
|
||||
subUnitId: string,
|
||||
tableId: string,
|
||||
columnIndex: number
|
||||
): ITableFilterColorList {
|
||||
const emptyResult = { cellFillColors: [], cellTextColors: [] };
|
||||
const table = this._tableManager.getTable(unitId, tableId);
|
||||
if (!table) {
|
||||
return emptyResult;
|
||||
}
|
||||
|
||||
const worksheet = this._univerInstanceService.getUnit<Workbook>(unitId)?.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) {
|
||||
return emptyResult;
|
||||
}
|
||||
|
||||
const normalizeColor = (color: Nullable<string>) => color ? new ColorKit(color).toRgbString() : null;
|
||||
const currentFilter = table.getTableFilterColumn(columnIndex);
|
||||
const checkedFillColors = new Set(
|
||||
isColorTableFilter(currentFilter) ? currentFilter.cellFillColors?.map(normalizeColor) : []
|
||||
);
|
||||
const checkedTextColors = new Set(
|
||||
isColorTableFilter(currentFilter) ? currentFilter.cellTextColors?.map(normalizeColor) : []
|
||||
);
|
||||
const cellFillColors = new Map<string | null, boolean>();
|
||||
const cellTextColors = new Map<string | null, boolean>();
|
||||
const tableRange = table.getTableFilterRange();
|
||||
const column = tableRange.startColumn + columnIndex;
|
||||
const filteredRowsByOtherColumns = this._getFilteredRowsByOtherColumns(
|
||||
worksheet,
|
||||
tableId,
|
||||
unitId,
|
||||
columnIndex
|
||||
);
|
||||
|
||||
for (let row = tableRange.startRow; row <= tableRange.endRow; row++) {
|
||||
if (filteredRowsByOtherColumns.has(row)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const cellData = worksheet.getCell(row, column);
|
||||
const style = worksheet.getComposedCellStyleByCellData(row, column, cellData);
|
||||
const fillColor = normalizeColor(getColorStyle(style.bg));
|
||||
const textColor = normalizeColor(getColorStyle(style.cl) ?? getColorStyle(DEFAULT_STYLES.cl));
|
||||
cellFillColors.set(fillColor, checkedFillColors.has(fillColor));
|
||||
cellTextColors.set(textColor, checkedTextColors.has(textColor));
|
||||
}
|
||||
|
||||
return {
|
||||
cellFillColors: Array.from(cellFillColors, ([color, checked]) => ({ color, checked })),
|
||||
cellTextColors: Array.from(cellTextColors, ([color, checked]) => ({ color, checked })),
|
||||
};
|
||||
}
|
||||
|
||||
getTableFilterItems(unitId: string, subUnitId: string, tableId: string, columnIndex: number): ITableFilterItemList {
|
||||
if (this._itemsCache.has(tableId + columnIndex)) {
|
||||
return this._itemsCache.get(tableId + columnIndex) || { data: [], itemsCountMap: new Map(), allItemsCount: 0 };
|
||||
}
|
||||
const table = this._tableManager.getTable(unitId, tableId);
|
||||
if (!table) {
|
||||
return { data: [], itemsCountMap: new Map(), allItemsCount: 0 };
|
||||
}
|
||||
const tableRange = table.getTableFilterRange();
|
||||
const { startRow, endRow, startColumn } = tableRange;
|
||||
const column = startColumn + columnIndex;
|
||||
const worksheet = this._univerInstanceService.getUnit<Workbook>(unitId)?.getSheetBySheetId(subUnitId);
|
||||
if (!worksheet) {
|
||||
return { data: [], itemsCountMap: new Map(), allItemsCount: 0 };
|
||||
}
|
||||
|
||||
const data: IFilterByValueWithTreeItem[] = [];
|
||||
|
||||
const map = new Map<string, number>();
|
||||
const filteredRowsByOtherColumns = this._getFilteredRowsByOtherColumns(
|
||||
worksheet,
|
||||
tableId,
|
||||
unitId,
|
||||
columnIndex
|
||||
);
|
||||
|
||||
let allItemsCount = 0;
|
||||
for (let row = startRow; row <= endRow; row++) {
|
||||
if (filteredRowsByOtherColumns.has(row)) {
|
||||
continue;
|
||||
}
|
||||
let stringItem = this._sheetTableService.getCellValueWithConditionType(worksheet, row, column) as string;
|
||||
|
||||
if (stringItem == null) {
|
||||
stringItem = this._localeService.t<LocaleKey>('sheets-table-ui.condition.empty');
|
||||
}
|
||||
|
||||
if (!map.has(stringItem)) {
|
||||
data.push({
|
||||
title: stringItem,
|
||||
key: `${column}_${row}`,
|
||||
leaf: true,
|
||||
});
|
||||
}
|
||||
allItemsCount++;
|
||||
map.set(stringItem, (map.get(stringItem) || 0) + 1);
|
||||
}
|
||||
this._itemsCache.set(tableId + columnIndex, { data, itemsCountMap: map, allItemsCount });
|
||||
return { data, itemsCountMap: map, allItemsCount };
|
||||
}
|
||||
|
||||
private _getFilteredRowsByOtherColumns(
|
||||
worksheet: Worksheet,
|
||||
tableId: string,
|
||||
unitId: string,
|
||||
columnIndex: number
|
||||
): Set<number> {
|
||||
const filteredRows = new Set<number>();
|
||||
const table = this._tableManager.getTable(unitId, tableId);
|
||||
if (!table) {
|
||||
return filteredRows;
|
||||
}
|
||||
|
||||
const tableRange = table.getTableFilterRange();
|
||||
const tableFilters = table.getTableFilters();
|
||||
for (let column = tableRange.startColumn; column <= tableRange.endColumn; column++) {
|
||||
const currentColumnIndex = column - tableRange.startColumn;
|
||||
if (currentColumnIndex !== columnIndex && table.getTableFilterColumn(currentColumnIndex)) {
|
||||
tableFilters.doColumnFilter(worksheet, tableRange, currentColumnIndex, filteredRows);
|
||||
}
|
||||
}
|
||||
return filteredRows;
|
||||
}
|
||||
}
|
||||
45
packages/sheets-table-ui/src/types.ts
Normal file
45
packages/sheets-table-ui/src/types.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export enum FilterByEnum {
|
||||
Items = 'items',
|
||||
Color = 'color',
|
||||
Condition = 'condition',
|
||||
}
|
||||
|
||||
export interface ITableFilterColorItem {
|
||||
color: string | null;
|
||||
checked: boolean;
|
||||
}
|
||||
|
||||
export interface ITableFilterColorList {
|
||||
cellFillColors: ITableFilterColorItem[];
|
||||
cellTextColors: ITableFilterColorItem[];
|
||||
}
|
||||
|
||||
export interface IFilterByValueWithTreeItem {
|
||||
title: string;
|
||||
key: string;
|
||||
leaf: boolean;
|
||||
originValues?: Set<string>;
|
||||
children?: IFilterByValueWithTreeItem[];
|
||||
}
|
||||
|
||||
export interface ITableFilterItemList {
|
||||
data: IFilterByValueWithTreeItem[];
|
||||
itemsCountMap: Map<string, number>;
|
||||
allItemsCount: number;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { ITableFilterColorItem, ITableFilterColorList } from '../../types';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { clsx, resetButtonClassName } from '@univerjs/design';
|
||||
import { AIcon, BanIcon, CheckMarkIcon } from '@univerjs/icons';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
interface IMobileSheetTableColorFilterPanelProps {
|
||||
colors: ITableFilterColorList;
|
||||
onChange: (colors: ITableFilterColorList) => void;
|
||||
}
|
||||
|
||||
function MobileColorOption(props: {
|
||||
item: ITableFilterColorItem;
|
||||
label: string;
|
||||
textColor?: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const { item, label, textColor = false, onClick } = props;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={item.color ?? label}
|
||||
aria-pressed={item.checked}
|
||||
className={clsx(resetButtonClassName, `
|
||||
univer-relative univer-flex univer-size-12 univer-items-center univer-justify-center univer-rounded-full
|
||||
univer-border univer-border-solid univer-border-gray-200 univer-bg-gray-0
|
||||
dark:!univer-border-gray-600 dark:!univer-bg-gray-800
|
||||
`)}
|
||||
style={!textColor && item.color ? { backgroundColor: item.color } : undefined}
|
||||
onClick={onClick}
|
||||
>
|
||||
{!item.color && <BanIcon className="univer-size-7" />}
|
||||
{textColor && item.color && <AIcon className="univer-size-7" style={{ color: item.color }} />}
|
||||
{item.checked && (
|
||||
<span
|
||||
className="
|
||||
univer-absolute -univer-bottom-1 -univer-right-1 univer-flex univer-size-5 univer-items-center
|
||||
univer-justify-center univer-rounded-full univer-bg-primary-600 univer-text-gray-0
|
||||
"
|
||||
>
|
||||
<CheckMarkIcon className="univer-size-3" />
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function MobileSheetTableColorFilterPanel(props: IMobileSheetTableColorFilterPanelProps) {
|
||||
const { colors, onChange } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const fillLabel = localeService.t<LocaleKey>('sheets-table-ui.filter.filter-by-cell-fill-color');
|
||||
const textLabel = localeService.t<LocaleKey>('sheets-table-ui.filter.filter-by-cell-text-color');
|
||||
const hasMultipleColors = colors.cellFillColors.length > 1 || colors.cellTextColors.length > 1;
|
||||
|
||||
function toggleColor(item: ITableFilterColorItem, fillColor: boolean) {
|
||||
const key = fillColor ? 'cellFillColors' : 'cellTextColors';
|
||||
const otherKey = fillColor ? 'cellTextColors' : 'cellFillColors';
|
||||
onChange({
|
||||
...colors,
|
||||
[key]: colors[key].map((color) => color.color === item.color
|
||||
? { ...color, checked: !color.checked }
|
||||
: color),
|
||||
[otherKey]: colors[otherKey].map((color) => ({ ...color, checked: false })),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-u-comp="mobile-sheet-table-filter-by-colors" className="univer-grid univer-gap-6">
|
||||
{colors.cellFillColors.length > 1 && (
|
||||
<section>
|
||||
<h3 className="univer-m-0 univer-mb-4 univer-text-base univer-font-medium">{fillLabel}</h3>
|
||||
<div className="univer-flex univer-flex-wrap univer-gap-4">
|
||||
{colors.cellFillColors.map((item) => (
|
||||
<MobileColorOption
|
||||
key={item.color ?? 'none'}
|
||||
item={item}
|
||||
label={fillLabel}
|
||||
onClick={() => toggleColor(item, true)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{colors.cellTextColors.length > 1 && (
|
||||
<section>
|
||||
<h3 className="univer-m-0 univer-mb-4 univer-text-base univer-font-medium">{textLabel}</h3>
|
||||
<div className="univer-flex univer-flex-wrap univer-gap-4">
|
||||
{colors.cellTextColors.map((item) => (
|
||||
<MobileColorOption
|
||||
key={item.color ?? 'none'}
|
||||
item={item}
|
||||
label={textLabel}
|
||||
textColor
|
||||
onClick={() => toggleColor(item, false)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{!hasMultipleColors && (
|
||||
<div
|
||||
className="
|
||||
univer-flex univer-min-h-40 univer-items-center univer-justify-center univer-text-gray-500
|
||||
"
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.filter-by-color-none')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* 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 { ITableFilterItem } from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type {
|
||||
IConditionCompareTypeEnum,
|
||||
IConditionExpect,
|
||||
IConditionInfo,
|
||||
ITableConditionTypeEnumWithoutLogic,
|
||||
} from './type';
|
||||
import { Injector, LocaleService } from '@univerjs/core';
|
||||
import {
|
||||
borderClassName,
|
||||
CascaderList,
|
||||
clsx,
|
||||
DatePicker,
|
||||
DateRangePicker,
|
||||
Input,
|
||||
InputNumber,
|
||||
MobileDropdown,
|
||||
MobileSelect,
|
||||
} from '@univerjs/design';
|
||||
import { MoreDownIcon } from '@univerjs/icons';
|
||||
import { TableConditionTypeEnum, TableDateCompareTypeEnum, TableStringCompareTypeEnum } from '@univerjs/sheets-table';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useState } from 'react';
|
||||
import { ConditionSubComponentEnum } from './type';
|
||||
import { datePickerSet, getCascaderListOptions, getConditionDateSelect, getSubComponentType } from './util';
|
||||
|
||||
interface IMobileSheetTableConditionPanelProps {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableFilter: ITableFilterItem | undefined;
|
||||
tableId: string;
|
||||
columnIndex: number;
|
||||
conditionInfo: IConditionInfo;
|
||||
onChange: (conditionInfo: IConditionInfo) => void;
|
||||
}
|
||||
|
||||
export function MobileSheetTableConditionPanel(props: IMobileSheetTableConditionPanelProps) {
|
||||
const { conditionInfo, onChange } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const injector = useDependency(Injector);
|
||||
const [conditionVisible, setConditionVisible] = useState(false);
|
||||
const [defaultDate] = useState(() => new Date());
|
||||
const cascaderOptions = getCascaderListOptions(injector);
|
||||
|
||||
function handleConditionInfo(
|
||||
info: IConditionExpect,
|
||||
type?: ITableConditionTypeEnumWithoutLogic,
|
||||
compare?: IConditionCompareTypeEnum
|
||||
) {
|
||||
onChange({
|
||||
type: type ?? conditionInfo.type,
|
||||
compare: compare ?? conditionInfo.compare,
|
||||
info,
|
||||
});
|
||||
}
|
||||
|
||||
function handleChange(value: string[]) {
|
||||
const type = value[0] as ITableConditionTypeEnumWithoutLogic;
|
||||
const compare = value[1] as IConditionCompareTypeEnum;
|
||||
if (compare) {
|
||||
setConditionVisible(false);
|
||||
}
|
||||
|
||||
const info: IConditionExpect = {};
|
||||
if (type === TableConditionTypeEnum.Date) {
|
||||
if (compare === TableDateCompareTypeEnum.Quarter) {
|
||||
info.dateSelect = TableDateCompareTypeEnum.Q1;
|
||||
} else if (compare === TableDateCompareTypeEnum.Month) {
|
||||
info.dateSelect = TableDateCompareTypeEnum.M1;
|
||||
} else if (datePickerSet.has(compare)) {
|
||||
info.date = new Date();
|
||||
} else {
|
||||
info.dateRange = [new Date(), new Date()];
|
||||
}
|
||||
} else if (type === TableConditionTypeEnum.Number) {
|
||||
info.number = 0;
|
||||
} else if (type === TableConditionTypeEnum.String) {
|
||||
info.string = '';
|
||||
}
|
||||
handleConditionInfo(info, type, compare ?? TableStringCompareTypeEnum.Equal);
|
||||
}
|
||||
|
||||
const subComponentType = getSubComponentType(conditionInfo.type, conditionInfo.compare);
|
||||
const selectType = conditionInfo.compare
|
||||
? `${localeService.t(`sheets-table-ui.condition.${conditionInfo.type}`)} - ${localeService.t(`sheets-table-ui.${conditionInfo.type}.compare.${conditionInfo.compare}`)}`
|
||||
: localeService.t(`sheets-table-ui.condition.${conditionInfo.type}`);
|
||||
const conditionDateOptions = getConditionDateSelect(injector, conditionInfo.compare as TableDateCompareTypeEnum);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MobileDropdown
|
||||
align="start"
|
||||
open={conditionVisible}
|
||||
onOpenChange={setConditionVisible}
|
||||
overlay={(
|
||||
<CascaderList
|
||||
value={[conditionInfo.type, conditionInfo.compare!]}
|
||||
options={cascaderOptions}
|
||||
onChange={handleChange}
|
||||
contentClassName="univer-flex-1"
|
||||
wrapperClassName="!univer-h-[150px]"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(`
|
||||
univer-box-border univer-flex univer-h-8 univer-w-full univer-items-center univer-justify-between
|
||||
univer-rounded-md univer-bg-gray-0 univer-px-2 univer-text-sm univer-transition-colors
|
||||
univer-duration-200
|
||||
hover:univer-border-primary-600
|
||||
focus:univer-border-primary-600 focus:univer-outline-none focus:univer-ring-2
|
||||
dark:!univer-bg-gray-700 dark:!univer-text-gray-0
|
||||
`, borderClassName)}
|
||||
>
|
||||
<span>{selectType}</span>
|
||||
<MoreDownIcon />
|
||||
</div>
|
||||
</MobileDropdown>
|
||||
|
||||
<div className="univer-mt-3 univer-w-full">
|
||||
{subComponentType === ConditionSubComponentEnum.Input && (
|
||||
<>
|
||||
{conditionInfo.type === TableConditionTypeEnum.String
|
||||
? (
|
||||
<Input
|
||||
className="univer-w-full"
|
||||
placeholder={localeService.t<LocaleKey>('sheets-table-ui.filter.input-values-placeholder')}
|
||||
value={conditionInfo.info.string}
|
||||
onChange={(value) => handleConditionInfo({ string: value })}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<InputNumber
|
||||
className="univer-h-7 univer-w-full"
|
||||
value={conditionInfo.info.number}
|
||||
controls={false}
|
||||
onChange={(value) => {
|
||||
if (value !== null) {
|
||||
handleConditionInfo({ number: value });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{subComponentType === ConditionSubComponentEnum.DatePicker && (
|
||||
<div id="univer-table-date-picker-wrapper">
|
||||
<DatePicker
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.date ?? defaultDate}
|
||||
onValueChange={(value) => handleConditionInfo({ date: value })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{subComponentType === ConditionSubComponentEnum.DateRange && (
|
||||
<div id="univer-table-date-range-wrapper">
|
||||
<DateRangePicker
|
||||
className="univer-w-full"
|
||||
value={[
|
||||
conditionInfo.info.dateRange?.[0] ?? defaultDate,
|
||||
conditionInfo.info.dateRange?.[1] ?? defaultDate,
|
||||
]}
|
||||
onValueChange={(value) => handleConditionInfo(value ? { dateRange: value } : {})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{subComponentType === ConditionSubComponentEnum.Inputs && (
|
||||
<div className="univer-flex univer-items-center univer-gap-2">
|
||||
<InputNumber
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.numberRange?.[0]}
|
||||
controls={false}
|
||||
onChange={(value) => {
|
||||
if (value !== null) {
|
||||
handleConditionInfo({ numberRange: [value, conditionInfo.info.numberRange?.[1]] });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<span> - </span>
|
||||
<InputNumber
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.numberRange?.[1]}
|
||||
controls={false}
|
||||
onChange={(value) => {
|
||||
if (value !== null) {
|
||||
handleConditionInfo({ numberRange: [conditionInfo.info.numberRange?.[0], value] });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{subComponentType === ConditionSubComponentEnum.Select && (
|
||||
<MobileSelect
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.dateSelect ?? conditionDateOptions[0].value}
|
||||
options={conditionDateOptions}
|
||||
onChange={(value) => handleConditionInfo({ dateSelect: value })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
/**
|
||||
* 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 { ISortRangeCommandParams } from '@univerjs/sheets-sort';
|
||||
import type {
|
||||
ITableColorFilterItem,
|
||||
ITableConditionFilterItem,
|
||||
ITableManualFilterItem,
|
||||
} from '@univerjs/sheets-table';
|
||||
import type { MobileDrawerSnap } from '@univerjs/ui';
|
||||
import type { ITableFilterPanelInfo } from '../../controllers/sheet-table-component.controller';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { ITableFilterColorList } from '../../types';
|
||||
import type { IConditionInfo } from './type';
|
||||
import { generateRandomId, ICommandService, IPermissionService, IUndoRedoService, LocaleService } from '@univerjs/core';
|
||||
import { Button, MobileActionRow, MobileActionRowGroup, resetButtonClassName } from '@univerjs/design';
|
||||
import {
|
||||
AscendingIcon,
|
||||
DeleteColumnDoubleIcon,
|
||||
DescendingIcon,
|
||||
LeftInsertColumnDoubleIcon,
|
||||
MoreLeftIcon,
|
||||
MoreRightIcon,
|
||||
RightInsertColumnDoubleIcon,
|
||||
} from '@univerjs/icons';
|
||||
import { WorkbookEditablePermission } from '@univerjs/sheets';
|
||||
import { SortRangeCommand, SortType } from '@univerjs/sheets-sort';
|
||||
import {
|
||||
SetSheetTableSortStateCommand,
|
||||
SheetsTableSortStateEnum,
|
||||
SheetTableInsertColumnAtCommand,
|
||||
SheetTableRemoveColumnAtCommand,
|
||||
TABLE_FILTER_EMPTY_VALUE,
|
||||
TableColumnFilterTypeEnum,
|
||||
TableDateCompareTypeEnum,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { MobileDrawer, useDependency } from '@univerjs/ui';
|
||||
import { useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { SheetsTableMobileComponentController } from '../../controllers/mobile/sheet-table-component.controller';
|
||||
import { SheetsTableUiService } from '../../services/sheets-table-ui.service';
|
||||
import { FilterByEnum } from '../../types';
|
||||
import { MobileSheetTableColorFilterPanel } from './MobileSheetTableColorFilterPanel';
|
||||
import { MobileSheetTableConditionPanel } from './MobileSheetTableConditionPanel';
|
||||
import { MobileSheetTableItemsFilterPanel } from './MobileSheetTableItemsFilterPanel';
|
||||
import { getInitConditionInfo } from './util';
|
||||
|
||||
const FILTER_BY_OPTIONS: Array<{ label: LocaleKey; value: FilterByEnum }> = [
|
||||
{ label: 'sheets-table-ui.filter.by-values', value: FilterByEnum.Items },
|
||||
{ label: 'sheets-table-ui.filter.by-colors', value: FilterByEnum.Color },
|
||||
{ label: 'sheets-table-ui.filter.by-conditions', value: FilterByEnum.Condition },
|
||||
];
|
||||
|
||||
export function MobileSheetTableFilterPanel() {
|
||||
const sheetsTableComponentController = useDependency(SheetsTableMobileComponentController);
|
||||
const tableFilterPanelInfo = sheetsTableComponentController.getCurrentTableFilterInfo();
|
||||
if (!tableFilterPanelInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return <MobileSheetTableFilterPanelContent tableFilterPanelInfo={tableFilterPanelInfo} />;
|
||||
}
|
||||
|
||||
interface IMobileSheetTableFilterPanelContentProps {
|
||||
tableFilterPanelInfo: ITableFilterPanelInfo;
|
||||
}
|
||||
|
||||
function MobileSheetTableFilterPanelContent(props: IMobileSheetTableFilterPanelContentProps) {
|
||||
const { tableFilterPanelInfo } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const tableUiService = useDependency(SheetsTableUiService);
|
||||
const tableManager = useDependency(TableManager);
|
||||
const commandService = useDependency(ICommandService);
|
||||
const permissionService = useDependency(IPermissionService);
|
||||
const undoRedoService = useDependency(IUndoRedoService);
|
||||
const sheetsTableComponentController = useDependency(SheetsTableMobileComponentController);
|
||||
const [detailFilterBy, setDetailFilterBy] = useState<FilterByEnum | null>(null);
|
||||
const [drawerSnap, setDrawerSnap] = useState<MobileDrawerSnap>('expanded');
|
||||
const layerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const panelProps = tableUiService.getTableFilterPanelInitProps(
|
||||
tableFilterPanelInfo.unitId,
|
||||
tableFilterPanelInfo.subUnitId,
|
||||
tableFilterPanelInfo.tableId,
|
||||
tableFilterPanelInfo.column
|
||||
);
|
||||
const { unitId, subUnitId, tableId, tableFilter, columnIndex } = panelProps;
|
||||
const currentTable = tableManager.getTable(unitId, tableId)!;
|
||||
|
||||
const items = tableUiService.getTableFilterItems(unitId, subUnitId, tableId, columnIndex);
|
||||
const checkedItems = tableUiService.getTableFilterCheckedItems(unitId, tableId, columnIndex);
|
||||
const [checkedItemSet, setCheckedItemSet] = useState<Set<string>>(() => new Set(
|
||||
tableFilter?.filterType === TableColumnFilterTypeEnum.manual
|
||||
? checkedItems
|
||||
: items.data.map((item) => item.title)
|
||||
));
|
||||
const [conditionInfo, setConditionInfo] = useState<IConditionInfo>(
|
||||
() => getInitConditionInfo(tableFilter) as IConditionInfo
|
||||
);
|
||||
const [colors, setColors] = useState<ITableFilterColorList>(
|
||||
() => tableUiService.getTableFilterColors(unitId, subUnitId, tableId, columnIndex)
|
||||
);
|
||||
const tableRange = currentTable.getRange();
|
||||
const canDeleteColumn = tableRange.endColumn > tableRange.startColumn;
|
||||
const absoluteColumn = tableFilterPanelInfo.column;
|
||||
const editable = permissionService.getPermissionPoint(new WorkbookEditablePermission(unitId).id)?.value;
|
||||
|
||||
function closePanel() {
|
||||
sheetsTableComponentController.closeFilterPanel();
|
||||
}
|
||||
|
||||
async function applySort(ascending: boolean) {
|
||||
const range = currentTable.getTableFilterRange();
|
||||
const undoRedoGroup = undoRedoService.beginUndoRedoGroup(unitId, generateRandomId(), 'append');
|
||||
try {
|
||||
const success = await commandService.executeCommand<ISortRangeCommandParams>(SortRangeCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range,
|
||||
orderRules: [{ colIndex: columnIndex + range.startColumn, type: ascending ? SortType.ASC : SortType.DESC }],
|
||||
hasTitle: false,
|
||||
});
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sortStateSet = await commandService.executeCommand(SetSheetTableSortStateCommand.id, {
|
||||
unitId,
|
||||
tableId,
|
||||
sortInfo: {
|
||||
columnIndex,
|
||||
sortState: ascending ? SheetsTableSortStateEnum.Asc : SheetsTableSortStateEnum.Desc,
|
||||
},
|
||||
});
|
||||
if (sortStateSet) {
|
||||
closePanel();
|
||||
}
|
||||
} finally {
|
||||
undoRedoGroup.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
async function insertColumn(side: 'left' | 'right') {
|
||||
const success = await commandService.executeCommand(SheetTableInsertColumnAtCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
index: side === 'left' ? absoluteColumn : absoluteColumn + 1,
|
||||
count: 1,
|
||||
});
|
||||
if (success) {
|
||||
closePanel();
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteColumn() {
|
||||
if (!canDeleteColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await commandService.executeCommand(SheetTableRemoveColumnAtCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
index: absoluteColumn,
|
||||
count: 1,
|
||||
});
|
||||
if (success) {
|
||||
closePanel();
|
||||
}
|
||||
}
|
||||
|
||||
async function applyFilter() {
|
||||
let filter: ITableColorFilterItem | ITableConditionFilterItem | ITableManualFilterItem | undefined;
|
||||
if (detailFilterBy === FilterByEnum.Items) {
|
||||
const emptyLabel = localeService.t<LocaleKey>('sheets-table-ui.condition.empty');
|
||||
const values = items.data
|
||||
.filter((item) => checkedItemSet.has(item.title))
|
||||
.map((item) => item.title === emptyLabel ? TABLE_FILTER_EMPTY_VALUE : item.title);
|
||||
filter = values.length === items.data.length
|
||||
? undefined
|
||||
: { filterType: TableColumnFilterTypeEnum.manual, values } satisfies ITableManualFilterItem;
|
||||
} else if (detailFilterBy === FilterByEnum.Color) {
|
||||
const cellFillColors = colors.cellFillColors.filter((item) => item.checked).map((item) => item.color);
|
||||
const cellTextColors = colors.cellTextColors.filter((item) => item.checked).map((item) => item.color);
|
||||
if (cellFillColors.length) {
|
||||
filter = { filterType: TableColumnFilterTypeEnum.color, cellFillColors } satisfies ITableColorFilterItem;
|
||||
} else if (cellTextColors.length) {
|
||||
filter = { filterType: TableColumnFilterTypeEnum.color, cellTextColors } satisfies ITableColorFilterItem;
|
||||
}
|
||||
} else if (detailFilterBy === FilterByEnum.Condition) {
|
||||
const dynamicCompare = conditionInfo.compare === TableDateCompareTypeEnum.Quarter
|
||||
|| conditionInfo.compare === TableDateCompareTypeEnum.Month;
|
||||
filter = {
|
||||
filterType: TableColumnFilterTypeEnum.condition,
|
||||
filterInfo: {
|
||||
conditionType: conditionInfo.type,
|
||||
compareType: dynamicCompare
|
||||
? Object.values(conditionInfo.info)[0] as IConditionInfo['compare']
|
||||
: conditionInfo.compare,
|
||||
expectedValue: dynamicCompare ? undefined : Object.values(conditionInfo.info)[0],
|
||||
} as ITableConditionFilterItem['filterInfo'],
|
||||
} satisfies ITableConditionFilterItem;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await tableUiService.setTableFilter(unitId, tableId, columnIndex, filter);
|
||||
if (success) {
|
||||
closePanel();
|
||||
}
|
||||
}
|
||||
|
||||
async function clearFilter() {
|
||||
const success = await tableUiService.setTableFilter(unitId, tableId, columnIndex, undefined);
|
||||
if (success) {
|
||||
closePanel();
|
||||
}
|
||||
}
|
||||
|
||||
function openDetail(filterBy: FilterByEnum) {
|
||||
if (filterBy === FilterByEnum.Items) {
|
||||
setCheckedItemSet(new Set(
|
||||
tableFilter?.filterType === TableColumnFilterTypeEnum.manual
|
||||
? checkedItems
|
||||
: items.data.map((item) => item.title)
|
||||
));
|
||||
} else if (filterBy === FilterByEnum.Color) {
|
||||
setColors(tableUiService.getTableFilterColors(unitId, subUnitId, tableId, columnIndex));
|
||||
} else if (filterBy === FilterByEnum.Condition) {
|
||||
setConditionInfo(getInitConditionInfo(tableFilter) as IConditionInfo);
|
||||
}
|
||||
setDetailFilterBy(filterBy);
|
||||
setDrawerSnap('expanded');
|
||||
}
|
||||
|
||||
function closeDetail() {
|
||||
setDetailFilterBy(null);
|
||||
}
|
||||
|
||||
const detailOption = FILTER_BY_OPTIONS.find((option) => option.value === detailFilterBy);
|
||||
const detailTitle = detailOption ? localeService.t<LocaleKey>(detailOption.label) : '';
|
||||
const detailDrawer = detailFilterBy
|
||||
? createPortal(
|
||||
<div ref={layerRef} className="univer-pointer-events-none univer-fixed univer-inset-0 univer-z-[1300]">
|
||||
<MobileDrawer
|
||||
layerRef={layerRef}
|
||||
componentName="mobile-sheet-table-filter-detail"
|
||||
openMode="push"
|
||||
snap={drawerSnap}
|
||||
expandLabel={localeService.t<LocaleKey>('sheets-table-ui.filter.resize')}
|
||||
collapseLabel={localeService.t<LocaleKey>('sheets-table-ui.filter.resize')}
|
||||
onSnapChange={setDrawerSnap}
|
||||
onClose={closeDetail}
|
||||
role="dialog"
|
||||
ariaLabel={detailTitle}
|
||||
panelClassName="univer-pointer-events-auto"
|
||||
contentClassName="univer-min-h-0 univer-px-4 univer-pb-4"
|
||||
header={(
|
||||
<header
|
||||
className="
|
||||
univer-grid univer-h-12 univer-flex-1 univer-grid-cols-[48px_1fr_48px] univer-items-center
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={localeService.t<LocaleKey>('sheets-table-ui.filter.back')}
|
||||
className={`
|
||||
${resetButtonClassName}
|
||||
univer-flex univer-size-12 univer-items-center univer-justify-center
|
||||
`}
|
||||
onClick={closeDetail}
|
||||
>
|
||||
<MoreLeftIcon className="univer-size-6" />
|
||||
</button>
|
||||
<div className="univer-truncate univer-text-center univer-text-base univer-font-semibold">
|
||||
{detailTitle}
|
||||
</div>
|
||||
</header>
|
||||
)}
|
||||
footer={(
|
||||
<footer
|
||||
className="
|
||||
univer-shrink-0 univer-border-0 univer-border-t univer-border-solid univer-border-gray-200
|
||||
univer-p-4
|
||||
dark:!univer-border-gray-700
|
||||
"
|
||||
>
|
||||
<MobileActionRowGroup>
|
||||
<Button onClick={closeDetail}>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.cancel')}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => applyFilter().catch(() => undefined)}>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.confirm')}
|
||||
</Button>
|
||||
</MobileActionRowGroup>
|
||||
</footer>
|
||||
)}
|
||||
>
|
||||
{detailFilterBy === FilterByEnum.Items && (
|
||||
<MobileSheetTableItemsFilterPanel
|
||||
items={items}
|
||||
checkedItemSet={checkedItemSet}
|
||||
setCheckedItemSet={setCheckedItemSet}
|
||||
/>
|
||||
)}
|
||||
{detailFilterBy === FilterByEnum.Color && (
|
||||
<MobileSheetTableColorFilterPanel colors={colors} onChange={setColors} />
|
||||
)}
|
||||
{detailFilterBy === FilterByEnum.Condition && (
|
||||
<MobileSheetTableConditionPanel
|
||||
tableFilter={tableFilter}
|
||||
unitId={unitId}
|
||||
subUnitId={subUnitId}
|
||||
tableId={tableId}
|
||||
columnIndex={columnIndex}
|
||||
conditionInfo={conditionInfo}
|
||||
onChange={setConditionInfo}
|
||||
/>
|
||||
)}
|
||||
</MobileDrawer>
|
||||
</div>,
|
||||
document.body
|
||||
)
|
||||
: null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="univer-flex univer-flex-col univer-gap-2">
|
||||
{editable && (
|
||||
<>
|
||||
<MobileActionRow
|
||||
variant="subtle"
|
||||
icon={<LeftInsertColumnDoubleIcon />}
|
||||
title={localeService.t<LocaleKey>('sheets-table-ui.columnMenu.insert-left')}
|
||||
onClick={() => insertColumn('left').catch(() => undefined)}
|
||||
/>
|
||||
<MobileActionRow
|
||||
variant="subtle"
|
||||
icon={<RightInsertColumnDoubleIcon />}
|
||||
title={localeService.t<LocaleKey>('sheets-table-ui.columnMenu.insert-right')}
|
||||
onClick={() => insertColumn('right').catch(() => undefined)}
|
||||
/>
|
||||
<MobileActionRow
|
||||
variant="subtle"
|
||||
icon={<DeleteColumnDoubleIcon />}
|
||||
title={localeService.t<LocaleKey>('sheets-table-ui.columnMenu.delete')}
|
||||
disabled={!canDeleteColumn}
|
||||
onClick={() => deleteColumn().catch(() => undefined)}
|
||||
/>
|
||||
<MobileActionRow
|
||||
variant="subtle"
|
||||
icon={<AscendingIcon />}
|
||||
title={localeService.t<LocaleKey>('sheets-table-ui.sort.sort-asc')}
|
||||
onClick={() => applySort(true).catch(() => undefined)}
|
||||
/>
|
||||
<MobileActionRow
|
||||
variant="subtle"
|
||||
icon={<DescendingIcon />}
|
||||
title={localeService.t<LocaleKey>('sheets-table-ui.sort.sort-desc')}
|
||||
onClick={() => applySort(false).catch(() => undefined)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{FILTER_BY_OPTIONS.map((option) => (
|
||||
<MobileActionRow
|
||||
key={option.value}
|
||||
variant="subtle"
|
||||
title={localeService.t<LocaleKey>(option.label)}
|
||||
trailing={<MoreRightIcon className="univer-size-5 univer-text-gray-400" />}
|
||||
onClick={() => openDetail(option.value)}
|
||||
/>
|
||||
))}
|
||||
<MobileActionRow
|
||||
variant="subtle"
|
||||
title={localeService.t<LocaleKey>('sheets-table-ui.filter.clear-filter')}
|
||||
disabled={!tableFilter}
|
||||
onClick={() => clearFilter().catch(() => undefined)}
|
||||
/>
|
||||
</div>
|
||||
{detailDrawer}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { ITableFilterItemList } from '../../types';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { Checkbox, Input } from '@univerjs/design';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
interface IMobileSheetTableItemsFilterPanelProps {
|
||||
items: ITableFilterItemList;
|
||||
checkedItemSet: Set<string>;
|
||||
setCheckedItemSet: Dispatch<SetStateAction<Set<string>>>;
|
||||
}
|
||||
|
||||
export function MobileSheetTableItemsFilterPanel(props: IMobileSheetTableItemsFilterPanelProps) {
|
||||
const { items, checkedItemSet, setCheckedItemSet } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const displayItems = useMemo(() => {
|
||||
const normalizedSearchText = searchText.toLowerCase();
|
||||
return normalizedSearchText
|
||||
? items.data.filter((item) => String(item.title).toLowerCase().includes(normalizedSearchText))
|
||||
: items.data;
|
||||
}, [items.data, searchText]);
|
||||
const allDisplayedChecked = displayItems.length > 0
|
||||
&& displayItems.every((item) => checkedItemSet.has(item.title));
|
||||
const someDisplayedChecked = displayItems.some((item) => checkedItemSet.has(item.title));
|
||||
|
||||
function toggleAllDisplayed() {
|
||||
setCheckedItemSet((current) => {
|
||||
const next = new Set(current);
|
||||
for (const item of displayItems) {
|
||||
if (allDisplayedChecked) {
|
||||
next.delete(item.title);
|
||||
} else {
|
||||
next.add(item.title);
|
||||
}
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
function toggleItem(title: string) {
|
||||
setCheckedItemSet((current) => {
|
||||
const next = new Set(current);
|
||||
if (next.has(title)) {
|
||||
next.delete(title);
|
||||
} else {
|
||||
next.add(title);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div data-u-comp="mobile-sheet-table-filter-by-values" className="univer-grid univer-gap-3">
|
||||
<Input
|
||||
value={searchText}
|
||||
placeholder={localeService.t<LocaleKey>('sheets-table-ui.filter.search-placeholder')}
|
||||
onChange={setSearchText}
|
||||
/>
|
||||
<div
|
||||
className="
|
||||
univer-overflow-hidden univer-rounded-xl univer-bg-gray-0
|
||||
dark:!univer-bg-gray-800
|
||||
"
|
||||
>
|
||||
<div
|
||||
className="
|
||||
univer-border-0 univer-border-b univer-border-solid univer-border-gray-200 univer-px-4
|
||||
dark:!univer-border-gray-700
|
||||
"
|
||||
>
|
||||
<Checkbox
|
||||
className="univer-flex univer-min-h-12 univer-items-center"
|
||||
checked={allDisplayedChecked}
|
||||
indeterminate={!allDisplayedChecked && someDisplayedChecked}
|
||||
disabled={displayItems.length === 0}
|
||||
onChange={toggleAllDisplayed}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.select-all')}
|
||||
</Checkbox>
|
||||
</div>
|
||||
{displayItems.map((item) => (
|
||||
<div
|
||||
key={item.key}
|
||||
className="
|
||||
univer-border-0 univer-border-b univer-border-solid univer-border-gray-200 univer-px-4
|
||||
last:univer-border-b-0
|
||||
dark:!univer-border-gray-700
|
||||
"
|
||||
>
|
||||
<Checkbox
|
||||
className="univer-flex univer-min-h-12 univer-items-center"
|
||||
checked={checkedItemSet.has(item.title)}
|
||||
onChange={() => toggleItem(item.title)}
|
||||
>
|
||||
<span className="univer-flex univer-min-w-0 univer-flex-1 univer-items-center univer-gap-2">
|
||||
<span className="univer-min-w-0 univer-flex-1 univer-truncate">{item.title}</span>
|
||||
<span className="univer-shrink-0 univer-text-gray-400">
|
||||
{`(${items.itemsCountMap.get(item.title) ?? 0})`}
|
||||
</span>
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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 type { ISheetTableMenuExtraProps, SheetTableMenuAction } from './SheetTableMenu';
|
||||
import { MobileActionRow } from '@univerjs/design';
|
||||
|
||||
const ACTIONS: SheetTableMenuAction[] = ['rename', 'update-range', 'set-theme', 'delete'];
|
||||
|
||||
export function MobileSheetTableMenu({ menu }: { menu?: ISheetTableMenuExtraProps }) {
|
||||
if (!menu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="univer-flex univer-flex-col univer-gap-2">
|
||||
{ACTIONS.map((action) => (
|
||||
<MobileActionRow
|
||||
key={action}
|
||||
title={menu.labels[action]}
|
||||
aria-label={menu.labels[action]}
|
||||
variant="subtle"
|
||||
onClick={() => menu.onSelect(action)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { ISheetTableRenameDialogProps } from './SheetTableRenameDialog';
|
||||
import { MobileActionRowGroup } from '@univerjs/design';
|
||||
import { SheetTableRenameDialog } from './SheetTableRenameDialog';
|
||||
|
||||
export function MobileSheetTableRenameDialog(props: ISheetTableRenameDialogProps) {
|
||||
return <SheetTableRenameDialog {...props} ActionRowComponent={MobileActionRowGroup} />;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { ISheetTableSelectorProps } from './SheetTableSelector';
|
||||
import { MobileActionRowGroup } from '@univerjs/design';
|
||||
import { SheetTableSelector } from './SheetTableSelector';
|
||||
|
||||
export function MobileSheetTableSelector(props: ISheetTableSelectorProps) {
|
||||
return <SheetTableSelector {...props} ActionRowComponent={MobileActionRowGroup} />;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ISheetTableThemePanelProps } from './SheetTableThemePanel';
|
||||
import { MobileColorPicker, MobileDropdown } from '@univerjs/design';
|
||||
import { SheetTableThemePanel } from './SheetTableThemePanel';
|
||||
|
||||
export function MobileSheetTableThemePanel(props: ISheetTableThemePanelProps) {
|
||||
return (
|
||||
<SheetTableThemePanel
|
||||
{...props}
|
||||
ColorPickerComponent={MobileColorPicker}
|
||||
DropdownComponent={MobileDropdown}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { ITableFilterColorItem, ITableFilterColorList } from '../../types';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { clsx, resetButtonClassName } from '@univerjs/design';
|
||||
import { AIcon, BanIcon, SuccessIcon } from '@univerjs/icons';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
interface ISheetTableColorFilterPanelProps {
|
||||
colors: ITableFilterColorList;
|
||||
onChange: (colors: ITableFilterColorList) => void;
|
||||
}
|
||||
|
||||
function ColorOption(props: {
|
||||
item: ITableFilterColorItem;
|
||||
label: string;
|
||||
textColor?: boolean;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
const { item, label, textColor = false, onClick } = props;
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
aria-label={item.color ?? label}
|
||||
aria-pressed={item.checked}
|
||||
className={clsx(resetButtonClassName, `
|
||||
univer-relative univer-flex univer-size-6 univer-items-center univer-justify-center univer-rounded-full
|
||||
univer-border univer-border-solid univer-border-gray-200 univer-bg-gray-0
|
||||
hover:univer-ring-2 hover:univer-ring-offset-2
|
||||
dark:!univer-border-gray-600 dark:!univer-bg-gray-800
|
||||
`)}
|
||||
style={!textColor && item.color ? { backgroundColor: item.color } : undefined}
|
||||
onClick={onClick}
|
||||
>
|
||||
{!item.color && <BanIcon className="univer-size-5" />}
|
||||
{textColor && item.color && <AIcon className="univer-size-5" style={{ color: item.color }} />}
|
||||
{item.checked && (
|
||||
<SuccessIcon
|
||||
className="
|
||||
univer-absolute -univer-bottom-1 -univer-right-1 univer-size-3 univer-rounded-full
|
||||
univer-bg-gray-0 univer-text-green-600
|
||||
"
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function SheetTableColorFilterPanel(props: ISheetTableColorFilterPanelProps) {
|
||||
const { colors, onChange } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const fillLabel = localeService.t<LocaleKey>('sheets-table-ui.filter.filter-by-cell-fill-color');
|
||||
const textLabel = localeService.t<LocaleKey>('sheets-table-ui.filter.filter-by-cell-text-color');
|
||||
const hasMultipleColors = colors.cellFillColors.length > 1 || colors.cellTextColors.length > 1;
|
||||
|
||||
function toggleColor(item: ITableFilterColorItem, fillColor: boolean) {
|
||||
const key = fillColor ? 'cellFillColors' : 'cellTextColors';
|
||||
const otherKey = fillColor ? 'cellTextColors' : 'cellFillColors';
|
||||
onChange({
|
||||
...colors,
|
||||
[key]: colors[key].map((color) => color.color === item.color
|
||||
? { ...color, checked: !color.checked }
|
||||
: color),
|
||||
[otherKey]: colors[otherKey].map((color) => ({ ...color, checked: false })),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="univer-grid univer-h-full univer-gap-4 univer-overflow-auto univer-rounded-md univer-p-2">
|
||||
{colors.cellFillColors.length > 1 && (
|
||||
<section>
|
||||
<h3 className="univer-m-0 univer-mb-2 univer-text-sm univer-font-normal">{fillLabel}</h3>
|
||||
<div className="univer-grid univer-grid-cols-8 univer-gap-2">
|
||||
{colors.cellFillColors.map((item) => (
|
||||
<ColorOption
|
||||
key={item.color ?? 'none'}
|
||||
item={item}
|
||||
label={fillLabel}
|
||||
onClick={() => toggleColor(item, true)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{colors.cellTextColors.length > 1 && (
|
||||
<section>
|
||||
<h3 className="univer-m-0 univer-mb-2 univer-text-sm univer-font-normal">{textLabel}</h3>
|
||||
<div className="univer-grid univer-grid-cols-8 univer-gap-2">
|
||||
{colors.cellTextColors.map((item) => (
|
||||
<ColorOption
|
||||
key={item.color ?? 'none'}
|
||||
item={item}
|
||||
label={textLabel}
|
||||
textColor
|
||||
onClick={() => toggleColor(item, false)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
{!hasMultipleColors && (
|
||||
<div className="univer-flex univer-size-full univer-items-center univer-justify-center univer-text-sm">
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.filter-by-color-none')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* 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 { ITableFilterItem } from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { IConditionCompareTypeEnum, IConditionExpect, IConditionInfo, ITableConditionTypeEnumWithoutLogic } from './type';
|
||||
import { Injector, LocaleService } from '@univerjs/core';
|
||||
import { borderClassName, CascaderList, clsx, DatePicker, DateRangePicker, Dropdown, Input, InputNumber, Select } from '@univerjs/design';
|
||||
import { MoreDownIcon } from '@univerjs/icons';
|
||||
import { TableConditionTypeEnum, TableDateCompareTypeEnum, TableStringCompareTypeEnum } from '@univerjs/sheets-table';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useState } from 'react';
|
||||
import { ConditionSubComponentEnum } from './type';
|
||||
import { datePickerSet, getCascaderListOptions, getConditionDateSelect, getSubComponentType } from './util';
|
||||
|
||||
interface IConditionFilterProps {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableFilter: ITableFilterItem | undefined;
|
||||
tableId: string;
|
||||
columnIndex: number;
|
||||
conditionInfo: IConditionInfo;
|
||||
onChange: (conditionInfo: IConditionInfo) => void;
|
||||
}
|
||||
|
||||
export const SheetTableConditionPanel = (props: IConditionFilterProps) => {
|
||||
const { conditionInfo, onChange } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
const [conditionVisible, setConditionVisible] = useState(false);
|
||||
|
||||
const injector = useDependency(Injector);
|
||||
|
||||
const cascaderOptions = getCascaderListOptions(injector);
|
||||
|
||||
const handleConditionInfo = (info: IConditionExpect, type?: ITableConditionTypeEnumWithoutLogic, compare?: IConditionCompareTypeEnum) => {
|
||||
onChange({
|
||||
type: type ?? conditionInfo.type,
|
||||
compare: compare ?? conditionInfo.compare,
|
||||
info,
|
||||
});
|
||||
};
|
||||
|
||||
const handleChange = (value: string[]) => {
|
||||
const type = value[0] as ITableConditionTypeEnumWithoutLogic;
|
||||
const compare = value[1] as IConditionCompareTypeEnum;
|
||||
if (compare) {
|
||||
setConditionVisible(false);
|
||||
};
|
||||
const info: IConditionExpect = {};
|
||||
if (type === TableConditionTypeEnum.Date) {
|
||||
if (compare === TableDateCompareTypeEnum.Quarter) {
|
||||
info.dateSelect = TableDateCompareTypeEnum.Q1;
|
||||
} else if (compare === TableDateCompareTypeEnum.Month) {
|
||||
info.dateSelect = TableDateCompareTypeEnum.M1;
|
||||
} else if (datePickerSet.has(compare)) {
|
||||
info.date = new Date();
|
||||
} else {
|
||||
info.dateRange = [new Date(), new Date()];
|
||||
}
|
||||
} else if (type === TableConditionTypeEnum.Number) {
|
||||
info.number = 0;
|
||||
} else if (type === TableConditionTypeEnum.String) {
|
||||
info.string = '';
|
||||
}
|
||||
handleConditionInfo(info, value[0] as ITableConditionTypeEnumWithoutLogic, (value[1] as IConditionCompareTypeEnum ?? TableStringCompareTypeEnum.Equal));
|
||||
};
|
||||
|
||||
const subComponentType = getSubComponentType(conditionInfo.type, conditionInfo.compare);
|
||||
|
||||
let selectType = '';
|
||||
if (conditionInfo.compare) {
|
||||
selectType = `${localeService.t(`sheets-table-ui.condition.${conditionInfo.type}`)} - ${localeService.t(`sheets-table-ui.${conditionInfo.type}.compare.${conditionInfo.compare}`)}`;
|
||||
} else {
|
||||
selectType = localeService.t(`sheets-table-ui.condition.${conditionInfo.type}`);
|
||||
}
|
||||
|
||||
const conditionDateOptions = getConditionDateSelect(injector, conditionInfo.compare as TableDateCompareTypeEnum);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown
|
||||
align="start"
|
||||
open={conditionVisible}
|
||||
onOpenChange={setConditionVisible}
|
||||
overlay={(
|
||||
<CascaderList
|
||||
value={[conditionInfo.type, conditionInfo.compare!]}
|
||||
options={cascaderOptions}
|
||||
onChange={handleChange}
|
||||
contentClassName="univer-flex-1"
|
||||
wrapperClassName="!univer-h-[150px]"
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={clsx(`
|
||||
univer-box-border univer-flex univer-h-8 univer-w-full univer-items-center univer-justify-between
|
||||
univer-rounded-md univer-bg-gray-0 univer-px-2 univer-text-sm univer-transition-colors
|
||||
univer-duration-200
|
||||
hover:univer-border-primary-600
|
||||
focus:univer-border-primary-600 focus:univer-outline-none focus:univer-ring-2
|
||||
dark:!univer-bg-gray-700 dark:!univer-text-gray-0
|
||||
`, borderClassName)}
|
||||
>
|
||||
<span>
|
||||
{selectType}
|
||||
</span>
|
||||
|
||||
<MoreDownIcon />
|
||||
</div>
|
||||
</Dropdown>
|
||||
|
||||
<div className="univer-mt-3 univer-w-full">
|
||||
{subComponentType === ConditionSubComponentEnum.Input && (
|
||||
<>
|
||||
{conditionInfo.type === TableConditionTypeEnum.String
|
||||
? (
|
||||
<Input
|
||||
className="univer-w-full"
|
||||
placeholder={localeService.t<LocaleKey>('sheets-table-ui.filter.input-values-placeholder')}
|
||||
value={conditionInfo.info.string}
|
||||
onChange={(v) => handleConditionInfo({ string: v })}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<InputNumber
|
||||
className="univer-h-7 univer-w-full"
|
||||
value={conditionInfo.info.number}
|
||||
controls={false}
|
||||
onChange={(v) => {
|
||||
if (v !== null) {
|
||||
handleConditionInfo({ number: v });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{!!(subComponentType === ConditionSubComponentEnum.DatePicker) && (
|
||||
<div id="univer-table-date-picker-wrapper">
|
||||
<DatePicker
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.date ?? new Date()}
|
||||
onValueChange={(v) => handleConditionInfo({ date: v })}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!!(subComponentType === ConditionSubComponentEnum.DateRange) && (
|
||||
<div id="univer-table-date-range-wrapper">
|
||||
<DateRangePicker
|
||||
className="univer-w-full"
|
||||
value={[conditionInfo.info.dateRange?.[0] ?? new Date(), conditionInfo.info.dateRange?.[1] ?? new Date()]}
|
||||
onValueChange={(v) => {
|
||||
if (v) {
|
||||
handleConditionInfo({ dateRange: v });
|
||||
} else {
|
||||
handleConditionInfo({});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{subComponentType === ConditionSubComponentEnum.Inputs && (
|
||||
<div className="univer-flex univer-items-center univer-gap-2">
|
||||
<InputNumber
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.numberRange?.[0]}
|
||||
onChange={(v) => {
|
||||
if (v !== null) {
|
||||
handleConditionInfo({ numberRange: [v, conditionInfo.info.numberRange?.[1]] });
|
||||
}
|
||||
}}
|
||||
controls={false}
|
||||
/>
|
||||
<span> - </span>
|
||||
<InputNumber
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.numberRange?.[1]}
|
||||
controls={false}
|
||||
onChange={(v) => {
|
||||
if (v !== null) {
|
||||
handleConditionInfo({ numberRange: [conditionInfo.info.numberRange?.[0], v] });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!!(subComponentType === ConditionSubComponentEnum.Select) && (
|
||||
<Select
|
||||
className="univer-w-full"
|
||||
value={conditionInfo.info.dateSelect ?? conditionDateOptions[0].value}
|
||||
options={conditionDateOptions}
|
||||
onChange={(v) => handleConditionInfo({ dateSelect: v })}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,383 @@
|
||||
/**
|
||||
* 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 { ISortRangeCommandParams } from '@univerjs/sheets-sort';
|
||||
import type {
|
||||
ITableColorFilterItem,
|
||||
ITableConditionFilterItem,
|
||||
ITableManualFilterItem,
|
||||
} from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { ITableFilterColorList } from '../../types';
|
||||
import type { IConditionInfo } from './type';
|
||||
import { generateRandomId, ICommandService, IPermissionService, IUndoRedoService, LocaleService } from '@univerjs/core';
|
||||
import { ActionRow, Button, ButtonGroup, Segmented } from '@univerjs/design';
|
||||
import {
|
||||
AscendingIcon,
|
||||
DeleteColumnDoubleIcon,
|
||||
DescendingIcon,
|
||||
LeftInsertColumnDoubleIcon,
|
||||
RightInsertColumnDoubleIcon,
|
||||
} from '@univerjs/icons';
|
||||
import { WorkbookEditablePermission } from '@univerjs/sheets';
|
||||
import { SortRangeCommand, SortType } from '@univerjs/sheets-sort';
|
||||
import {
|
||||
isManualTableFilter,
|
||||
SetSheetTableSortStateCommand,
|
||||
SheetsTableSortStateEnum,
|
||||
SheetTableInsertColumnAtCommand,
|
||||
SheetTableRemoveColumnAtCommand,
|
||||
TABLE_FILTER_EMPTY_VALUE,
|
||||
TableColumnFilterTypeEnum,
|
||||
TableDateCompareTypeEnum,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useState } from 'react';
|
||||
import { SheetsTableComponentController } from '../../controllers/sheet-table-component.controller';
|
||||
import { SheetsTableUiService } from '../../services/sheets-table-ui.service';
|
||||
import { FilterByEnum } from '../../types';
|
||||
import { SheetTableColorFilterPanel } from './SheetTableColorFilterPanel';
|
||||
import { SheetTableConditionPanel } from './SheetTableConditionPanel';
|
||||
import { SheetTableItemsFilterPanel } from './SheetTableItemsFilterPanel';
|
||||
import { getInitConditionInfo } from './util';
|
||||
|
||||
const FILTER_BY_OPTIONS: Array<{ label: LocaleKey; value: FilterByEnum }> = [
|
||||
{ label: 'sheets-table-ui.filter.by-values', value: FilterByEnum.Items },
|
||||
{ label: 'sheets-table-ui.filter.by-colors', value: FilterByEnum.Color },
|
||||
{ label: 'sheets-table-ui.filter.by-conditions', value: FilterByEnum.Condition },
|
||||
];
|
||||
|
||||
export function SheetTableFilterPanel() {
|
||||
const localeService = useDependency(LocaleService);
|
||||
const filterByItems = FILTER_BY_OPTIONS.map((option) => ({ ...option, label: localeService.t(option.label) }));
|
||||
const tableUiService = useDependency(SheetsTableUiService);
|
||||
const tableManager = useDependency(TableManager);
|
||||
const commandService = useDependency(ICommandService);
|
||||
const permissionService = useDependency(IPermissionService);
|
||||
const undoRedoService = useDependency(IUndoRedoService);
|
||||
const sheetsTableComponentController = useDependency(SheetsTableComponentController);
|
||||
|
||||
const tableFilterPanelInfo = sheetsTableComponentController.getCurrentTableFilterInfo()!;
|
||||
const panelProps = tableUiService.getTableFilterPanelInitProps(
|
||||
tableFilterPanelInfo.unitId,
|
||||
tableFilterPanelInfo.subUnitId,
|
||||
tableFilterPanelInfo.tableId,
|
||||
tableFilterPanelInfo.column
|
||||
);
|
||||
|
||||
const { unitId, subUnitId, tableId, tableFilter, currentFilterBy, columnIndex } = panelProps;
|
||||
|
||||
const { data } = tableUiService.getTableFilterItems(unitId, subUnitId, tableId, columnIndex);
|
||||
const checkedItems = tableUiService.getTableFilterCheckedItems(unitId, tableId, columnIndex);
|
||||
|
||||
const [checkedItemSet, setCheckedItemSet] = useState<Set<string>>(() => new Set<string>(
|
||||
isManualTableFilter(tableFilter) ? checkedItems : data.map((item) => item.title)
|
||||
));
|
||||
const [filterBy, setFilterBy] = useState(currentFilterBy || FilterByEnum.Items);
|
||||
|
||||
const [conditionInfo, setConditionInfo] = useState<IConditionInfo>(() => {
|
||||
const tableFilter = panelProps.tableFilter;
|
||||
return getInitConditionInfo(tableFilter) as IConditionInfo;
|
||||
});
|
||||
const [colors, setColors] = useState<ITableFilterColorList>(
|
||||
() => tableUiService.getTableFilterColors(unitId, subUnitId, tableId, columnIndex)
|
||||
);
|
||||
|
||||
const table = tableManager.getTable(unitId, tableId);
|
||||
if (!table) return null;
|
||||
|
||||
const tableRange = table.getRange();
|
||||
const absoluteColumn = tableFilterPanelInfo.column;
|
||||
const canDeleteColumn = tableRange.endColumn > tableRange.startColumn;
|
||||
|
||||
const closeDialog = (): void => {
|
||||
sheetsTableComponentController.closeFilterPanel();
|
||||
};
|
||||
const onCancel = () => {
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
const applySort = async (asc: boolean) => {
|
||||
const range = table.getTableFilterRange();
|
||||
const undoRedoGroup = undoRedoService.beginUndoRedoGroup(unitId, generateRandomId(), 'append');
|
||||
try {
|
||||
const success = await commandService.executeCommand<ISortRangeCommandParams>(SortRangeCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
range,
|
||||
orderRules: [{ colIndex: columnIndex + range.startColumn, type: asc ? SortType.ASC : SortType.DESC }],
|
||||
hasTitle: false,
|
||||
});
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sortStateSet = await commandService.executeCommand(SetSheetTableSortStateCommand.id, {
|
||||
unitId,
|
||||
tableId,
|
||||
sortInfo: {
|
||||
columnIndex,
|
||||
sortState: asc ? SheetsTableSortStateEnum.Asc : SheetsTableSortStateEnum.Desc,
|
||||
},
|
||||
});
|
||||
if (sortStateSet) {
|
||||
closeDialog();
|
||||
}
|
||||
} finally {
|
||||
undoRedoGroup.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
const insertColumn = async (side: 'left' | 'right') => {
|
||||
const success = await commandService.executeCommand(SheetTableInsertColumnAtCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
index: side === 'left' ? absoluteColumn : absoluteColumn + 1,
|
||||
count: 1,
|
||||
});
|
||||
if (success) {
|
||||
closeDialog();
|
||||
}
|
||||
};
|
||||
|
||||
const deleteColumn = async () => {
|
||||
if (!canDeleteColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const success = await commandService.executeCommand(SheetTableRemoveColumnAtCommand.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
index: absoluteColumn,
|
||||
count: 1,
|
||||
});
|
||||
if (success) {
|
||||
closeDialog();
|
||||
}
|
||||
};
|
||||
|
||||
const onApply = async () => {
|
||||
let filter: ITableColorFilterItem | ITableConditionFilterItem | ITableManualFilterItem | undefined;
|
||||
if (filterBy === FilterByEnum.Items) {
|
||||
// do items
|
||||
const filteredItems: string[] = [];
|
||||
const emptyLabel = localeService.t<LocaleKey>('sheets-table-ui.condition.empty');
|
||||
for (const itemInfo of data) {
|
||||
if (checkedItemSet.has(itemInfo.title)) {
|
||||
filteredItems.push(itemInfo.title === emptyLabel ? TABLE_FILTER_EMPTY_VALUE : itemInfo.title);
|
||||
}
|
||||
}
|
||||
const originFilter = table.getTableFilterColumn(columnIndex);
|
||||
if (isManualTableFilter(originFilter)) {
|
||||
const originValue = originFilter.values;
|
||||
if (originValue.join(',') === filteredItems.join(',')) {
|
||||
closeDialog();
|
||||
return;
|
||||
}
|
||||
} else if (filteredItems.length === data.length && !originFilter) {
|
||||
closeDialog();
|
||||
return;
|
||||
}
|
||||
filter = filteredItems.length === data.length
|
||||
? undefined
|
||||
: {
|
||||
filterType: TableColumnFilterTypeEnum.manual,
|
||||
values: filteredItems,
|
||||
};
|
||||
} else if (filterBy === FilterByEnum.Color) {
|
||||
const cellFillColors = colors.cellFillColors.filter((item) => item.checked).map((item) => item.color);
|
||||
const cellTextColors = colors.cellTextColors.filter((item) => item.checked).map((item) => item.color);
|
||||
if (cellFillColors.length) {
|
||||
filter = { filterType: TableColumnFilterTypeEnum.color, cellFillColors };
|
||||
} else if (cellTextColors.length) {
|
||||
filter = { filterType: TableColumnFilterTypeEnum.color, cellTextColors };
|
||||
}
|
||||
} else {
|
||||
let filterInfo;
|
||||
if (conditionInfo.compare === TableDateCompareTypeEnum.Quarter || conditionInfo.compare === TableDateCompareTypeEnum.Month) {
|
||||
filterInfo = {
|
||||
conditionType: conditionInfo.type,
|
||||
compareType: Object.values(conditionInfo.info)[0],
|
||||
};
|
||||
} else {
|
||||
filterInfo = {
|
||||
conditionType: conditionInfo.type,
|
||||
compareType: conditionInfo.compare,
|
||||
expectedValue: Object.values(conditionInfo.info)[0],
|
||||
};
|
||||
}
|
||||
filter = {
|
||||
filterType: TableColumnFilterTypeEnum.condition,
|
||||
// @ts-ignore
|
||||
filterInfo,
|
||||
};
|
||||
}
|
||||
const success = await tableUiService.setTableFilter(unitId, tableId, columnIndex, filter);
|
||||
if (success) {
|
||||
closeDialog();
|
||||
}
|
||||
};
|
||||
const onClearFilter = async () => {
|
||||
const success = await tableUiService.setTableFilter(unitId, tableId, columnIndex, undefined);
|
||||
if (success) {
|
||||
closeDialog();
|
||||
}
|
||||
};
|
||||
|
||||
const workbookEditableId = new WorkbookEditablePermission(unitId).id;
|
||||
const editable = permissionService.getPermissionPoint(workbookEditableId)?.value;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
univer-box-border univer-flex univer-w-[400px] univer-flex-col univer-rounded-[10px] univer-bg-gray-0
|
||||
univer-p-4 univer-shadow-lg
|
||||
dark:!univer-border-gray-600 dark:!univer-bg-gray-700
|
||||
`}
|
||||
>
|
||||
{editable && (
|
||||
<>
|
||||
<div
|
||||
className={`
|
||||
-univer-mx-4 -univer-mt-2 univer-mb-3 univer-border-0 univer-border-b univer-border-solid
|
||||
univer-border-gray-200 univer-py-1
|
||||
`}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
univer-box-border univer-flex univer-h-10 univer-w-full univer-cursor-pointer
|
||||
univer-items-center univer-gap-3 univer-border-none univer-bg-transparent univer-px-4
|
||||
univer-text-left univer-text-sm univer-text-gray-900
|
||||
hover:univer-bg-gray-100
|
||||
disabled:univer-cursor-not-allowed disabled:univer-text-gray-400
|
||||
dark:!univer-text-gray-0
|
||||
dark:hover:!univer-bg-gray-600
|
||||
`}
|
||||
onClick={() => insertColumn('left').catch(() => undefined)}
|
||||
>
|
||||
<LeftInsertColumnDoubleIcon className="univer-size-5" extend={{ colorChannel1: 'var(--univer-primary-600)' }} />
|
||||
<span>{localeService.t<LocaleKey>('sheets-table-ui.columnMenu.insert-left')}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
univer-box-border univer-flex univer-h-10 univer-w-full univer-cursor-pointer
|
||||
univer-items-center univer-gap-3 univer-border-none univer-bg-transparent univer-px-4
|
||||
univer-text-left univer-text-sm univer-text-gray-900
|
||||
hover:univer-bg-gray-100
|
||||
disabled:univer-cursor-not-allowed disabled:univer-text-gray-400
|
||||
dark:!univer-text-gray-0
|
||||
dark:hover:!univer-bg-gray-600
|
||||
`}
|
||||
onClick={() => insertColumn('right').catch(() => undefined)}
|
||||
>
|
||||
<RightInsertColumnDoubleIcon className="univer-size-5" extend={{ colorChannel1: 'var(--univer-primary-600)' }} />
|
||||
<span>{localeService.t<LocaleKey>('sheets-table-ui.columnMenu.insert-right')}</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`
|
||||
univer-box-border univer-flex univer-h-10 univer-w-full univer-cursor-pointer
|
||||
univer-items-center univer-gap-3 univer-border-none univer-bg-transparent univer-px-4
|
||||
univer-text-left univer-text-sm univer-text-gray-900
|
||||
hover:univer-bg-gray-100
|
||||
disabled:univer-cursor-not-allowed disabled:univer-text-gray-400
|
||||
dark:!univer-text-gray-0
|
||||
dark:hover:!univer-bg-gray-600
|
||||
`}
|
||||
disabled={!canDeleteColumn}
|
||||
onClick={() => deleteColumn().catch(() => undefined)}
|
||||
>
|
||||
<DeleteColumnDoubleIcon className="univer-size-5" extend={{ colorChannel1: 'var(--univer-primary-600)' }} />
|
||||
<span>{localeService.t<LocaleKey>('sheets-table-ui.columnMenu.delete')}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div className="univer-mb-3 univer-flex">
|
||||
<ButtonGroup className="univer-mb-3 !univer-flex univer-w-full">
|
||||
<Button className="univer-w-1/2" onClick={() => applySort(true).catch(() => undefined)}>
|
||||
<AscendingIcon className="univer-mr-1" />
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.sort.sort-asc')}
|
||||
</Button>
|
||||
<Button className="univer-w-1/2" onClick={() => applySort(false).catch(() => undefined)}>
|
||||
<DescendingIcon className="univer-mr-1" />
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.sort.sort-desc')}
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
<div className="univer-w-full">
|
||||
<Segmented
|
||||
value={filterBy}
|
||||
items={filterByItems}
|
||||
onChange={(value) => setFilterBy(value as FilterByEnum)}
|
||||
/>
|
||||
</div>
|
||||
<div className="univer-z-10 univer-h-60">
|
||||
<div className="univer-mt-3 univer-size-full">
|
||||
{filterBy === FilterByEnum.Items && (
|
||||
<SheetTableItemsFilterPanel
|
||||
tableFilter={tableFilter}
|
||||
unitId={unitId}
|
||||
subUnitId={subUnitId}
|
||||
tableId={tableId}
|
||||
columnIndex={columnIndex}
|
||||
checkedItemSet={checkedItemSet}
|
||||
setCheckedItemSet={setCheckedItemSet}
|
||||
/>
|
||||
)}
|
||||
{filterBy === FilterByEnum.Color && (
|
||||
<SheetTableColorFilterPanel colors={colors} onChange={setColors} />
|
||||
)}
|
||||
{filterBy === FilterByEnum.Condition && (
|
||||
<SheetTableConditionPanel
|
||||
tableFilter={tableFilter}
|
||||
unitId={unitId}
|
||||
subUnitId={subUnitId}
|
||||
tableId={tableId}
|
||||
columnIndex={columnIndex}
|
||||
conditionInfo={conditionInfo}
|
||||
onChange={setConditionInfo}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<ActionRow
|
||||
className={`
|
||||
univer-flex-wrap-nowrap univer-mt-4 univer-inline-flex univer-flex-shrink-0 univer-flex-grow-0
|
||||
univer-justify-between univer-gap-6 univer-overflow-hidden
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
disabled={tableFilter === undefined}
|
||||
onClick={() => onClearFilter().catch(() => undefined)}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.clear-filter')}
|
||||
</Button>
|
||||
<ActionRow className="univer-flex univer-flex-1 univer-gap-x-2">
|
||||
<Button onClick={onCancel}>{localeService.t<LocaleKey>('sheets-table-ui.filter.cancel')}</Button>
|
||||
<Button variant="primary" onClick={() => onApply().catch(() => undefined)}>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.filter.confirm')}
|
||||
</Button>
|
||||
</ActionRow>
|
||||
</ActionRow>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
/**
|
||||
* 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 { ITableFilterItem } from '@univerjs/sheets-table';
|
||||
import type { Dispatch, SetStateAction } from 'react';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { borderClassName, Checkbox, clsx, Input, scrollbarClassName } from '@univerjs/design';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { SheetsTableUiService } from '../../services/sheets-table-ui.service';
|
||||
|
||||
interface ISheetTableItemsFilterPanelProps {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableFilter: ITableFilterItem | undefined;
|
||||
tableId: string;
|
||||
columnIndex: number;
|
||||
checkedItemSet: Set<string>;
|
||||
setCheckedItemSet: Dispatch<SetStateAction<Set<string>>>;
|
||||
}
|
||||
|
||||
const getCheckedItemCount = (map: Map<string, number>, checkedItemSet: Set<string>) => {
|
||||
let count = 0;
|
||||
checkedItemSet.forEach((item) => {
|
||||
count += map.get(item) ?? 0;
|
||||
});
|
||||
return count;
|
||||
};
|
||||
|
||||
export function SheetTableItemsFilterPanel(props: ISheetTableItemsFilterPanelProps) {
|
||||
const { unitId, tableId, subUnitId, columnIndex, checkedItemSet, setCheckedItemSet, tableFilter } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const tableService = useDependency(SheetsTableUiService);
|
||||
|
||||
const { data: items, itemsCountMap, allItemsCount } = tableService.getTableFilterItems(unitId, subUnitId, tableId, columnIndex);
|
||||
|
||||
// const allChecked = tableFilter === undefined ? true : checkedItemSet.size === itemsCountMap.size;
|
||||
const [allChecked, setAllChecked] = useState<boolean>(tableFilter === undefined ? true : checkedItemSet.size === itemsCountMap.size);
|
||||
|
||||
const [checkedCount, setCheckedCount] = useState<number>(() => allChecked ? allItemsCount : getCheckedItemCount(itemsCountMap, checkedItemSet));
|
||||
|
||||
const indeterminate = !allChecked && checkedItemSet.size > 0;
|
||||
const [searchText, setSearchText] = useState('');
|
||||
|
||||
const displayItems = useMemo(() => {
|
||||
return searchText
|
||||
? items.filter((item: { title: string }) => {
|
||||
return String(item.title).toLowerCase().includes(searchText.toLowerCase());
|
||||
})
|
||||
: items;
|
||||
}, [searchText, items]);
|
||||
|
||||
const onCheckAllToggled = useCallback(() => {
|
||||
if (allChecked) {
|
||||
checkedItemSet.clear();
|
||||
setCheckedItemSet(new Set(checkedItemSet));
|
||||
setAllChecked(false);
|
||||
} else {
|
||||
displayItems.forEach((item) => {
|
||||
checkedItemSet.add(item.title);
|
||||
});
|
||||
setCheckedItemSet(new Set(checkedItemSet));
|
||||
setAllChecked(true);
|
||||
}
|
||||
}, [allChecked]);
|
||||
|
||||
const onSearchValueChange = useCallback((str: string) => {
|
||||
if (str === '') {
|
||||
setAllChecked(true);
|
||||
items.forEach((item) => {
|
||||
checkedItemSet.add(item.title);
|
||||
});
|
||||
setCheckedCount(allItemsCount);
|
||||
} else {
|
||||
checkedItemSet.clear();
|
||||
setAllChecked(false);
|
||||
setCheckedCount(0);
|
||||
}
|
||||
setSearchText(str);
|
||||
}, []);
|
||||
|
||||
const onCheckItemToggled = (key: string) => {
|
||||
if (allChecked) {
|
||||
setAllChecked(false);
|
||||
const newSet = new Set<string>();
|
||||
for (const { title } of items) {
|
||||
if (key !== title) {
|
||||
newSet.add(title);
|
||||
}
|
||||
}
|
||||
setCheckedCount(allItemsCount - itemsCountMap.get(key)!);
|
||||
setCheckedItemSet(newSet);
|
||||
} else {
|
||||
if (checkedItemSet.has(key)) {
|
||||
checkedItemSet.delete(key);
|
||||
setCheckedCount(checkedCount - itemsCountMap.get(key)!);
|
||||
} else {
|
||||
checkedItemSet.add(key);
|
||||
setCheckedCount(checkedCount + itemsCountMap.get(key)!);
|
||||
}
|
||||
setCheckedItemSet(new Set(checkedItemSet));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="univer-flex univer-h-full univer-flex-col">
|
||||
<Input autoFocus value={searchText} placeholder={localeService.t<LocaleKey>('sheets-table-ui.filter.search-placeholder')} onChange={onSearchValueChange} />
|
||||
<div
|
||||
className={clsx(`
|
||||
univer-mt-2 univer-box-border univer-flex univer-h-[180px] univer-max-h-[180px] univer-flex-grow
|
||||
univer-flex-col univer-overflow-hidden univer-rounded-md univer-py-1.5 univer-pl-2
|
||||
`, borderClassName)}
|
||||
>
|
||||
<div
|
||||
className={clsx('univer-h-40 univer-min-w-0 univer-overflow-y-auto univer-py-1 univer-pl-2', scrollbarClassName)}
|
||||
>
|
||||
<div className="univer-h-full">
|
||||
<div className="univer-flex univer-items-center univer-px-2 univer-py-1">
|
||||
<Checkbox
|
||||
className="univer-min-w-0 univer-flex-1"
|
||||
contentClassName="univer-flex-1 univer-min-w-0"
|
||||
indeterminate={indeterminate}
|
||||
disabled={items.length === 0}
|
||||
checked={allChecked}
|
||||
onChange={onCheckAllToggled}
|
||||
>
|
||||
<div className="univer-flex univer-h-5 univer-flex-1 univer-items-center univer-text-sm">
|
||||
<span className="univer-flex-1 univer-truncate">{`${localeService.t<LocaleKey>('sheets-table-ui.filter.select-all')}`}</span>
|
||||
<span className="univer-ml univer-text-gray-400">{`(${checkedCount}/${searchText ? displayItems.length : allItemsCount})`}</span>
|
||||
</div>
|
||||
</Checkbox>
|
||||
</div>
|
||||
{displayItems.map((item) => {
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
className="univer-flex univer-items-center univer-px-2 univer-py-1"
|
||||
>
|
||||
<Checkbox
|
||||
className="univer-min-w-0 univer-flex-1"
|
||||
contentClassName="univer-flex-1 univer-min-w-0"
|
||||
checked={allChecked || checkedItemSet.has(item.title)}
|
||||
onChange={() => { onCheckItemToggled(item.title); }}
|
||||
>
|
||||
<span
|
||||
className={`
|
||||
univer-flex univer-h-5 univer-flex-1 univer-items-center univer-text-sm
|
||||
`}
|
||||
>
|
||||
<span className="univer-flex-1 univer-truncate">
|
||||
{item.title}
|
||||
</span>
|
||||
<span
|
||||
className={`
|
||||
univer-ml-1 univer-inline-flex univer-h-full univer-items-center
|
||||
univer-text-gray-400
|
||||
`}
|
||||
>
|
||||
{`(${itemsCountMap.get(item.title) || 0})`}
|
||||
</span>
|
||||
</span>
|
||||
</Checkbox>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
/**
|
||||
* 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 { ICanvasPopup } from '@univerjs/sheets-ui';
|
||||
import { DropdownMenu } from '@univerjs/design';
|
||||
|
||||
export type SheetTableMenuAction = 'rename' | 'update-range' | 'set-theme' | 'delete';
|
||||
|
||||
export interface ISheetTableMenuExtraProps {
|
||||
anchorWidth: number;
|
||||
tableName: string;
|
||||
labels: Record<SheetTableMenuAction, string>;
|
||||
onSelect: (action: SheetTableMenuAction) => void | Promise<void>;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
interface ISheetTableMenuProps {
|
||||
popup: ICanvasPopup;
|
||||
}
|
||||
|
||||
export function SheetTableMenu({ popup }: ISheetTableMenuProps) {
|
||||
const menu = popup.extraProps as unknown as ISheetTableMenuExtraProps | undefined;
|
||||
|
||||
if (!menu) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenu
|
||||
align="start"
|
||||
className="univer-min-w-40"
|
||||
items={[
|
||||
{ type: 'item', children: menu.labels.rename, onSelect: () => menu.onSelect('rename') },
|
||||
{ type: 'item', children: menu.labels['update-range'], onSelect: () => menu.onSelect('update-range') },
|
||||
{ type: 'item', children: menu.labels['set-theme'], onSelect: () => menu.onSelect('set-theme') },
|
||||
{ type: 'item', children: menu.labels.delete, variant: 'destructive', onSelect: () => menu.onSelect('delete') },
|
||||
]}
|
||||
open
|
||||
sideOffset={0}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) {
|
||||
menu.onClose();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={menu.tableName}
|
||||
className="univer-block univer-h-px univer-opacity-0"
|
||||
style={{ width: menu.anchorWidth }}
|
||||
tabIndex={-1}
|
||||
/>
|
||||
</DropdownMenu>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* 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, IUniverInstanceService, LocaleService } from '@univerjs/core';
|
||||
import { ActionRow, Button, Input } from '@univerjs/design';
|
||||
import { IDefinedNamesService } from '@univerjs/engine-formula';
|
||||
import {
|
||||
getExistingNamesSet,
|
||||
SetSheetTableCommand,
|
||||
TableManager,
|
||||
validateSheetTableName,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useMemo, useState } from 'react';
|
||||
|
||||
export interface ISheetTableRenameDialogProps {
|
||||
unitId: string;
|
||||
tableId: string;
|
||||
onClose: () => void;
|
||||
ActionRowComponent?: typeof ActionRow;
|
||||
}
|
||||
|
||||
export function SheetTableRenameDialog(props: ISheetTableRenameDialogProps) {
|
||||
const { unitId, tableId, onClose, ActionRowComponent = ActionRow } = props;
|
||||
const localeService = useDependency(LocaleService);
|
||||
const commandService = useDependency(ICommandService);
|
||||
const tableManager = useDependency<TableManager>(TableManager);
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const definedNamesService = useDependency<IDefinedNamesService>(IDefinedNamesService);
|
||||
const table = tableManager.getTableById(unitId, tableId);
|
||||
const [value, setValue] = useState(table?.getDisplayName() ?? '');
|
||||
const [error, setError] = useState('');
|
||||
|
||||
const existingNames = useMemo(() => {
|
||||
const names = getExistingNamesSet(unitId, {
|
||||
univerInstanceService,
|
||||
tableManager,
|
||||
definedNamesService,
|
||||
});
|
||||
const currentName = table?.getDisplayName().toLowerCase();
|
||||
|
||||
if (currentName) {
|
||||
names.delete(currentName);
|
||||
}
|
||||
|
||||
return names;
|
||||
}, [definedNamesService, table, tableManager, unitId, univerInstanceService]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
const nextName = value.trim();
|
||||
if (!table || nextName === table.getDisplayName()) {
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
|
||||
const validation = validateSheetTableName(nextName, existingNames);
|
||||
if (!validation.valid) {
|
||||
setError(localeService.t<LocaleKey>('sheets-table-ui.tableNameError'));
|
||||
return;
|
||||
}
|
||||
|
||||
commandService.executeCommand(SetSheetTableCommand.id, {
|
||||
unitId,
|
||||
tableId,
|
||||
name: nextName,
|
||||
});
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`
|
||||
univer-box-border univer-flex univer-w-full univer-flex-col univer-gap-4 univer-pb-3 univer-pt-2
|
||||
`}
|
||||
>
|
||||
<Input
|
||||
size="middle"
|
||||
value={value}
|
||||
placeholder={localeService.t<LocaleKey>('sheets-table-ui.renamePlaceholder')}
|
||||
onChange={(nextValue) => {
|
||||
setValue(nextValue);
|
||||
setError('');
|
||||
}}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === 'Enter') {
|
||||
handleConfirm();
|
||||
}
|
||||
}}
|
||||
autoFocus
|
||||
/>
|
||||
{error ? <div className="-univer-mt-2 univer-text-sm univer-text-red-500">{error}</div> : null}
|
||||
<ActionRowComponent
|
||||
className="univer-flex univer-w-full univer-items-center univer-justify-end univer-gap-2"
|
||||
>
|
||||
<Button className="univer-min-w-16" onClick={onClose}>{localeService.t<LocaleKey>('sheets-table-ui.cancel')}</Button>
|
||||
<Button className="univer-min-w-16" variant="primary" onClick={handleConfirm}>{localeService.t<LocaleKey>('sheets-table-ui.confirm')}</Button>
|
||||
</ActionRowComponent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { ITableSelectionInfo } from '../../commands/operations/open-table-selector.operation';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import { IUniverInstanceService, LocaleService, Rectangle } from '@univerjs/core';
|
||||
import { ActionRow, Button } from '@univerjs/design';
|
||||
import { deserializeRangeWithSheet, serializeRange } from '@univerjs/engine-formula';
|
||||
import { getSheetCommandTarget } from '@univerjs/sheets';
|
||||
import { RangeSelector } from '@univerjs/sheets-formula-ui';
|
||||
import { TableManager } from '@univerjs/sheets-table';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useState } from 'react';
|
||||
|
||||
export interface ISheetTableSelectorProps extends ITableSelectionInfo {
|
||||
onConfirm: (info: ITableSelectionInfo) => void;
|
||||
onCancel: () => void;
|
||||
ActionRowComponent?: typeof ActionRow;
|
||||
}
|
||||
|
||||
export const SheetTableSelector = (props: ISheetTableSelectorProps) => {
|
||||
const { unitId, subUnitId, range, onCancel, onConfirm, tableId, ActionRowComponent = ActionRow } = props;
|
||||
|
||||
const tableManager = useDependency(TableManager);
|
||||
const [selectedRange, setSelectedRange] = useState(range);
|
||||
const [rangeError, setRangeError] = useState('');
|
||||
const localeService = useDependency(LocaleService);
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
|
||||
return (
|
||||
<>
|
||||
<RangeSelector
|
||||
maxRangeCount={1}
|
||||
unitId={unitId}
|
||||
subUnitId={subUnitId}
|
||||
initialValue={serializeRange(range)}
|
||||
onChange={(_, text) => {
|
||||
const originValue = serializeRange(range);
|
||||
const newRange = deserializeRangeWithSheet(text).range;
|
||||
const target = getSheetCommandTarget(univerInstanceService, { unitId, subUnitId });
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
const worksheet = target.worksheet;
|
||||
const merges = worksheet.getMergeData();
|
||||
const hasOverlapWithMerge = merges.some((merge) => {
|
||||
return Rectangle.intersects(newRange, merge);
|
||||
});
|
||||
|
||||
if (hasOverlapWithMerge) {
|
||||
setRangeError(localeService.t<LocaleKey>('sheets-table-ui.tableRangeWithMergeError'));
|
||||
return;
|
||||
}
|
||||
|
||||
const hasOverlapWithOtherTable = tableManager.getTablesBySubunitId(unitId, subUnitId).some((table) => {
|
||||
if (table.getId() === tableId) {
|
||||
return false;
|
||||
}
|
||||
const tableRange = table.getRange();
|
||||
return Rectangle.intersects(newRange, tableRange);
|
||||
});
|
||||
|
||||
if (hasOverlapWithOtherTable) {
|
||||
setRangeError(localeService.t<LocaleKey>('sheets-table-ui.tableRangeWithOtherTableError'));
|
||||
return;
|
||||
}
|
||||
const { startRow, endRow } = newRange;
|
||||
const isSingleRow = startRow === endRow;
|
||||
if (isSingleRow) {
|
||||
setRangeError(localeService.t<LocaleKey>('sheets-table-ui.tableRangeSingleRowError'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (originValue === text) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tableId) {
|
||||
const table = tableManager.getTableById(unitId, tableId);
|
||||
if (table) {
|
||||
const oldRange = table.getRange();
|
||||
if (Rectangle.intersects(newRange, oldRange) && oldRange.startRow === newRange.startRow) {
|
||||
setSelectedRange(newRange);
|
||||
setRangeError('');
|
||||
onConfirm({
|
||||
unitId,
|
||||
subUnitId,
|
||||
range: newRange,
|
||||
});
|
||||
return;
|
||||
} else {
|
||||
setRangeError(localeService.t<LocaleKey>('sheets-table-ui.updateError'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedRange(newRange);
|
||||
setRangeError('');
|
||||
}}
|
||||
supportAcrossSheet={false}
|
||||
/>
|
||||
{rangeError && (
|
||||
<div className="univer-mt-1 univer-text-xs univer-text-red-500">
|
||||
{rangeError}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ActionRowComponent className="univer-mt-4 univer-flex univer-justify-end univer-gap-2">
|
||||
<Button onClick={onCancel}>{localeService.t<LocaleKey>('sheets-table-ui.cancel')}</Button>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
if (rangeError) {
|
||||
return;
|
||||
}
|
||||
onConfirm({
|
||||
unitId,
|
||||
subUnitId,
|
||||
range: selectedRange,
|
||||
});
|
||||
}}
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.confirm')}
|
||||
</Button>
|
||||
</ActionRowComponent>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,431 @@
|
||||
/**
|
||||
* 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 { IRangeThemeStyleItem, ISetRangeThemeMutationParams } from '@univerjs/sheets';
|
||||
import type { IAddTableThemeCommandParams, ISetSheetTableCommandParams, ITableSetConfig } from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import { ColorKit, ErrorService, ICommandService, LocaleService } from '@univerjs/core';
|
||||
import { borderClassName, clsx, ColorPicker, Dropdown } from '@univerjs/design';
|
||||
import { CloseIcon, DownIcon, IncreaseIcon } from '@univerjs/icons';
|
||||
import { RangeThemeStyle, SetRangeThemeMutation, SheetRangeThemeModel } from '@univerjs/sheets';
|
||||
import {
|
||||
AddTableThemeCommand,
|
||||
customEmptyThemeWithBorderStyle,
|
||||
processStyleWithBorderStyle,
|
||||
RemoveTableThemeCommand,
|
||||
SetSheetTableCommand,
|
||||
TableManager,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
import {
|
||||
TABLE_BORDER_DEFAULT,
|
||||
TABLE_BORDER_NONE,
|
||||
TABLE_CUSTOM_NAME_PREFIX,
|
||||
TABLE_DEFAULT_BG_COLOR,
|
||||
TABLE_DEFAULT_NAME_PREFIX,
|
||||
} from '../../const';
|
||||
import { SheetTableThemeUIController } from '../../controllers/sheet-table-theme-ui.controller';
|
||||
|
||||
export interface ISheetTableThemePanelProps {
|
||||
unitId: string;
|
||||
subUnitId: string;
|
||||
tableId: string;
|
||||
oldConfig: ITableSetConfig;
|
||||
ColorPickerComponent?: typeof ColorPicker;
|
||||
DropdownComponent?: typeof Dropdown;
|
||||
}
|
||||
|
||||
interface ITableThemeStyle {
|
||||
headerRowStyle?: IRangeThemeStyleItem;
|
||||
firstRowStyle?: IRangeThemeStyleItem;
|
||||
secondRowStyle?: IRangeThemeStyleItem;
|
||||
lastRowStyle?: IRangeThemeStyleItem;
|
||||
}
|
||||
|
||||
export const SheetTableThemePanel = (props: ISheetTableThemePanelProps) => {
|
||||
const {
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
ColorPickerComponent = ColorPicker,
|
||||
DropdownComponent = Dropdown,
|
||||
} = props;
|
||||
const commandService = useDependency(ICommandService);
|
||||
const localeService = useDependency(LocaleService);
|
||||
const tableManager = useDependency(TableManager);
|
||||
const table = tableManager.getTableById(unitId, tableId);
|
||||
const rangeThemeModel = useDependency(SheetRangeThemeModel);
|
||||
const sheetTableThemeUIController = useDependency(SheetTableThemeUIController);
|
||||
|
||||
const rangeThemeMapChanged = useObservable(rangeThemeModel.rangeThemeMapChange$);
|
||||
const tableRefresh = useObservable(sheetTableThemeUIController.refreshTable$);
|
||||
|
||||
const errorService = useDependency(ErrorService);
|
||||
|
||||
const [, setRefresh] = useState(Math.random);
|
||||
|
||||
const themeConfig = useObservable(tableManager.tableThemeChanged$, {
|
||||
theme: table?.getTableStyleId(),
|
||||
oldTheme: table?.getTableStyleId(),
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
});
|
||||
|
||||
const defaultRangeThemes = rangeThemeModel.getRegisteredRangeThemes().filter((item) => item?.startsWith(TABLE_DEFAULT_NAME_PREFIX));
|
||||
const customRangeThemes = rangeThemeModel.getALLRegisteredTheme(unitId).filter((item) => item?.startsWith(TABLE_CUSTOM_NAME_PREFIX));
|
||||
const selectedTheme = table?.getTableStyleId();
|
||||
const customSelected = customRangeThemes.find((item) => item === selectedTheme);
|
||||
const customStyleName = customSelected || customRangeThemes[0];
|
||||
const customStyle = rangeThemeModel.getCustomRangeThemeStyle(unitId, customStyleName);
|
||||
const headerBg = customStyle?.getHeaderRowStyle()?.bg?.rgb ?? TABLE_DEFAULT_BG_COLOR;
|
||||
const firstRowBg = customStyle?.getFirstRowStyle()?.bg?.rgb ?? TABLE_DEFAULT_BG_COLOR;
|
||||
const secondRowBg = customStyle?.getSecondRowStyle()?.bg?.rgb ?? TABLE_DEFAULT_BG_COLOR;
|
||||
const lastRowBg = customStyle?.getLastRowStyle()?.bg?.rgb ?? TABLE_DEFAULT_BG_COLOR;
|
||||
const direction = useObservable(localeService.direction$, localeService.getDirection());
|
||||
|
||||
const handleThemeChange = (theme: string) => {
|
||||
commandService.executeCommand<ISetSheetTableCommandParams>(SetSheetTableCommand.id, {
|
||||
unitId,
|
||||
tableId,
|
||||
theme,
|
||||
});
|
||||
};
|
||||
|
||||
const handleAddCustomTheme = () => {
|
||||
if (customRangeThemes.length >= 11) {
|
||||
errorService.emit(localeService.t<LocaleKey>('sheets-table-ui.customTooMore'));
|
||||
return;
|
||||
}
|
||||
const lastCustomTheme = customRangeThemes[customRangeThemes.length - 1];
|
||||
let newThemeName = `${TABLE_CUSTOM_NAME_PREFIX}1`;
|
||||
if (lastCustomTheme) {
|
||||
const index = Number(lastCustomTheme.split('-')[2]);
|
||||
newThemeName = `${TABLE_CUSTOM_NAME_PREFIX}${index + 1}`;
|
||||
}
|
||||
const newTheme = new RangeThemeStyle(newThemeName, { ...customEmptyThemeWithBorderStyle });
|
||||
commandService.executeCommand<IAddTableThemeCommandParams>(AddTableThemeCommand.id, {
|
||||
unitId,
|
||||
tableId,
|
||||
themeStyle: newTheme,
|
||||
});
|
||||
};
|
||||
|
||||
const setCustomTheme = (themeName: string, tableThemeStyle: ITableThemeStyle) => {
|
||||
commandService.executeCommand<ISetRangeThemeMutationParams>(SetRangeThemeMutation.id, {
|
||||
unitId,
|
||||
subUnitId,
|
||||
styleName: themeName,
|
||||
style: tableThemeStyle,
|
||||
});
|
||||
};
|
||||
|
||||
const removeCustomTheme = (themeName: string) => {
|
||||
commandService.executeCommand(RemoveTableThemeCommand.id, {
|
||||
unitId,
|
||||
tableId,
|
||||
themeName,
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setRefresh(Math.random());
|
||||
}, [rangeThemeMapChanged, tableRefresh]);
|
||||
|
||||
if (!table) return null;
|
||||
|
||||
const headerBgIsDark = new ColorKit(headerBg).isDark();
|
||||
const firstRowBgIsDark = new ColorKit(firstRowBg).isDark();
|
||||
const secondRowBgIsDark = new ColorKit(secondRowBg).isDark();
|
||||
const lastRowBgIsDark = new ColorKit(lastRowBg).isDark();
|
||||
|
||||
const customThemeRows = [
|
||||
{
|
||||
key: 'header',
|
||||
label: localeService.t<LocaleKey>('sheets-table-ui.header'),
|
||||
background: headerBg,
|
||||
isDark: headerBgIsDark,
|
||||
onChange: (value: string) => {
|
||||
const headerRowStyle = processStyleWithBorderStyle('headerRowStyle', {
|
||||
bg: { rgb: value },
|
||||
cl: { rgb: new ColorKit(value).isDark() ? '#fff' : '#000' },
|
||||
});
|
||||
setCustomTheme(table.getTableStyleId(), { headerRowStyle });
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'first-row',
|
||||
label: localeService.t<LocaleKey>('sheets-table-ui.firstLine'),
|
||||
background: firstRowBg,
|
||||
isDark: firstRowBgIsDark,
|
||||
onChange: (value: string) => {
|
||||
setCustomTheme(table.getTableStyleId(), {
|
||||
firstRowStyle: {
|
||||
bg: { rgb: value },
|
||||
cl: { rgb: new ColorKit(value).isDark() ? '#fff' : '#000' },
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'second-row',
|
||||
label: localeService.t<LocaleKey>('sheets-table-ui.secondLine'),
|
||||
background: secondRowBg,
|
||||
isDark: secondRowBgIsDark,
|
||||
onChange: (value: string) => {
|
||||
setCustomTheme(table.getTableStyleId(), {
|
||||
secondRowStyle: {
|
||||
bg: { rgb: value },
|
||||
cl: { rgb: new ColorKit(value).isDark() ? '#fff' : '#000' },
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'last-row',
|
||||
label: localeService.t<LocaleKey>('sheets-table-ui.footer'),
|
||||
background: lastRowBg,
|
||||
isDark: lastRowBgIsDark,
|
||||
onChange: (value: string) => {
|
||||
const lastRowStyle = processStyleWithBorderStyle('lastRowStyle', {
|
||||
bg: { rgb: value },
|
||||
cl: { rgb: new ColorKit(value).isDark() ? '#fff' : '#000' },
|
||||
});
|
||||
setCustomTheme(table.getTableStyleId(), { lastRowStyle });
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div dir={direction} className="univer-flex univer-flex-col univer-gap-5 univer-pb-4">
|
||||
<section className="univer-flex univer-flex-col univer-gap-2">
|
||||
<h5
|
||||
className="
|
||||
univer-m-0 univer-text-xs univer-font-medium univer-text-gray-500
|
||||
dark:!univer-text-gray-300
|
||||
"
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.defaultStyle')}
|
||||
</h5>
|
||||
<div className="univer-grid univer-grid-cols-6 univer-gap-2">
|
||||
{defaultRangeThemes.map((item) => {
|
||||
const rangeThemeItem = rangeThemeModel.getDefaultRangeThemeStyle(item);
|
||||
const rowBackgrounds = [
|
||||
rangeThemeItem?.getHeaderRowStyle()?.bg?.rgb || TABLE_DEFAULT_BG_COLOR,
|
||||
rangeThemeItem?.getFirstRowStyle()?.bg?.rgb || TABLE_DEFAULT_BG_COLOR,
|
||||
rangeThemeItem?.getSecondRowStyle()?.bg?.rgb || TABLE_DEFAULT_BG_COLOR,
|
||||
rangeThemeItem?.getLastRowStyle()?.bg?.rgb || TABLE_DEFAULT_BG_COLOR,
|
||||
];
|
||||
const selected = item === themeConfig.theme;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={item}
|
||||
type="button"
|
||||
title={item}
|
||||
aria-label={item}
|
||||
aria-pressed={selected}
|
||||
className={clsx(`
|
||||
univer-flex univer-h-12 univer-w-full univer-cursor-pointer univer-flex-col
|
||||
univer-overflow-hidden univer-rounded-lg univer-bg-gray-0 univer-p-1 univer-shadow-sm
|
||||
univer-transition-all
|
||||
hover:univer-border-gray-400 hover:univer-shadow-md
|
||||
focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-200
|
||||
dark:!univer-bg-gray-800
|
||||
`, borderClassName, {
|
||||
'!univer-border-primary-500 univer-ring-2 univer-ring-primary-100': selected,
|
||||
})}
|
||||
onClick={() => handleThemeChange(item)}
|
||||
>
|
||||
{rowBackgrounds.map((background, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="univer-min-h-0 univer-w-full univer-flex-1"
|
||||
style={{
|
||||
background,
|
||||
border: background ? TABLE_BORDER_NONE : TABLE_BORDER_DEFAULT,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="univer-flex univer-flex-col univer-gap-2">
|
||||
<h5
|
||||
className="
|
||||
univer-m-0 univer-text-xs univer-font-medium univer-text-gray-500
|
||||
dark:!univer-text-gray-300
|
||||
"
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-table-ui.customStyle')}
|
||||
</h5>
|
||||
<div
|
||||
className="
|
||||
univer-grid univer-grid-cols-5 univer-gap-2 univer-rounded-lg univer-bg-gray-50 univer-p-2
|
||||
dark:!univer-bg-gray-800
|
||||
"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
data-u-comp="sheet-table-theme-add"
|
||||
aria-label={localeService.t<LocaleKey>('sheets-table-ui.setTheme')}
|
||||
className="
|
||||
univer-flex univer-h-12 univer-w-full univer-cursor-pointer univer-items-center
|
||||
univer-justify-center univer-rounded-lg univer-border univer-border-dashed
|
||||
univer-border-gray-300 univer-bg-gray-0 univer-p-0 univer-text-gray-500
|
||||
univer-transition-colors
|
||||
hover:univer-border-primary-500 hover:univer-text-primary-600
|
||||
focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-200
|
||||
dark:!univer-border-gray-600 dark:!univer-bg-gray-900 dark:!univer-text-gray-300
|
||||
"
|
||||
onClick={handleAddCustomTheme}
|
||||
>
|
||||
<IncreaseIcon className="univer-size-4" />
|
||||
</button>
|
||||
{customRangeThemes.map((item) => {
|
||||
const rangeThemeItem = rangeThemeModel.getCustomRangeThemeStyle(unitId, item);
|
||||
const rowBackgrounds = [
|
||||
rangeThemeItem?.getHeaderRowStyle()?.bg?.rgb,
|
||||
rangeThemeItem?.getFirstRowStyle()?.bg?.rgb,
|
||||
rangeThemeItem?.getSecondRowStyle()?.bg?.rgb,
|
||||
rangeThemeItem?.getLastRowStyle()?.bg?.rgb,
|
||||
];
|
||||
const selected = item === themeConfig.theme;
|
||||
|
||||
return (
|
||||
<div key={item} className="univer-group univer-relative univer-h-12 univer-min-w-0">
|
||||
<button
|
||||
type="button"
|
||||
title={item}
|
||||
aria-label={item}
|
||||
aria-pressed={selected}
|
||||
className={clsx(`
|
||||
univer-flex univer-size-full univer-cursor-pointer univer-flex-col
|
||||
univer-overflow-hidden univer-rounded-lg univer-bg-gray-0 univer-p-1
|
||||
univer-shadow-sm univer-transition-all
|
||||
hover:univer-border-gray-400 hover:univer-shadow-md
|
||||
focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-200
|
||||
dark:!univer-bg-gray-900
|
||||
`, borderClassName, {
|
||||
'!univer-border-primary-500 univer-ring-2 univer-ring-primary-100': selected,
|
||||
})}
|
||||
onClick={() => handleThemeChange(item)}
|
||||
>
|
||||
{rowBackgrounds.map((background, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="univer-min-h-0 univer-w-full univer-flex-1"
|
||||
style={{
|
||||
background: background ?? TABLE_BORDER_NONE,
|
||||
border: background ? TABLE_BORDER_NONE : TABLE_BORDER_DEFAULT,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
data-u-comp="sheet-table-theme-remove"
|
||||
aria-label={`${localeService.t<LocaleKey>('sheets-table-ui.customStyle')} ${item}`}
|
||||
className={clsx(`
|
||||
univer-absolute univer-right-[-6px] univer-top-[-6px] univer-z-10 univer-flex
|
||||
univer-size-5 univer-cursor-pointer univer-items-center univer-justify-center
|
||||
univer-rounded-full univer-border univer-border-solid univer-border-gray-200
|
||||
univer-bg-gray-0 univer-p-0 univer-text-gray-500 univer-shadow-sm
|
||||
univer-transition-all
|
||||
hover:univer-border-red-200 hover:univer-bg-red-50 hover:univer-text-red-600
|
||||
focus:univer-opacity-100 focus:univer-outline-none focus:univer-ring-2
|
||||
focus:univer-ring-primary-200
|
||||
rtl:univer-left-[-6px] rtl:univer-right-auto
|
||||
dark:!univer-border-gray-600 dark:!univer-bg-gray-900
|
||||
`, {
|
||||
'univer-opacity-100': selected,
|
||||
'univer-opacity-0 group-focus-within:univer-opacity-100 group-hover:univer-opacity-100': !selected,
|
||||
})}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation();
|
||||
removeCustomTheme(item);
|
||||
}}
|
||||
>
|
||||
<CloseIcon className="univer-size-3" />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{customSelected && (
|
||||
<div
|
||||
className="
|
||||
univer-flex univer-flex-col univer-gap-2 univer-rounded-lg univer-bg-gray-50 univer-p-3
|
||||
dark:!univer-bg-gray-800
|
||||
"
|
||||
>
|
||||
{customThemeRows.map((row) => (
|
||||
<div key={row.key} className="univer-flex univer-h-10 univer-min-w-0 univer-gap-2">
|
||||
<div
|
||||
className={clsx(`
|
||||
univer-flex univer-min-w-0 univer-flex-1 univer-items-center univer-rounded-md
|
||||
univer-px-3 univer-text-sm univer-font-medium
|
||||
rtl:univer-text-right
|
||||
`, borderClassName, {
|
||||
'univer-text-gray-0': row.isDark,
|
||||
'univer-text-gray-900': !row.isDark,
|
||||
})}
|
||||
style={{ background: row.background }}
|
||||
>
|
||||
<span className="univer-truncate">{row.label}</span>
|
||||
</div>
|
||||
<DropdownComponent
|
||||
overlay={(
|
||||
<div dir={direction} className="univer-p-2">
|
||||
<ColorPickerComponent
|
||||
value={new ColorKit(row.background).toHexString()}
|
||||
onChange={row.onChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
aria-label={`${row.label} ${localeService.t<LocaleKey>('sheets-table-ui.setTheme')}`}
|
||||
className={clsx(`
|
||||
univer-flex univer-h-10 univer-cursor-pointer univer-items-center univer-gap-2
|
||||
univer-rounded-md univer-bg-gray-0 univer-px-2 univer-text-gray-600
|
||||
univer-transition-colors
|
||||
hover:univer-bg-gray-100
|
||||
focus:univer-outline-none focus:univer-ring-2 focus:univer-ring-primary-200
|
||||
dark:!univer-bg-gray-900 dark:!univer-text-gray-300
|
||||
`, borderClassName)}
|
||||
>
|
||||
<span
|
||||
className={clsx('univer-size-4 univer-rounded-full', borderClassName)}
|
||||
style={{ background: row.background }}
|
||||
/>
|
||||
<DownIcon className="univer-size-3" />
|
||||
</button>
|
||||
</DropdownComponent>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 { ITableFilterItem } from '@univerjs/sheets-table';
|
||||
import {
|
||||
TableColumnFilterTypeEnum,
|
||||
TableConditionTypeEnum,
|
||||
TableDateCompareTypeEnum,
|
||||
TableNumberCompareTypeEnum,
|
||||
TableStringCompareTypeEnum,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { ConditionSubComponentEnum } from '../type';
|
||||
import { getInitConditionInfo, getSubComponentType } from '../util';
|
||||
|
||||
describe('sheet table condition utilities', () => {
|
||||
it('initializes a new condition filter as a text equality rule', () => {
|
||||
expect(getInitConditionInfo()).toEqual({
|
||||
type: TableConditionTypeEnum.String,
|
||||
compare: TableStringCompareTypeEnum.Equal,
|
||||
info: {},
|
||||
});
|
||||
});
|
||||
|
||||
it('maps condition compare types to the editor control needed by users', () => {
|
||||
expect(getSubComponentType(TableConditionTypeEnum.String, TableStringCompareTypeEnum.Contains)).toBe(ConditionSubComponentEnum.Input);
|
||||
expect(getSubComponentType(TableConditionTypeEnum.Number, TableNumberCompareTypeEnum.Between)).toBe(ConditionSubComponentEnum.Inputs);
|
||||
expect(getSubComponentType(TableConditionTypeEnum.Date, TableDateCompareTypeEnum.Between)).toBe(ConditionSubComponentEnum.DateRange);
|
||||
expect(getSubComponentType(TableConditionTypeEnum.Date, TableDateCompareTypeEnum.Quarter)).toBe(ConditionSubComponentEnum.Select);
|
||||
expect(getSubComponentType(TableConditionTypeEnum.Date, TableDateCompareTypeEnum.After)).toBe(ConditionSubComponentEnum.DatePicker);
|
||||
expect(getSubComponentType(TableConditionTypeEnum.Date, TableDateCompareTypeEnum.Today)).toBe(ConditionSubComponentEnum.None);
|
||||
});
|
||||
|
||||
it('hydrates saved date filters back into panel state without losing relative date choices', () => {
|
||||
const absoluteDateFilter: ITableFilterItem = {
|
||||
filterType: TableColumnFilterTypeEnum.condition,
|
||||
filterInfo: {
|
||||
conditionType: TableConditionTypeEnum.Date,
|
||||
compareType: TableDateCompareTypeEnum.Between,
|
||||
expectedValue: ['2026-01-01', '2026-01-31'],
|
||||
},
|
||||
};
|
||||
const quarterFilter: ITableFilterItem = {
|
||||
filterType: TableColumnFilterTypeEnum.condition,
|
||||
filterInfo: {
|
||||
conditionType: TableConditionTypeEnum.Date,
|
||||
compareType: TableDateCompareTypeEnum.Q3,
|
||||
},
|
||||
};
|
||||
const relativeDateFilter: ITableFilterItem = {
|
||||
filterType: TableColumnFilterTypeEnum.condition,
|
||||
filterInfo: {
|
||||
conditionType: TableConditionTypeEnum.Date,
|
||||
compareType: TableDateCompareTypeEnum.ThisMonth,
|
||||
},
|
||||
};
|
||||
|
||||
expect(getInitConditionInfo(absoluteDateFilter)).toMatchObject({
|
||||
type: TableConditionTypeEnum.Date,
|
||||
compare: TableDateCompareTypeEnum.Between,
|
||||
info: {
|
||||
dateRange: [
|
||||
new Date('2026-01-01'),
|
||||
new Date('2026-01-31'),
|
||||
],
|
||||
},
|
||||
});
|
||||
expect(getInitConditionInfo(quarterFilter)).toEqual({
|
||||
type: TableConditionTypeEnum.Date,
|
||||
compare: TableDateCompareTypeEnum.Quarter,
|
||||
info: {
|
||||
dateSelect: TableDateCompareTypeEnum.Q3,
|
||||
},
|
||||
});
|
||||
expect(getInitConditionInfo(relativeDateFilter)).toEqual({
|
||||
type: TableConditionTypeEnum.Date,
|
||||
compare: TableDateCompareTypeEnum.ThisMonth,
|
||||
info: {},
|
||||
});
|
||||
});
|
||||
});
|
||||
45
packages/sheets-table-ui/src/views/components/type.ts
Normal file
45
packages/sheets-table-ui/src/views/components/type.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { TableConditionTypeEnum, TableDateCompareTypeEnum, TableNumberCompareTypeEnum, TableStringCompareTypeEnum } from '@univerjs/sheets-table';
|
||||
|
||||
export enum ConditionSubComponentEnum {
|
||||
DatePicker = 'DatePicker',
|
||||
DateRange = 'DateRange',
|
||||
Input = 'Input',
|
||||
Inputs = 'Inputs',
|
||||
Select = 'Select',
|
||||
None = 'None',
|
||||
}
|
||||
|
||||
export type IConditionCompareTypeEnum = TableStringCompareTypeEnum | TableDateCompareTypeEnum | TableNumberCompareTypeEnum;
|
||||
|
||||
export type ITableConditionTypeEnumWithoutLogic = Exclude<TableConditionTypeEnum, TableConditionTypeEnum.Logic>;
|
||||
|
||||
export interface IConditionInfo {
|
||||
type: ITableConditionTypeEnumWithoutLogic;
|
||||
compare: IConditionCompareTypeEnum;
|
||||
info: IConditionExpect;
|
||||
}
|
||||
|
||||
export interface IConditionExpect {
|
||||
date?: Date;
|
||||
dateRange?: [Date, Date];
|
||||
dateSelect?: string;
|
||||
number?: number;
|
||||
numberRange?: [number?, number?];
|
||||
string?: string;
|
||||
}
|
||||
446
packages/sheets-table-ui/src/views/components/util.ts
Normal file
446
packages/sheets-table-ui/src/views/components/util.ts
Normal file
@@ -0,0 +1,446 @@
|
||||
/**
|
||||
* 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 { Injector } from '@univerjs/core';
|
||||
import type { ITableFilterItem } from '@univerjs/sheets-table';
|
||||
import type { LocaleKey } from '../../locale/types';
|
||||
import type { IConditionCompareTypeEnum } from './type';
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import {
|
||||
TableColumnFilterTypeEnum,
|
||||
TableConditionTypeEnum,
|
||||
TableDateCompareTypeEnum,
|
||||
TableNumberCompareTypeEnum,
|
||||
TableStringCompareTypeEnum,
|
||||
} from '@univerjs/sheets-table';
|
||||
import { ConditionSubComponentEnum } from './type';
|
||||
|
||||
export function getCascaderListOptions(injector: Injector) {
|
||||
const localeService = injector.get(LocaleService);
|
||||
return [
|
||||
{
|
||||
value: TableConditionTypeEnum.String,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.condition.${TableConditionTypeEnum.String}`),
|
||||
children: [
|
||||
{
|
||||
value: TableStringCompareTypeEnum.Equal,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.string.compare.${TableStringCompareTypeEnum.Equal}`),
|
||||
},
|
||||
{
|
||||
value: TableStringCompareTypeEnum.NotEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.string.compare.${TableStringCompareTypeEnum.NotEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableStringCompareTypeEnum.Contains,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.string.compare.${TableStringCompareTypeEnum.Contains}`),
|
||||
},
|
||||
{
|
||||
value: TableStringCompareTypeEnum.NotContains,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.string.compare.${TableStringCompareTypeEnum.NotContains}`),
|
||||
},
|
||||
{
|
||||
value: TableStringCompareTypeEnum.StartsWith,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.string.compare.${TableStringCompareTypeEnum.StartsWith}`),
|
||||
},
|
||||
{
|
||||
value: TableStringCompareTypeEnum.EndsWith,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.string.compare.${TableStringCompareTypeEnum.EndsWith}`),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
value: TableConditionTypeEnum.Number,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.condition.${TableConditionTypeEnum.Number}`),
|
||||
children: [
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.Equal,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.Equal}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.NotEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.NotEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.GreaterThan,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.GreaterThan}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.GreaterThanOrEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.GreaterThanOrEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.LessThan,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.LessThan}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.LessThanOrEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.LessThanOrEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.Between,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.Between}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.NotBetween,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.NotBetween}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.Above,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.Above}`),
|
||||
},
|
||||
{
|
||||
value: TableNumberCompareTypeEnum.Below,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.Below}`),
|
||||
},
|
||||
// {
|
||||
// value: TableNumberCompareTypeEnum.TopN,
|
||||
// label: localeService.t<LocaleKey>(`sheets-table-ui.number.compare.${TableNumberCompareTypeEnum.TopN}`),
|
||||
// },
|
||||
],
|
||||
},
|
||||
{
|
||||
value: TableConditionTypeEnum.Date,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.condition.${TableConditionTypeEnum.Date}`),
|
||||
children: [
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Equal,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Equal}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.NotEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.NotEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.After,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.After}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.AfterOrEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.AfterOrEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Before,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Before}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.BeforeOrEqual,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.BeforeOrEqual}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Between,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Between}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.NotBetween,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.NotBetween}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Today,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Today}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Yesterday,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Yesterday}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Tomorrow,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Tomorrow}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.ThisWeek,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.ThisWeek}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.LastWeek,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.LastWeek}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.NextWeek,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.NextWeek}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.ThisMonth,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.ThisMonth}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.LastMonth,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.LastMonth}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.NextMonth,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.NextMonth}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.ThisYear,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.ThisYear}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.LastYear,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.LastYear}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.NextYear,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.NextYear}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Quarter,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Quarter}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Month,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Month}`),
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function getConditionDateSelect(injector: Injector, dateType?: TableDateCompareTypeEnum) {
|
||||
if (!dateType) {
|
||||
return [];
|
||||
}
|
||||
const localeService = injector.get(LocaleService);
|
||||
switch (dateType) {
|
||||
case TableDateCompareTypeEnum.Quarter:
|
||||
return [
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Q1,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Q1}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Q2,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Q2}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Q3,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Q3}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.Q4,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.Q4}`),
|
||||
},
|
||||
];
|
||||
case TableDateCompareTypeEnum.Month:
|
||||
return [
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M1,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M1}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M2,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M2}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M3,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M3}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M4,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M4}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M5,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M5}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M6,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M6}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M7,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M7}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M8,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M8}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M9,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M9}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M10,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M10}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M11,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M11}`),
|
||||
},
|
||||
{
|
||||
value: TableDateCompareTypeEnum.M12,
|
||||
label: localeService.t<LocaleKey>(`sheets-table-ui.date.compare.${TableDateCompareTypeEnum.M12}`),
|
||||
},
|
||||
];
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export const datePickerSet: Set<IConditionCompareTypeEnum> = new Set([
|
||||
TableDateCompareTypeEnum.Equal,
|
||||
TableDateCompareTypeEnum.NotEqual,
|
||||
TableDateCompareTypeEnum.After,
|
||||
TableDateCompareTypeEnum.AfterOrEqual,
|
||||
TableDateCompareTypeEnum.Before,
|
||||
TableDateCompareTypeEnum.BeforeOrEqual,
|
||||
]);
|
||||
|
||||
export function getSubComponentType(type: TableConditionTypeEnum, compare?: TableStringCompareTypeEnum | TableNumberCompareTypeEnum | TableDateCompareTypeEnum): ConditionSubComponentEnum {
|
||||
if (!compare) {
|
||||
return ConditionSubComponentEnum.None;
|
||||
}
|
||||
if (type === TableConditionTypeEnum.String) {
|
||||
return ConditionSubComponentEnum.Input;
|
||||
} else if (type === TableConditionTypeEnum.Number) {
|
||||
if (compare === TableNumberCompareTypeEnum.Between || compare === TableNumberCompareTypeEnum.NotBetween) {
|
||||
return ConditionSubComponentEnum.Inputs;
|
||||
} else {
|
||||
return ConditionSubComponentEnum.Input;
|
||||
}
|
||||
} else if (type === TableConditionTypeEnum.Date) {
|
||||
if (compare === TableDateCompareTypeEnum.Between || compare === TableDateCompareTypeEnum.NotBetween) {
|
||||
return ConditionSubComponentEnum.DateRange;
|
||||
} else if (compare === TableDateCompareTypeEnum.Quarter || compare === TableDateCompareTypeEnum.Month) {
|
||||
return ConditionSubComponentEnum.Select;
|
||||
} else if (datePickerSet.has(compare as TableDateCompareTypeEnum)) {
|
||||
return ConditionSubComponentEnum.DatePicker;
|
||||
}
|
||||
return ConditionSubComponentEnum.None;
|
||||
}
|
||||
return ConditionSubComponentEnum.None;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export function getInitConditionInfo(tableFilter?: ITableFilterItem) {
|
||||
if (!tableFilter || tableFilter.filterType !== TableColumnFilterTypeEnum.condition) {
|
||||
return {
|
||||
type: TableConditionTypeEnum.String,
|
||||
compare: TableStringCompareTypeEnum.Equal,
|
||||
info: {},
|
||||
};
|
||||
}
|
||||
|
||||
const filterInfo = tableFilter.filterInfo;
|
||||
const { conditionType, compareType } = filterInfo;
|
||||
if (conditionType === TableConditionTypeEnum.Date) {
|
||||
if (compareType === TableDateCompareTypeEnum.Between || compareType === TableDateCompareTypeEnum.NotBetween) {
|
||||
// ensure expectedValue is an array of two dates
|
||||
let dateRange: [Date, Date] | undefined;
|
||||
if (Array.isArray(filterInfo.expectedValue)) {
|
||||
dateRange = filterInfo.expectedValue.map((i) => (typeof i === 'string' ? new Date(i) : i)) as [Date, Date];
|
||||
}
|
||||
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: compareType,
|
||||
info: {
|
||||
dateRange,
|
||||
},
|
||||
};
|
||||
} else if (
|
||||
compareType === TableDateCompareTypeEnum.Today
|
||||
|| compareType === TableDateCompareTypeEnum.Yesterday
|
||||
|| compareType === TableDateCompareTypeEnum.Tomorrow
|
||||
|| compareType === TableDateCompareTypeEnum.ThisWeek
|
||||
|| compareType === TableDateCompareTypeEnum.LastWeek
|
||||
|| compareType === TableDateCompareTypeEnum.NextWeek
|
||||
|| compareType === TableDateCompareTypeEnum.ThisMonth
|
||||
|| compareType === TableDateCompareTypeEnum.LastMonth
|
||||
|| compareType === TableDateCompareTypeEnum.NextMonth
|
||||
|| compareType === TableDateCompareTypeEnum.ThisYear
|
||||
|| compareType === TableDateCompareTypeEnum.LastYear
|
||||
|| compareType === TableDateCompareTypeEnum.NextYear
|
||||
) {
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: compareType,
|
||||
info: {},
|
||||
};
|
||||
} else if (datePickerSet.has(compareType as TableDateCompareTypeEnum)) {
|
||||
// ensure expectedValue is a date or an array of two dates
|
||||
let date: Date | [Date, Date] | undefined;
|
||||
|
||||
if (typeof filterInfo.expectedValue === 'string') {
|
||||
date = new Date(filterInfo.expectedValue);
|
||||
} else if (Array.isArray(filterInfo.expectedValue)) {
|
||||
for (let i = 0; i < filterInfo.expectedValue.length; i++) {
|
||||
if (typeof filterInfo.expectedValue[i] === 'string') {
|
||||
filterInfo.expectedValue[i] = new Date(filterInfo.expectedValue[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: compareType,
|
||||
info: {
|
||||
date,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const quarter = new Set([TableDateCompareTypeEnum.Q1, TableDateCompareTypeEnum.Q2, TableDateCompareTypeEnum.Q3, TableDateCompareTypeEnum.Q4]);
|
||||
if (quarter.has(compareType as TableDateCompareTypeEnum)) {
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: TableDateCompareTypeEnum.Quarter,
|
||||
info: {
|
||||
dateSelect: filterInfo.compareType,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: TableDateCompareTypeEnum.Month,
|
||||
info: {
|
||||
dateSelect: filterInfo.compareType,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
} else if (conditionType === TableConditionTypeEnum.Number) {
|
||||
if (compareType === TableNumberCompareTypeEnum.Between || compareType === TableNumberCompareTypeEnum.NotBetween) {
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: compareType,
|
||||
info: {
|
||||
numberRange: filterInfo.expectedValue,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: compareType,
|
||||
info: {
|
||||
number: filterInfo.expectedValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
} else if (conditionType === TableConditionTypeEnum.String) {
|
||||
return {
|
||||
type: conditionType,
|
||||
compare: compareType,
|
||||
info: {
|
||||
string: filterInfo.expectedValue,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: TableConditionTypeEnum.String,
|
||||
compare: TableStringCompareTypeEnum.Equal,
|
||||
info: {},
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { SpreadsheetSkeleton, UniverRenderingContext } from '@univerjs/engine-render';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import {
|
||||
buildCenteredPlusSegments,
|
||||
hitTestTableControl,
|
||||
TABLE_CONTROL_INSERT_BUTTON_SIZE,
|
||||
} from '../table-controls-util';
|
||||
import { SheetTableControlsShape } from '../table-controls.shape';
|
||||
|
||||
class DrawableSheetTableControlsShape extends SheetTableControlsShape {
|
||||
drawForTest(ctx: UniverRenderingContext): void {
|
||||
this._draw(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
function createCanvasContext(): UniverRenderingContext {
|
||||
return new Proxy({}, {
|
||||
get(_target, property) {
|
||||
if (property === 'measureText') {
|
||||
return () => ({ width: 0 });
|
||||
}
|
||||
|
||||
return () => {};
|
||||
},
|
||||
set() {
|
||||
return true;
|
||||
},
|
||||
}) as UniverRenderingContext;
|
||||
}
|
||||
|
||||
function createSkeleton(): SpreadsheetSkeleton {
|
||||
return {
|
||||
rowHeaderWidth: 40,
|
||||
columnTotalWidth: 400,
|
||||
columnHeaderHeight: 32,
|
||||
rowTotalHeight: 600,
|
||||
getNoMergeCellWithCoordByIndex: () => ({
|
||||
startX: 40,
|
||||
startY: 60,
|
||||
}),
|
||||
} as unknown as SpreadsheetSkeleton;
|
||||
}
|
||||
|
||||
describe('table controls geometry', () => {
|
||||
it('hit-tests the topmost region first', () => {
|
||||
const hit = hitTestTableControl([
|
||||
{ type: 'anchor-main', tableId: 't1', left: 0, top: 0, width: 100, height: 24 },
|
||||
{ type: 'anchor-menu-toggle', tableId: 't1', left: 76, top: 0, width: 24, height: 24 },
|
||||
], 80, 12);
|
||||
|
||||
expect(hit?.type).toBe('anchor-menu-toggle');
|
||||
});
|
||||
|
||||
it('misses points outside all regions', () => {
|
||||
expect(hitTestTableControl([
|
||||
{ type: 'insert-row', tableId: 't1', index: 2, left: 10, top: 10, width: TABLE_CONTROL_INSERT_BUTTON_SIZE, height: TABLE_CONTROL_INSERT_BUTTON_SIZE },
|
||||
], 0, 0)).toBeNull();
|
||||
});
|
||||
|
||||
it('builds centered plus segments around the insert button center', () => {
|
||||
const segments = buildCenteredPlusSegments(20, 30, 8);
|
||||
|
||||
expect(segments).toEqual([
|
||||
{ fromX: 16, fromY: 30, toX: 24, toY: 30 },
|
||||
{ fromX: 20, fromY: 26, toX: 20, toY: 34 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps menu action rows out of canvas hit testing', () => {
|
||||
const shape = new DrawableSheetTableControlsShape('table-controls', createSkeleton);
|
||||
shape.setItems([{
|
||||
tableId: 'table-orders',
|
||||
tableName: 'Orders',
|
||||
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 1 },
|
||||
fill: '#1f5eff',
|
||||
text: '#ffffff',
|
||||
}]);
|
||||
shape.setOpenedMenuTableId('table-orders');
|
||||
|
||||
shape.drawForTest(createCanvasContext());
|
||||
|
||||
expect(shape.hitTest(56, 170)).toBeNull();
|
||||
});
|
||||
|
||||
it('activates the hovered insert region for row and column insertion', () => {
|
||||
const shape = new DrawableSheetTableControlsShape('table-controls', createSkeleton);
|
||||
shape.setItems([{
|
||||
tableId: 'table-orders',
|
||||
tableName: 'Orders',
|
||||
range: { startRow: 0, endRow: 3, startColumn: 0, endColumn: 1 },
|
||||
fill: '#1f5eff',
|
||||
text: '#ffffff',
|
||||
}]);
|
||||
shape.setHoveredInsertRegion({
|
||||
type: 'insert-row',
|
||||
tableId: 'table-orders',
|
||||
index: 2,
|
||||
left: 100,
|
||||
top: 120,
|
||||
width: TABLE_CONTROL_INSERT_BUTTON_SIZE,
|
||||
height: TABLE_CONTROL_INSERT_BUTTON_SIZE,
|
||||
});
|
||||
|
||||
shape.drawForTest(createCanvasContext());
|
||||
|
||||
expect(shape.hitTest(111, 131)).toMatchObject({
|
||||
type: 'insert-row',
|
||||
tableId: 'table-orders',
|
||||
index: 2,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,126 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IMouseEvent } from '@univerjs/engine-render';
|
||||
import type { IOpenTableFilterPanelOperationParams } from '../../../commands/operations/open-table-filter-dialog.opration';
|
||||
import { CommandType, ICommandService, Univer } from '@univerjs/core';
|
||||
import { SheetsTableButtonStateEnum } from '@univerjs/sheets-table';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import { OpenTableFilterPanelOperation } from '../../../commands/operations/open-table-filter-dialog.opration';
|
||||
import { SheetsTableFilterButtonShape } from '../table-filter-button.shape';
|
||||
|
||||
function createPointerEvent(button: number): IMouseEvent {
|
||||
return { button } as IMouseEvent;
|
||||
}
|
||||
|
||||
async function waitForFilterButtonDelay(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 230));
|
||||
await Promise.resolve();
|
||||
}
|
||||
|
||||
describe('sheet table filter button shape', () => {
|
||||
let univer: Univer | undefined;
|
||||
|
||||
afterEach(() => {
|
||||
univer?.dispose();
|
||||
univer = undefined;
|
||||
});
|
||||
|
||||
function createShape(executedParams: IOpenTableFilterPanelOperationParams[]): SheetsTableFilterButtonShape {
|
||||
univer = new Univer();
|
||||
const commandService = univer.__getInjector().get(ICommandService);
|
||||
commandService.registerCommand({
|
||||
id: OpenTableFilterPanelOperation.id,
|
||||
type: CommandType.OPERATION,
|
||||
handler: (_accessor, params?: IOpenTableFilterPanelOperationParams) => {
|
||||
if (params) {
|
||||
executedParams.push(params);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
});
|
||||
|
||||
return new SheetsTableFilterButtonShape('filter-button', {
|
||||
width: 16,
|
||||
height: 16,
|
||||
cellWidth: 72,
|
||||
cellHeight: 28,
|
||||
iconColor: '#ffffff',
|
||||
hoverBackground: '#eeeeee',
|
||||
hoverIconColor: '#111111',
|
||||
filterParams: {
|
||||
row: 3,
|
||||
col: 4,
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
buttonState: SheetsTableButtonStateEnum.FilterNoneSortNone,
|
||||
tableId: 'table-orders',
|
||||
},
|
||||
}, commandService);
|
||||
}
|
||||
|
||||
it('opens the table filter panel with the current cell and table parameters on primary pointer down', async () => {
|
||||
const executedParams: IOpenTableFilterPanelOperationParams[] = [];
|
||||
const shape = createShape(executedParams);
|
||||
|
||||
shape.onPointerDown(createPointerEvent(0));
|
||||
await waitForFilterButtonDelay();
|
||||
|
||||
expect(executedParams).toEqual([{
|
||||
row: 3,
|
||||
col: 4,
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
tableId: 'table-orders',
|
||||
}]);
|
||||
});
|
||||
|
||||
it('opens the filter panel with updated cell and table parameters after the shape is reused', async () => {
|
||||
const executedParams: IOpenTableFilterPanelOperationParams[] = [];
|
||||
const shape = createShape(executedParams);
|
||||
|
||||
shape.setShapeProps({
|
||||
filterParams: {
|
||||
row: 8,
|
||||
col: 2,
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
buttonState: SheetsTableButtonStateEnum.FilterNoneSortNone,
|
||||
tableId: 'table-archive',
|
||||
},
|
||||
});
|
||||
shape.onPointerDown(createPointerEvent(0));
|
||||
await waitForFilterButtonDelay();
|
||||
|
||||
expect(executedParams).toEqual([{
|
||||
row: 8,
|
||||
col: 2,
|
||||
unitId: 'unit-1',
|
||||
subUnitId: 'sheet-1',
|
||||
tableId: 'table-archive',
|
||||
}]);
|
||||
});
|
||||
|
||||
it('keeps the filter panel closed for secondary pointer down', async () => {
|
||||
const executedParams: IOpenTableFilterPanelOperationParams[] = [];
|
||||
const shape = createShape(executedParams);
|
||||
|
||||
shape.onPointerDown(createPointerEvent(2));
|
||||
await waitForFilterButtonDelay();
|
||||
|
||||
expect(executedParams).toEqual([]);
|
||||
});
|
||||
});
|
||||
71
packages/sheets-table-ui/src/views/widgets/drawings.ts
Normal file
71
packages/sheets-table-ui/src/views/widgets/drawings.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { UniverRenderingContext2D } from '@univerjs/engine-render';
|
||||
import { Rect } from '@univerjs/engine-render';
|
||||
|
||||
const BUTTON_VIEWPORT = 16;
|
||||
|
||||
export const PIVOT_BUTTON_EMPTY = new Path2D('M3.30363 3C2.79117 3 2.51457 3.60097 2.84788 3.99024L6.8 8.60593V12.5662C6.8 12.7184 6.8864 12.8575 7.02289 12.9249L8.76717 13.7863C8.96655 13.8847 9.2 13.7396 9.2 13.5173V8.60593L13.1521 3.99024C13.4854 3.60097 13.2088 3 12.6964 3H3.30363Z');
|
||||
|
||||
export class TableButton {
|
||||
static drawNoSetting(ctx: UniverRenderingContext2D, size: number, fgColor: string, bgColor: string): void {
|
||||
ctx.save();
|
||||
|
||||
Rect.drawWith(ctx, {
|
||||
radius: 2,
|
||||
width: BUTTON_VIEWPORT,
|
||||
height: BUTTON_VIEWPORT,
|
||||
fill: bgColor,
|
||||
});
|
||||
|
||||
ctx.lineCap = 'square';
|
||||
ctx.strokeStyle = fgColor;
|
||||
ctx.scale(size / BUTTON_VIEWPORT, size / BUTTON_VIEWPORT);
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.moveTo(3, 4);
|
||||
ctx.lineTo(13, 4);
|
||||
ctx.moveTo(4.5, 8);
|
||||
ctx.lineTo(11.5, 8);
|
||||
ctx.moveTo(6, 12);
|
||||
ctx.lineTo(10, 12);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
static drawIconByPath(ctx: UniverRenderingContext2D, pathData: string[], fgColor: string, bgColor: string): void {
|
||||
ctx.save();
|
||||
ctx.strokeStyle = fgColor;
|
||||
ctx.fillStyle = bgColor;
|
||||
|
||||
Rect.drawWith(ctx, {
|
||||
radius: 2,
|
||||
width: BUTTON_VIEWPORT,
|
||||
height: BUTTON_VIEWPORT,
|
||||
fill: bgColor,
|
||||
});
|
||||
|
||||
pathData.forEach((d) => {
|
||||
const path = new Path2D(d);
|
||||
ctx.fillStyle = fgColor;
|
||||
ctx.fill(path, 'evenodd');
|
||||
});
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
41
packages/sheets-table-ui/src/views/widgets/icons.ts
Normal file
41
packages/sheets-table-ui/src/views/widgets/icons.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const filterPartial = [
|
||||
'M3.30363 3C2.79117 3 2.51457 3.60097 2.84788 3.99024L6.8 8.60593V12.5662C6.8 12.7184 6.8864 12.8575 7.02289 12.9249L8.76717 13.7863C8.96655 13.8847 9.2 13.7396 9.2 13.5173V8.60593L13.1521 3.99024C13.4854 3.60097 13.2088 3 12.6964 3H3.30363Z',
|
||||
];
|
||||
|
||||
export const filteredSortAsc = [
|
||||
'M12.4008 13.1831C12.6907 13.1831 12.9258 12.9481 12.9258 12.6581V4.60873L14.013 5.69597C14.218 5.901 14.5505 5.901 14.7555 5.69597C14.9605 5.49094 14.9605 5.15853 14.7555 4.95351L12.7721 2.97017C12.5671 2.76515 12.2347 2.76515 12.0297 2.97017L10.0463 4.95351C9.84132 5.15853 9.84132 5.49094 10.0463 5.69597C10.2514 5.901 10.5838 5.901 10.7888 5.69597L11.8758 4.60901V12.6581C11.8758 12.9481 12.1108 13.1831 12.4008 13.1831Z',
|
||||
'M1.28069 4.85447C0.842195 4.33439 1.21191 3.5391 1.89218 3.5391H8.59333C9.2736 3.5391 9.64331 4.33439 9.20482 4.85447L6.51052 8.0501V11.6601C6.51052 12.2245 5.94174 12.6114 5.41683 12.404L4.48092 12.0343C4.1756 11.9136 3.97498 11.6187 3.97498 11.2904V8.0501L1.28069 4.85447Z',
|
||||
];
|
||||
|
||||
export const filteredSortDesc = [
|
||||
'M12.4008 2.81641C12.6907 2.81641 12.9258 3.05146 12.9258 3.34141V11.3908L14.013 10.3036C14.218 10.0986 14.5505 10.0986 14.7555 10.3036C14.9605 10.5086 14.9605 10.841 14.7555 11.046L12.7721 13.0294C12.5671 13.2344 12.2347 13.2344 12.0297 13.0294L10.0463 11.046C9.84132 10.841 9.84132 10.5086 10.0463 10.3036C10.2514 10.0986 10.5838 10.0986 10.7888 10.3036L11.8758 11.3905V3.34141C11.8758 3.05146 12.1108 2.81641 12.4008 2.81641Z',
|
||||
'M1.28069 4.85444C0.842195 4.33435 1.21191 3.53906 1.89218 3.53906H8.59333C9.2736 3.53906 9.64331 4.33435 9.20482 4.85443L6.51052 8.05006V11.6601C6.51052 12.2245 5.94174 12.6113 5.41683 12.404L4.48092 12.0342C4.1756 11.9136 3.97498 11.6186 3.97498 11.2903V8.05006L1.28069 4.85444Z',
|
||||
];
|
||||
|
||||
export const filterNoneSortAsc = [
|
||||
'M11.9003 13.7046C11.9003 13.9969 11.6633 14.2339 11.371 14.2339C11.0787 14.2339 10.8417 13.9969 10.8417 13.7046V3.57272L9.74577 4.66862C9.53906 4.87534 9.20391 4.87534 8.9972 4.66862C8.79048 4.46191 8.79048 4.12676 8.9972 3.92005L10.9969 1.92039C11.2036 1.71368 11.5387 1.71368 11.7454 1.92039L13.7451 3.92005C13.9518 4.12676 13.9518 4.46191 13.7451 4.66862C13.5384 4.87534 13.2032 4.87534 12.9965 4.66862L11.9003 3.57243V13.7046Z',
|
||||
'M2.69779 10.0113C2.40546 10.0113 2.16847 9.77429 2.16847 9.48196C2.16847 9.18962 2.40546 8.95264 2.69779 8.95264H6.67804C6.89213 8.95264 7.08514 9.0816 7.16707 9.2794C7.249 9.47719 7.20371 9.70486 7.05233 9.85624L3.97569 12.9329H6.67804C6.97038 12.9329 7.20736 13.1699 7.20736 13.4622C7.20736 13.7545 6.97038 13.9915 6.67804 13.9915H2.69779C2.4837 13.9915 2.29069 13.8626 2.20876 13.6648C2.12684 13.467 2.17212 13.2393 2.32351 13.0879L5.40015 10.0113H2.69779Z',
|
||||
'M5.51638 2.58693C5.23363 1.81542 4.14248 1.81543 3.85973 2.58693L2.13245 7.29995C2.03185 7.57443 2.17281 7.87849 2.4473 7.97909C2.72178 8.07969 3.02584 7.93872 3.12644 7.66424L3.64346 6.25351L3.64504 6.25351H5.73266L6.24968 7.66424C6.35027 7.93872 6.65433 8.07969 6.92882 7.97909C7.2033 7.87849 7.34426 7.57443 7.24367 7.29995L5.51638 2.58693ZM5.34467 5.19487L4.68806 3.40325L4.03144 5.19487H5.34467Z',
|
||||
];
|
||||
|
||||
export const filterNoneSortDesc = [
|
||||
'M11.9003 2.29495C11.9003 2.00261 11.6633 1.76562 11.371 1.76562C11.0787 1.76562 10.8417 2.00261 10.8417 2.29495V12.4268L9.74577 11.3309C9.53906 11.1242 9.20391 11.1242 8.9972 11.3309C8.79048 11.5376 8.79048 11.8727 8.9972 12.0795L10.9969 14.0791C11.2036 14.2858 11.5387 14.2858 11.7454 14.0791L13.7451 12.0795C13.9518 11.8727 13.9518 11.5376 13.7451 11.3309C13.5384 11.1242 13.2032 11.1242 12.9965 11.3309L11.9003 12.4271V2.29495Z',
|
||||
'M2.69792 10.0113C2.40558 10.0113 2.16859 9.77429 2.16859 9.48196C2.16859 9.18962 2.40558 8.95264 2.69792 8.95264H6.67816C6.89225 8.95264 7.08526 9.0816 7.16719 9.2794C7.24912 9.47719 7.20384 9.70486 7.05245 9.85624L3.97581 12.9329H6.67816C6.9705 12.9329 7.20749 13.1699 7.20749 13.4622C7.20749 13.7545 6.9705 13.9915 6.67816 13.9915H2.69792C2.48383 13.9915 2.29082 13.8626 2.20889 13.6648C2.12696 13.467 2.17224 13.2393 2.32363 13.0879L5.40027 10.0113H2.69792Z',
|
||||
'M5.5165 2.58693C5.23375 1.81542 4.1426 1.81543 3.85985 2.58693L2.13257 7.29995C2.03197 7.57443 2.17294 7.8785 2.44742 7.97909C2.7219 8.07969 3.02596 7.93872 3.12656 7.66424L3.64358 6.25351L3.64516 6.25351H5.73278L6.2498 7.66424C6.35039 7.93872 6.65446 8.07969 6.92894 7.97909C7.20342 7.8785 7.34438 7.57443 7.24379 7.29995L5.5165 2.58693ZM5.34479 5.19487L4.68818 3.40325L4.03156 5.19487H5.34479Z',
|
||||
];
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const TABLE_CONTROL_ANCHOR_HEIGHT = 28;
|
||||
export const TABLE_CONTROL_ANCHOR_RADIUS = 14;
|
||||
export const TABLE_CONTROL_INSERT_BUTTON_SIZE = 22;
|
||||
export const TABLE_CONTROL_TOP_GAP_SIZE = 32;
|
||||
|
||||
export type TableControlHitType =
|
||||
| 'anchor-main'
|
||||
| 'anchor-menu-toggle'
|
||||
| 'insert-row'
|
||||
| 'insert-column';
|
||||
|
||||
export interface ITableControlHitRegion {
|
||||
type: TableControlHitType;
|
||||
tableId: string;
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
export interface ITableControlLineSegment {
|
||||
fromX: number;
|
||||
fromY: number;
|
||||
toX: number;
|
||||
toY: number;
|
||||
}
|
||||
|
||||
export function isPointInTableControlRegion(region: ITableControlHitRegion, x: number, y: number): boolean {
|
||||
return x >= region.left && x <= region.left + region.width && y >= region.top && y <= region.top + region.height;
|
||||
}
|
||||
|
||||
export function hitTestTableControl(regions: ITableControlHitRegion[], x: number, y: number): ITableControlHitRegion | null {
|
||||
for (let i = regions.length - 1; i >= 0; i--) {
|
||||
if (isPointInTableControlRegion(regions[i], x, y)) {
|
||||
return regions[i];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function buildCenteredPlusSegments(centerX: number, centerY: number, size: number): [ITableControlLineSegment, ITableControlLineSegment] {
|
||||
const halfSize = size / 2;
|
||||
|
||||
return [
|
||||
{ fromX: centerX - halfSize, fromY: centerY, toX: centerX + halfSize, toY: centerY },
|
||||
{ fromX: centerX, fromY: centerY - halfSize, toX: centerX, toY: centerY + halfSize },
|
||||
];
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/**
|
||||
* 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 { IShapeProps, SpreadsheetSkeleton, UniverRenderingContext, Vector2 } from '@univerjs/engine-render';
|
||||
import type { ITableControlHitRegion } from './table-controls-util';
|
||||
import { DEFAULT_FONTFACE_PLANE, Shape } from '@univerjs/engine-render';
|
||||
import {
|
||||
buildCenteredPlusSegments,
|
||||
hitTestTableControl,
|
||||
TABLE_CONTROL_ANCHOR_HEIGHT,
|
||||
TABLE_CONTROL_ANCHOR_RADIUS,
|
||||
} from './table-controls-util';
|
||||
|
||||
const ANCHOR_MIN_WIDTH = 122;
|
||||
const ANCHOR_MAX_WIDTH = 240;
|
||||
const ANCHOR_PADDING_X = 12;
|
||||
const ANCHOR_TOGGLE_WIDTH = 30;
|
||||
const ANCHOR_OFFSET_Y = 0;
|
||||
const ANCHOR_BORDER = 'rgba(0, 0, 0, 0.22)';
|
||||
const ANCHOR_DIVIDER = 'rgba(0, 0, 0, 0.20)';
|
||||
const ANCHOR_TOGGLE_BG_ACTIVE = 'rgba(0, 0, 0, 0.12)';
|
||||
const INSERT_BUTTON_VISUAL_SIZE = 18;
|
||||
const INSERT_BUTTON_PLUS_SIZE = 8;
|
||||
|
||||
export interface ITableControlRenderItem {
|
||||
tableId: string;
|
||||
tableName: string;
|
||||
range: { startRow: number; endRow: number; startColumn: number; endColumn: number };
|
||||
fill: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export class SheetTableControlsShape extends Shape<IShapeProps> {
|
||||
private _items: ITableControlRenderItem[] = [];
|
||||
private _regions: ITableControlHitRegion[] = [];
|
||||
private _openedMenuTableId: string | null = null;
|
||||
private _hoveredRegion: ITableControlHitRegion | null = null;
|
||||
private _hoveredInsertRegion: ITableControlHitRegion | null = null;
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
private readonly _getSkeleton: () => SpreadsheetSkeleton | null | undefined
|
||||
) {
|
||||
super(key, {
|
||||
evented: true,
|
||||
fill: 'rgba(0, 0, 0, 0)',
|
||||
zIndex: 5001,
|
||||
});
|
||||
}
|
||||
|
||||
setItems(items: ITableControlRenderItem[]): void {
|
||||
this._items = items;
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
setOpenedMenuTableId(tableId: string | null): void {
|
||||
if (this._openedMenuTableId === tableId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._openedMenuTableId = tableId;
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
getOpenedMenuTableId(): string | null {
|
||||
return this._openedMenuTableId;
|
||||
}
|
||||
|
||||
getAnchorRegion(tableId: string): ITableControlHitRegion | null {
|
||||
return this._regions.find((region) => region.type === 'anchor-main' && region.tableId === tableId) ?? null;
|
||||
}
|
||||
|
||||
setHoveredRegion(region: ITableControlHitRegion | null): void {
|
||||
if (this._hoveredRegion === region) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hoveredRegion = region;
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
setHoveredInsertRegion(region: ITableControlHitRegion | null): void {
|
||||
if (this._hoveredInsertRegion === region) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._hoveredInsertRegion = region;
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
hitTest(x: number, y: number): ITableControlHitRegion | null {
|
||||
return hitTestTableControl(this._regions, x, y);
|
||||
}
|
||||
|
||||
override isHit(coord: Vector2): boolean {
|
||||
return this.hitTest(coord.x, coord.y) != null;
|
||||
}
|
||||
|
||||
refreshBounds(): void {
|
||||
const skeleton = this._getSkeleton();
|
||||
if (!skeleton) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.show();
|
||||
this.transformByState({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: skeleton.rowHeaderWidth + skeleton.columnTotalWidth,
|
||||
height: skeleton.columnHeaderHeight + skeleton.rowTotalHeight,
|
||||
});
|
||||
}
|
||||
|
||||
protected override _draw(ctx: UniverRenderingContext): void {
|
||||
this._regions = [];
|
||||
const skeleton = this._getSkeleton();
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
for (const item of this._items) {
|
||||
this._drawAnchor(ctx, skeleton, item);
|
||||
}
|
||||
|
||||
if (this._hoveredInsertRegion) {
|
||||
const item = this._items.find((renderItem) => renderItem.tableId === this._hoveredInsertRegion?.tableId);
|
||||
this._drawInsertButton(ctx, this._hoveredInsertRegion, item?.fill ?? '#355bb7');
|
||||
this._regions.push(this._hoveredInsertRegion);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private _drawAnchor(ctx: UniverRenderingContext, skeleton: SpreadsheetSkeleton, item: ITableControlRenderItem): void {
|
||||
const position = skeleton.getNoMergeCellWithCoordByIndex(item.range.startRow, item.range.startColumn);
|
||||
const left = position.startX;
|
||||
const rawTop = position.startY - TABLE_CONTROL_ANCHOR_HEIGHT - ANCHOR_OFFSET_Y;
|
||||
const top = Math.max(0, rawTop);
|
||||
const width = Math.max(ANCHOR_MIN_WIDTH, Math.min(ANCHOR_MAX_WIDTH, item.tableName.length * 8.5 + ANCHOR_PADDING_X * 2 + ANCHOR_TOGGLE_WIDTH));
|
||||
const toggleRegion: ITableControlHitRegion = {
|
||||
type: 'anchor-menu-toggle',
|
||||
tableId: item.tableId,
|
||||
left: left + width - ANCHOR_TOGGLE_WIDTH,
|
||||
top,
|
||||
width: ANCHOR_TOGGLE_WIDTH,
|
||||
height: TABLE_CONTROL_ANCHOR_HEIGHT,
|
||||
};
|
||||
|
||||
ctx.save();
|
||||
ctx.translateWithPrecision(left, top);
|
||||
this._drawTopRoundedRect(ctx, width, TABLE_CONTROL_ANCHOR_HEIGHT, TABLE_CONTROL_ANCHOR_RADIUS, item.fill, ANCHOR_BORDER);
|
||||
this._drawAnchorToggle(ctx, width, item.text, this._openedMenuTableId === item.tableId || this._isSameRegion(this._hoveredRegion, toggleRegion));
|
||||
ctx.font = `600 13px ${DEFAULT_FONTFACE_PLANE}`;
|
||||
ctx.fillStyle = item.text;
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(item.tableName, ANCHOR_PADDING_X, TABLE_CONTROL_ANCHOR_HEIGHT / 2);
|
||||
ctx.restore();
|
||||
|
||||
this._regions.push({
|
||||
type: 'anchor-main',
|
||||
tableId: item.tableId,
|
||||
left,
|
||||
top,
|
||||
width,
|
||||
height: TABLE_CONTROL_ANCHOR_HEIGHT,
|
||||
});
|
||||
this._regions.push(toggleRegion);
|
||||
}
|
||||
|
||||
private _drawAnchorToggle(ctx: UniverRenderingContext, anchorWidth: number, color: string, active: boolean): void {
|
||||
const toggleLeft = anchorWidth - ANCHOR_TOGGLE_WIDTH;
|
||||
if (active) {
|
||||
this._drawRightTopRoundedRect(ctx, toggleLeft, anchorWidth, TABLE_CONTROL_ANCHOR_HEIGHT, TABLE_CONTROL_ANCHOR_RADIUS, ANCHOR_TOGGLE_BG_ACTIVE);
|
||||
}
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = ANCHOR_DIVIDER;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.moveTo(toggleLeft + 0.5, 5);
|
||||
ctx.lineTo(toggleLeft + 0.5, TABLE_CONTROL_ANCHOR_HEIGHT - 5);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
const centerX = anchorWidth - ANCHOR_TOGGLE_WIDTH / 2;
|
||||
const centerY = TABLE_CONTROL_ANCHOR_HEIGHT / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 1.6;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.moveTo(centerX - 5, centerY - 4);
|
||||
ctx.lineTo(centerX + 5, centerY - 4);
|
||||
ctx.moveTo(centerX - 5, centerY);
|
||||
ctx.lineTo(centerX + 5, centerY);
|
||||
ctx.moveTo(centerX - 5, centerY + 4);
|
||||
ctx.lineTo(centerX + 5, centerY + 4);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private _drawTopRoundedRect(ctx: UniverRenderingContext, width: number, height: number, radius: number, fill: string, stroke?: string): void {
|
||||
const r = Math.min(radius, width / 2, height);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, height);
|
||||
ctx.lineTo(0, r);
|
||||
ctx.arcTo(0, 0, r, 0, r);
|
||||
ctx.lineTo(width - r, 0);
|
||||
ctx.arcTo(width, 0, width, r, r);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
if (stroke) {
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
private _drawRightTopRoundedRect(ctx: UniverRenderingContext, left: number, width: number, height: number, radius: number, fill: string): void {
|
||||
const r = Math.min(radius, width - left, height);
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(left, height);
|
||||
ctx.lineTo(left, 0);
|
||||
ctx.lineTo(width - r, 0);
|
||||
ctx.arcTo(width, 0, width, r, r);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private _drawInsertButton(ctx: UniverRenderingContext, region: ITableControlHitRegion, fill: string): void {
|
||||
const centerX = region.left + region.width / 2;
|
||||
const centerY = region.top + region.height / 2;
|
||||
const radius = INSERT_BUTTON_VISUAL_SIZE / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = fill;
|
||||
ctx.stroke();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = fill;
|
||||
ctx.lineWidth = 1;
|
||||
ctx.lineCap = 'round';
|
||||
for (const segment of buildCenteredPlusSegments(centerX, centerY, INSERT_BUTTON_PLUS_SIZE)) {
|
||||
ctx.moveTo(segment.fromX, segment.fromY);
|
||||
ctx.lineTo(segment.toX, segment.toY);
|
||||
}
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private _isSameRegion(a: ITableControlHitRegion | null, b: ITableControlHitRegion): boolean {
|
||||
return Boolean(a && a.type === b.type && a.tableId === b.tableId && a.index === b.index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* 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 { IMouseEvent, IPointerEvent, IShapeProps, UniverRenderingContext2D } from '@univerjs/engine-render';
|
||||
import type { SheetsTableButtonStateEnum } from '@univerjs/sheets-table';
|
||||
import type { IOpenTableFilterPanelOperationParams } from '../../commands/operations/open-table-filter-dialog.opration';
|
||||
import { ICommandService } from '@univerjs/core';
|
||||
import { Shape } from '@univerjs/engine-render';
|
||||
import { OpenTableFilterPanelOperation } from '../../commands/operations/open-table-filter-dialog.opration';
|
||||
|
||||
export const FILTER_ICON_SIZE = 16;
|
||||
export const FILTER_ICON_PADDING = 1;
|
||||
const FILTER_TRIGGER_HOVER_RADIUS = 4;
|
||||
|
||||
export interface ISheetsTableFilterButtonShapeProps extends IShapeProps {
|
||||
cellWidth: number;
|
||||
cellHeight: number;
|
||||
iconColor: string;
|
||||
hoverBackground: string;
|
||||
hoverIconColor: string;
|
||||
filterParams: { row: number; col: number; unitId: string; subUnitId: string; buttonState: SheetsTableButtonStateEnum; tableId: string };
|
||||
}
|
||||
|
||||
/**
|
||||
* The widget to render a filter button on canvas.
|
||||
*/
|
||||
export class SheetsTableFilterButtonShape extends Shape<ISheetsTableFilterButtonShapeProps> {
|
||||
private _cellWidth: number = 0;
|
||||
private _cellHeight: number = 0;
|
||||
|
||||
private _filterParams?: { row: number; col: number; unitId: string; subUnitId: string; buttonState: SheetsTableButtonStateEnum; tableId: string };
|
||||
private _iconColor = '#fff';
|
||||
private _hoverBackground = 'rgba(255, 255, 255, 0.92)';
|
||||
private _hoverIconColor = '#202124';
|
||||
|
||||
private _hovered = false;
|
||||
|
||||
constructor(
|
||||
key: string,
|
||||
props: ISheetsTableFilterButtonShapeProps,
|
||||
@ICommandService private readonly _commandService: ICommandService
|
||||
) {
|
||||
super(key, props);
|
||||
|
||||
this.setShapeProps(props);
|
||||
|
||||
// Here we need to make sure that the event is on the rectangle range.
|
||||
this.onPointerDown$.subscribeEvent((evt) => this.onPointerDown(evt));
|
||||
this.onPointerEnter$.subscribeEvent(() => this.onPointerEnter());
|
||||
this.onPointerLeave$.subscribeEvent(() => this.onPointerLeave());
|
||||
}
|
||||
|
||||
setShapeProps(props: Partial<ISheetsTableFilterButtonShapeProps>): void {
|
||||
if (typeof props.cellHeight !== 'undefined') {
|
||||
this._cellHeight = props.cellHeight;
|
||||
}
|
||||
|
||||
if (typeof props.cellWidth !== 'undefined') {
|
||||
this._cellWidth = props.cellWidth;
|
||||
}
|
||||
|
||||
if (typeof props.filterParams !== 'undefined') {
|
||||
this._filterParams = props.filterParams;
|
||||
}
|
||||
|
||||
if (typeof props.iconColor !== 'undefined') {
|
||||
this._iconColor = props.iconColor;
|
||||
}
|
||||
|
||||
if (typeof props.hoverBackground !== 'undefined') {
|
||||
this._hoverBackground = props.hoverBackground;
|
||||
}
|
||||
|
||||
if (typeof props.hoverIconColor !== 'undefined') {
|
||||
this._hoverIconColor = props.hoverIconColor;
|
||||
}
|
||||
|
||||
this.transformByState({
|
||||
width: props.width!,
|
||||
height: props.height!,
|
||||
});
|
||||
}
|
||||
|
||||
protected override _draw(ctx: UniverRenderingContext2D): void {
|
||||
const cellHeight = this._cellHeight;
|
||||
const cellWidth = this._cellWidth;
|
||||
|
||||
const left = FILTER_ICON_SIZE - cellWidth;
|
||||
const top = FILTER_ICON_SIZE - cellHeight;
|
||||
|
||||
ctx.save();
|
||||
|
||||
const cellRegion = new Path2D();
|
||||
cellRegion.rect(left, top, cellWidth, cellHeight);
|
||||
ctx.clip(cellRegion);
|
||||
|
||||
if (this._hovered) {
|
||||
ctx.save();
|
||||
ctx.fillStyle = this._hoverBackground;
|
||||
ctx.beginPath();
|
||||
ctx.roundRect?.(0, 0, FILTER_ICON_SIZE, FILTER_ICON_SIZE, FILTER_TRIGGER_HOVER_RADIUS);
|
||||
if (!ctx.roundRect) {
|
||||
ctx.rect(0, 0, FILTER_ICON_SIZE, FILTER_ICON_SIZE);
|
||||
}
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
this._drawChevron(ctx, this._hovered ? this._hoverIconColor : this._iconColor);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
private _drawChevron(ctx: UniverRenderingContext2D, color: string): void {
|
||||
const centerX = FILTER_ICON_SIZE / 2;
|
||||
const centerY = FILTER_ICON_SIZE / 2;
|
||||
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = color;
|
||||
ctx.lineWidth = 1.8;
|
||||
ctx.lineCap = 'round';
|
||||
ctx.lineJoin = 'round';
|
||||
ctx.moveTo(centerX - 4.5, centerY - 2.5);
|
||||
ctx.lineTo(centerX, centerY + 2);
|
||||
ctx.lineTo(centerX + 4.5, centerY - 2.5);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
onPointerDown(evt: IPointerEvent | IMouseEvent): void {
|
||||
// Right click not trigger this event.
|
||||
if (evt.button === 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { row, col, unitId, subUnitId, tableId } = this._filterParams!;
|
||||
if (!this._commandService.hasCommand(OpenTableFilterPanelOperation.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
const cmdParams: IOpenTableFilterPanelOperationParams = {
|
||||
row,
|
||||
col,
|
||||
unitId,
|
||||
subUnitId,
|
||||
tableId,
|
||||
};
|
||||
this._commandService.executeCommand(OpenTableFilterPanelOperation.id, cmdParams);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
onPointerEnter(): void {
|
||||
this._hovered = true;
|
||||
this.makeDirty(true);
|
||||
}
|
||||
|
||||
onPointerLeave(): void {
|
||||
this._hovered = false;
|
||||
this.makeDirty(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user