snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled
This commit is contained in:
41
packages/sheets-crosshair-highlight/README.md
Normal file
41
packages/sheets-crosshair-highlight/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# @univerjs/sheets-crosshair-highlight
|
||||
|
||||
[](https://npmjs.com/package/@univerjs/sheets-crosshair-highlight)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-crosshair-highlight)
|
||||
[](https://npmjs.com/package/@univerjs/sheets-crosshair-highlight)
|
||||
|
||||
`@univerjs/sheets-crosshair-highlight` adds crosshair highlighting for the active row and column in Univer Sheets.
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package | UMD global | CSS | Locales | Facade entry |
|
||||
| --- | --- | :---: | :---: | :---: |
|
||||
| `@univerjs/sheets-crosshair-highlight` | `UniverSheetsCrosshairHighlight` | Yes | Yes | Yes |
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
pnpm add @univerjs/sheets-crosshair-highlight
|
||||
# or
|
||||
npm install @univerjs/sheets-crosshair-highlight
|
||||
```
|
||||
|
||||
Keep all `@univerjs/*` packages on the same version.
|
||||
|
||||
## Usage
|
||||
|
||||
```ts
|
||||
import '@univerjs/sheets-crosshair-highlight/lib/index.css';
|
||||
import EnUS from '@univerjs/sheets-crosshair-highlight/locale/en-US';
|
||||
import { UniverSheetsCrosshairHighlightPlugin } from '@univerjs/sheets-crosshair-highlight';
|
||||
|
||||
univer.registerPlugin(UniverSheetsCrosshairHighlightPlugin);
|
||||
|
||||
// Merge EnUS into your Univer locale map when this package contributes UI text.
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [Documentation](https://docs.univer.ai)
|
||||
- [NPM package](https://npmjs.com/package/@univerjs/sheets-crosshair-highlight)
|
||||
- [GitHub repository](https://github.com/dream-num/univer)
|
||||
105
packages/sheets-crosshair-highlight/package.json
Normal file
105
packages/sheets-crosshair-highlight/package.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "@univerjs/sheets-crosshair-highlight",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": false,
|
||||
"description": "Crosshair highlight plugin for Univer Sheets.",
|
||||
"author": "DreamNum Co., Ltd. <developer@univer.ai>",
|
||||
"license": "Apache-2.0",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/univer"
|
||||
},
|
||||
"homepage": "https://univer.ai",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/dream-num/univer"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/dream-num/univer/issues"
|
||||
},
|
||||
"keywords": [
|
||||
"univer",
|
||||
"sheets",
|
||||
"crosshair",
|
||||
"highlight",
|
||||
"plugin"
|
||||
],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*",
|
||||
"./locale/*": "./src/locale/*.ts",
|
||||
"./facade": "./src/facade/index.ts"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"types": "./lib/types/index.d.ts",
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"main": "./lib/es/index.js",
|
||||
"module": "./lib/es/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./lib/es/index.js",
|
||||
"require": "./lib/cjs/index.js",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./*": {
|
||||
"import": "./lib/es/*",
|
||||
"require": "./lib/cjs/*",
|
||||
"types": "./lib/types/index.d.ts"
|
||||
},
|
||||
"./locale/*": {
|
||||
"import": "./lib/es/locale/*.js",
|
||||
"require": "./lib/cjs/locale/*.js",
|
||||
"types": "./lib/types/locale/*.d.ts"
|
||||
},
|
||||
"./facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/facade": {
|
||||
"import": "./lib/es/facade.js",
|
||||
"require": "./lib/cjs/facade.js",
|
||||
"types": "./lib/types/facade/index.d.ts"
|
||||
},
|
||||
"./lib/*": "./lib/*"
|
||||
}
|
||||
},
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
},
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"scripts": {
|
||||
"test": "vitest run",
|
||||
"test:watch": "vitest",
|
||||
"coverage": "vitest run --coverage",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build:bundle": "univer-cli build",
|
||||
"build:types": "tsc -p tsconfig.node.json",
|
||||
"build": "pnpm run build:bundle && pnpm run build:types"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc",
|
||||
"rxjs": ">=7.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@univerjs/core": "workspace:*",
|
||||
"@univerjs/design": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/icons": "1.38.0",
|
||||
"@univerjs/sheets": "workspace:*",
|
||||
"@univerjs/sheets-ui": "workspace:*",
|
||||
"@univerjs/ui": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@univerjs-infra/shared": "workspace:*",
|
||||
"postcss": "^8.5.28",
|
||||
"react": "18.3.1",
|
||||
"rxjs": "^7.8.2",
|
||||
"tailwindcss": "3.4.18",
|
||||
"typescript": "^6.0.3",
|
||||
"vitest": "^5.0.0"
|
||||
}
|
||||
}
|
||||
1
packages/sheets-crosshair-highlight/postcss.config.mjs
Normal file
1
packages/sheets-crosshair-highlight/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@univerjs-infra/shared/postcss';
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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 { RANGE_TYPE, Rectangle } from '@univerjs/core';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { CrossHairRangeCollection } from '../util';
|
||||
|
||||
describe('CrossHairRangeCollection', () => {
|
||||
it('should add/split/reset ranges and skip full row/column/all ranges', () => {
|
||||
const collection = new CrossHairRangeCollection();
|
||||
|
||||
collection.addRange({
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
rangeType: RANGE_TYPE.COLUMN,
|
||||
} as never);
|
||||
collection.addRange({
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
rangeType: RANGE_TYPE.ROW,
|
||||
} as never);
|
||||
collection.addRange({
|
||||
startRow: 0,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 1,
|
||||
rangeType: RANGE_TYPE.ALL,
|
||||
} as never);
|
||||
expect(collection.getRanges()).toEqual([]);
|
||||
|
||||
collection.setSelectedRanges([{
|
||||
startRow: 0,
|
||||
endRow: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
} as never]);
|
||||
|
||||
collection.addRange({
|
||||
startRow: 0,
|
||||
endRow: 2,
|
||||
startColumn: 0,
|
||||
endColumn: 2,
|
||||
} as never);
|
||||
expect(collection.getRanges().length).toBeGreaterThan(0);
|
||||
|
||||
const subtractSpy = vi.spyOn(Rectangle, 'subtract').mockReturnValueOnce([{
|
||||
startRow: 2,
|
||||
endRow: 1,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
} as never]);
|
||||
collection.addRange({
|
||||
startRow: 0,
|
||||
endRow: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
} as never);
|
||||
expect(subtractSpy).toHaveBeenCalled();
|
||||
|
||||
vi.spyOn(Rectangle, 'subtract').mockReturnValueOnce(undefined as never);
|
||||
collection.addRange({
|
||||
startRow: 0,
|
||||
endRow: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 0,
|
||||
} as never);
|
||||
|
||||
collection.reset();
|
||||
expect(collection.getRanges()).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
/**
|
||||
* 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 {
|
||||
CommandService,
|
||||
ConfigService,
|
||||
ContextService,
|
||||
DesktopLogService,
|
||||
ICommandService,
|
||||
IConfigService,
|
||||
IContextService,
|
||||
ILogService,
|
||||
Injector,
|
||||
ThemeService,
|
||||
} from '@univerjs/core';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { beforeEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS,
|
||||
SheetsCrosshairHighlightService,
|
||||
} from '../../../services/crosshair.service';
|
||||
import {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from '../operation';
|
||||
|
||||
function createOperationTestBed() {
|
||||
const injector = new Injector();
|
||||
injector.add([ICommandService, { useClass: CommandService }]);
|
||||
injector.add([ILogService, { useClass: DesktopLogService }]);
|
||||
injector.add([IContextService, { useClass: ContextService }]);
|
||||
injector.add([IConfigService, { useClass: ConfigService }]);
|
||||
injector.add([ThemeService]);
|
||||
injector.add([SheetsCrosshairHighlightService]);
|
||||
|
||||
const commandService = injector.get(ICommandService);
|
||||
commandService.registerCommand(ToggleCrosshairHighlightOperation);
|
||||
commandService.registerCommand(EnableCrosshairHighlightOperation);
|
||||
commandService.registerCommand(DisableCrosshairHighlightOperation);
|
||||
commandService.registerCommand(SetCrosshairHighlightColorOperation);
|
||||
|
||||
const themeService = injector.get(ThemeService);
|
||||
const highlightBackground: Array<{ color: string; alpha: number }> = [];
|
||||
for (let index = 0; index < CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS.length; index++) {
|
||||
highlightBackground.push({
|
||||
color: index === 2 ? '#00ff00' : '#ff0000',
|
||||
alpha: index === 2 ? 0.5 : 1,
|
||||
});
|
||||
}
|
||||
|
||||
themeService.setTheme({
|
||||
...themeService.getCurrentTheme(),
|
||||
highlight: {
|
||||
background: highlightBackground,
|
||||
},
|
||||
} as never);
|
||||
|
||||
return {
|
||||
commandService,
|
||||
service: injector.get(SheetsCrosshairHighlightService),
|
||||
};
|
||||
}
|
||||
|
||||
describe('crosshair highlight operations', () => {
|
||||
let commandService: ICommandService;
|
||||
let service: SheetsCrosshairHighlightService;
|
||||
|
||||
beforeEach(() => {
|
||||
const testBed = createOperationTestBed();
|
||||
commandService = testBed.commandService;
|
||||
service = testBed.service;
|
||||
});
|
||||
|
||||
it('toggles crosshair highlighting for the current sheet view', async () => {
|
||||
expect(service.enabled).toBe(false);
|
||||
|
||||
await commandService.executeCommand(ToggleCrosshairHighlightOperation.id);
|
||||
expect(service.enabled).toBe(true);
|
||||
|
||||
await commandService.executeCommand(ToggleCrosshairHighlightOperation.id);
|
||||
expect(service.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('reports whether explicit enable and disable commands actually changed the state', async () => {
|
||||
await expect(commandService.executeCommand(EnableCrosshairHighlightOperation.id)).resolves.toBe(true);
|
||||
await expect(commandService.executeCommand(EnableCrosshairHighlightOperation.id)).resolves.toBe(false);
|
||||
expect(service.enabled).toBe(true);
|
||||
|
||||
await expect(commandService.executeCommand(DisableCrosshairHighlightOperation.id)).resolves.toBe(true);
|
||||
await expect(commandService.executeCommand(DisableCrosshairHighlightOperation.id)).resolves.toBe(false);
|
||||
expect(service.enabled).toBe(false);
|
||||
});
|
||||
|
||||
it('enables crosshair highlighting when the user chooses a highlight color', async () => {
|
||||
const result = await commandService.executeCommand(SetCrosshairHighlightColorOperation.id, {
|
||||
value: CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[1],
|
||||
});
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(service.enabled).toBe(true);
|
||||
await expect(firstValueFrom(service.color$)).resolves.toBe('rgba(0,255,0,0.5)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,90 @@
|
||||
/**
|
||||
* 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 { IOperation } from '@univerjs/core';
|
||||
import { CommandType } from '@univerjs/core';
|
||||
import { SheetsCrosshairHighlightService } from '../../services/crosshair.service';
|
||||
|
||||
/**
|
||||
* A {@link CommandType.OPERATION} to toggle the crosshair highlight.
|
||||
*/
|
||||
export const ToggleCrosshairHighlightOperation: IOperation = {
|
||||
id: 'sheet.operation.toggle-crosshair-highlight',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor) {
|
||||
const service = accessor.get(SheetsCrosshairHighlightService);
|
||||
const turnedOn = service.enabled;
|
||||
service.setEnabled(!turnedOn);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link CommandType.OPERATION} to set the crosshair highlight color theme token.
|
||||
* @property {string} value - The theme token path to set.
|
||||
*/
|
||||
export interface ISetCrosshairHighlightColorOperationParams {
|
||||
value: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link CommandType.OPERATION} to set the crosshair highlight color. If the crosshair highlight is not enabled,
|
||||
* it will be enabled.
|
||||
*/
|
||||
export const SetCrosshairHighlightColorOperation: IOperation<ISetCrosshairHighlightColorOperationParams> = {
|
||||
id: 'sheet.operation.set-crosshair-highlight-color',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor, { value }) {
|
||||
const service = accessor.get(SheetsCrosshairHighlightService);
|
||||
if (!service.enabled) service.setEnabled(true);
|
||||
service.setColor(value);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link CommandType.OPERATION} to enable the crosshair highlight.
|
||||
*/
|
||||
export const EnableCrosshairHighlightOperation: IOperation = {
|
||||
id: 'sheet.operation.enable-crosshair-highlight',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor) {
|
||||
const service = accessor.get(SheetsCrosshairHighlightService);
|
||||
if (service.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
service.setEnabled(true);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A {@link CommandType.OPERATION} to disable the crosshair highlight.
|
||||
*/
|
||||
export const DisableCrosshairHighlightOperation: IOperation = {
|
||||
id: 'sheet.operation.disable-crosshair-highlight',
|
||||
type: CommandType.OPERATION,
|
||||
handler(accessor) {
|
||||
const service = accessor.get(SheetsCrosshairHighlightService);
|
||||
if (!service.enabled) {
|
||||
return false;
|
||||
}
|
||||
|
||||
service.setEnabled(false);
|
||||
return true;
|
||||
},
|
||||
};
|
||||
27
packages/sheets-crosshair-highlight/src/config/config.ts
Normal file
27
packages/sheets-crosshair-highlight/src/config/config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { MenuConfig } from '@univerjs/ui';
|
||||
|
||||
export const SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY = 'sheets-crosshair-highlight.config';
|
||||
|
||||
export const configSymbol = Symbol(SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export interface IUniverSheetsCrosshairHighlightConfig {
|
||||
menu?: MenuConfig;
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: IUniverSheetsCrosshairHighlightConfig = {};
|
||||
18
packages/sheets-crosshair-highlight/src/const.ts
Normal file
18
packages/sheets-crosshair-highlight/src/const.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export const SHEETS_CROSSHAIR_HIGHLIGHT_Z_INDEX = 1;
|
||||
export const SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_NAME = 'SHEET_CROSSHAIR_HIGHLIGHT_PLUGIN';
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* 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 { CrossHighlightingIcon } from '@univerjs/icons';
|
||||
import { ComponentManager, IconManager } from '@univerjs/ui';
|
||||
import { CROSSHAIR_HIGHLIGHT_OVERLAY_COMPONENT } from '../menu/crosshair.menu';
|
||||
import { MOBILE_CROSSHAIR_HIGHLIGHT_PANEL } from '../menu/mobile-crosshair.menu';
|
||||
import { CrosshairOverlay } from '../views/components/CrosshairHighlight';
|
||||
import { MobileCrosshairHighlightPanel } from '../views/mobile/MobileCrosshairHighlightPanel';
|
||||
|
||||
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 {
|
||||
this.disposeWithMe(this._componentManager.register(CROSSHAIR_HIGHLIGHT_OVERLAY_COMPONENT, CrosshairOverlay));
|
||||
this.disposeWithMe(this._componentManager.register(
|
||||
MOBILE_CROSSHAIR_HIGHLIGHT_PANEL,
|
||||
MobileCrosshairHighlightPanel
|
||||
));
|
||||
}
|
||||
|
||||
private _registerIcons(): void {
|
||||
this.disposeWithMe(this._iconManager.register({
|
||||
CrossHighlightingIcon,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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, ICommandService } from '@univerjs/core';
|
||||
import { IMenuManagerService } from '@univerjs/ui';
|
||||
import {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from '../commands/operations/operation';
|
||||
import { menuSchema } from '../menu/schema';
|
||||
|
||||
export class SheetsCrosshairHighlightController extends Disposable {
|
||||
constructor(
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService,
|
||||
@ICommandService private readonly _cmdSrv: ICommandService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCommands();
|
||||
this._initMenus();
|
||||
}
|
||||
|
||||
private _initCommands(): void {
|
||||
([
|
||||
ToggleCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
DisableCrosshairHighlightOperation,
|
||||
]).forEach((c) => this._cmdSrv.registerCommand(c));
|
||||
}
|
||||
|
||||
private _initMenus(): void {
|
||||
this._menuManagerService.mergeMenu(menuSchema);
|
||||
}
|
||||
}
|
||||
@@ -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 { Disposable, ICommandService } from '@univerjs/core';
|
||||
import { IMenuManagerService } from '@univerjs/ui';
|
||||
import {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from '../../commands/operations/operation';
|
||||
import { mobileMenuSchema } from '../../menu/mobile-schema';
|
||||
|
||||
export class SheetsCrosshairHighlightMobileController extends Disposable {
|
||||
constructor(
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IMenuManagerService menuManagerService: IMenuManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
[
|
||||
ToggleCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
DisableCrosshairHighlightOperation,
|
||||
].forEach((command) => this.disposeWithMe(commandService.registerCommand(command)));
|
||||
menuManagerService.mergeMenu(mobileMenuSchema);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* 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 } from '@univerjs/core';
|
||||
import { ICommandService, Injector } from '@univerjs/core';
|
||||
import { FEventName, FUniver } from '@univerjs/core/facade';
|
||||
import {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SheetsCrosshairHighlightService,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from '@univerjs/sheets-crosshair-highlight';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { FSheetsCrosshairHighlightEventNameMixin } from '../f-event';
|
||||
import { FUniverSheetsCrosshairHighlightMixin } from '../f-univer';
|
||||
|
||||
class TestCommandService {
|
||||
readonly callbacks: Array<(commandInfo: { id: string }) => void> = [];
|
||||
readonly executedCommandIds: string[] = [];
|
||||
|
||||
onCommandExecuted(callback: (commandInfo: { id: string }) => void): IDisposable {
|
||||
this.callbacks.push(callback);
|
||||
return { dispose: () => undefined };
|
||||
}
|
||||
|
||||
syncExecuteCommand(id: string): boolean {
|
||||
this.executedCommandIds.push(id);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class TestCrosshairHighlightService {
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
describe('crosshair facade', () => {
|
||||
it('emits the enabled-change event for crosshair commands on the active sheet', () => {
|
||||
const injector = new Injector();
|
||||
injector.add([ICommandService, { useClass: TestCommandService as never }]);
|
||||
injector.add([SheetsCrosshairHighlightService, { useClass: TestCrosshairHighlightService as never }]);
|
||||
const commandService = injector.get(ICommandService) as unknown as TestCommandService;
|
||||
const fireEvent = vi.fn();
|
||||
const registerEventHandler = vi.fn((_eventName: string, setup: () => unknown) => setup());
|
||||
const thisArg = {
|
||||
Event: FEventName.get(),
|
||||
_injector: injector,
|
||||
_commandService: commandService,
|
||||
getActiveSheet: vi.fn(() => ({
|
||||
workbook: { id: 'workbook-1' },
|
||||
worksheet: { id: 'sheet-1' },
|
||||
})),
|
||||
getCrosshairHighlightEnabled: () => FUniverSheetsCrosshairHighlightMixin.prototype.getCrosshairHighlightEnabled.call(thisArg as never),
|
||||
fireEvent,
|
||||
registerEventHandler,
|
||||
disposeWithMe: vi.fn(),
|
||||
};
|
||||
|
||||
FUniverSheetsCrosshairHighlightMixin.prototype._initialize.call(thisArg as never, injector);
|
||||
expect(registerEventHandler).toHaveBeenCalledWith('CrosshairHighlightEnabledChanged', expect.any(Function));
|
||||
|
||||
commandService.callbacks[0]({ id: EnableCrosshairHighlightOperation.id });
|
||||
commandService.callbacks[0]({ id: DisableCrosshairHighlightOperation.id });
|
||||
commandService.callbacks[0]({ id: ToggleCrosshairHighlightOperation.id });
|
||||
commandService.callbacks[0]({ id: 'unrelated-command' });
|
||||
|
||||
expect(fireEvent).toHaveBeenCalledTimes(3);
|
||||
expect(fireEvent).toHaveBeenLastCalledWith('CrosshairHighlightEnabledChanged', {
|
||||
enabled: true,
|
||||
workbook: { id: 'workbook-1' },
|
||||
worksheet: { id: 'sheet-1' },
|
||||
});
|
||||
|
||||
thisArg.getActiveSheet.mockReturnValue(undefined as never);
|
||||
commandService.callbacks[0]({ id: EnableCrosshairHighlightOperation.id });
|
||||
expect(fireEvent).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('updates crosshair state through commands and exposes the event name on real facade classes', () => {
|
||||
const injector = new Injector();
|
||||
injector.add([ICommandService, { useClass: TestCommandService as never }]);
|
||||
injector.add([SheetsCrosshairHighlightService, { useClass: TestCrosshairHighlightService as never }]);
|
||||
const commandService = injector.get(ICommandService) as unknown as TestCommandService;
|
||||
const thisArg = {
|
||||
_injector: injector,
|
||||
_commandService: commandService,
|
||||
};
|
||||
|
||||
expect(FUniverSheetsCrosshairHighlightMixin.prototype.setCrosshairHighlightEnabled.call(thisArg as never, true)).toBe(thisArg);
|
||||
expect(FUniverSheetsCrosshairHighlightMixin.prototype.setCrosshairHighlightEnabled.call(thisArg as never, false)).toBe(thisArg);
|
||||
expect(commandService.executedCommandIds).toEqual([
|
||||
EnableCrosshairHighlightOperation.id,
|
||||
DisableCrosshairHighlightOperation.id,
|
||||
]);
|
||||
expect(FUniverSheetsCrosshairHighlightMixin.prototype.getCrosshairHighlightEnabled.call(thisArg as never)).toBe(true);
|
||||
expect(FEventName.get().CrosshairHighlightEnabledChanged).toBe('CrosshairHighlightEnabledChanged');
|
||||
expect(Object.create(FSheetsCrosshairHighlightEventNameMixin.prototype).CrosshairHighlightEnabledChanged).toBe('CrosshairHighlightEnabledChanged');
|
||||
expect(typeof FUniver.prototype.setCrosshairHighlightEnabled).toBe('function');
|
||||
});
|
||||
});
|
||||
75
packages/sheets-crosshair-highlight/src/facade/f-event.ts
Normal file
75
packages/sheets-crosshair-highlight/src/facade/f-event.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IEventBase } from '@univerjs/core/facade';
|
||||
import type { FWorkbook, FWorksheet } from '@univerjs/sheets/facade';
|
||||
import { FEventName } from '@univerjs/core/facade';
|
||||
|
||||
export interface IFSheetsCrosshairHighlightEventNameMixin {
|
||||
/**
|
||||
* Triggered when the crosshair highlight is enabled or disabled.
|
||||
* @see {@link ICrosshairHighlightEnabledChangedEventParams}
|
||||
* @example
|
||||
* ```ts
|
||||
* const disposable = univerAPI.addEvent(univerAPI.Event.CrosshairHighlightEnabledChanged, (params) => {
|
||||
* const { enabled, workbook, worksheet } = params;
|
||||
* console.log(params);
|
||||
* });
|
||||
*
|
||||
* // Remove the event listener, use `disposable.dispose()`
|
||||
* ```
|
||||
*/
|
||||
readonly CrosshairHighlightEnabledChanged: 'CrosshairHighlightEnabledChanged';
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export class FSheetsCrosshairHighlightEventNameMixin extends FEventName implements IFSheetsCrosshairHighlightEventNameMixin {
|
||||
override get CrosshairHighlightEnabledChanged(): 'CrosshairHighlightEnabledChanged' {
|
||||
return 'CrosshairHighlightEnabledChanged';
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICrosshairHighlightEnabledChangedEventParams extends IEventBase {
|
||||
/**
|
||||
* Whether the crosshair highlight is enabled.
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* The workbook that the crosshair highlight is enabled in.
|
||||
*/
|
||||
workbook: FWorkbook;
|
||||
/**
|
||||
* The worksheet that the crosshair highlight is enabled in.
|
||||
*/
|
||||
worksheet: FWorksheet;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface ISheetsCrosshairHighlightEventParamConfig {
|
||||
CrosshairHighlightEnabledChanged: ICrosshairHighlightEnabledChangedEventParams;
|
||||
}
|
||||
|
||||
FEventName.extend(FSheetsCrosshairHighlightEventNameMixin);
|
||||
declare module '@univerjs/core/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FEventName extends IFSheetsCrosshairHighlightEventNameMixin { }
|
||||
|
||||
interface IEventParamConfig extends ISheetsCrosshairHighlightEventParamConfig { }
|
||||
}
|
||||
106
packages/sheets-crosshair-highlight/src/facade/f-univer.ts
Normal file
106
packages/sheets-crosshair-highlight/src/facade/f-univer.ts
Normal file
@@ -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 { Injector } from '@univerjs/core';
|
||||
import type { ICrosshairHighlightEnabledChangedEventParams } from './f-event';
|
||||
import { ICommandService } from '@univerjs/core';
|
||||
import { FUniver } from '@univerjs/core/facade';
|
||||
import {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SheetsCrosshairHighlightService,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from '@univerjs/sheets-crosshair-highlight';
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export interface IFUniverSheetsCrosshairHighlightMixin {
|
||||
/**
|
||||
* Enable or disable crosshair highlight.
|
||||
* @param {boolean} enabled - Whether to enable the crosshair highlight
|
||||
* @returns {FUniver} The FUniver instance for chaining
|
||||
* @example
|
||||
* ```ts
|
||||
* univerAPI.setCrosshairHighlightEnabled(true);
|
||||
* ```
|
||||
*/
|
||||
setCrosshairHighlightEnabled(enabled: boolean): FUniver;
|
||||
|
||||
/**
|
||||
* Get whether the crosshair highlight is enabled.
|
||||
* @returns {boolean} Whether the crosshair highlight is enabled
|
||||
* @example
|
||||
* ```ts
|
||||
* console.log(univerAPI.getCrosshairHighlightEnabled());
|
||||
* ```
|
||||
*/
|
||||
getCrosshairHighlightEnabled(): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
export class FUniverSheetsCrosshairHighlightMixin extends FUniver implements IFUniverSheetsCrosshairHighlightMixin {
|
||||
/**
|
||||
* @ignore
|
||||
*/
|
||||
override _initialize(injector: Injector): void {
|
||||
const commandService = injector.get(ICommandService);
|
||||
|
||||
this.disposeWithMe(
|
||||
this.registerEventHandler(
|
||||
this.Event.CrosshairHighlightEnabledChanged,
|
||||
() => commandService.onCommandExecuted((commandInfo) => {
|
||||
if (
|
||||
commandInfo.id === EnableCrosshairHighlightOperation.id ||
|
||||
commandInfo.id === DisableCrosshairHighlightOperation.id ||
|
||||
commandInfo.id === ToggleCrosshairHighlightOperation.id
|
||||
) {
|
||||
const activeSheet = this.getActiveSheet();
|
||||
if (!activeSheet) return;
|
||||
const eventParams: ICrosshairHighlightEnabledChangedEventParams = {
|
||||
enabled: this.getCrosshairHighlightEnabled(),
|
||||
...activeSheet,
|
||||
};
|
||||
this.fireEvent(this.Event.CrosshairHighlightEnabledChanged, eventParams);
|
||||
}
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
override setCrosshairHighlightEnabled(enabled: boolean): FUniver {
|
||||
if (enabled) {
|
||||
this._commandService.syncExecuteCommand(EnableCrosshairHighlightOperation.id);
|
||||
} else {
|
||||
this._commandService.syncExecuteCommand(DisableCrosshairHighlightOperation.id);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
override getCrosshairHighlightEnabled(): boolean {
|
||||
const crosshairHighlightService = this._injector.get(SheetsCrosshairHighlightService);
|
||||
return crosshairHighlightService.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
FUniver.extend(FUniverSheetsCrosshairHighlightMixin);
|
||||
declare module '@univerjs/core/facade' {
|
||||
// eslint-disable-next-line ts/naming-convention
|
||||
interface FUniver extends IFUniverSheetsCrosshairHighlightMixin { }
|
||||
}
|
||||
21
packages/sheets-crosshair-highlight/src/facade/index.ts
Normal file
21
packages/sheets-crosshair-highlight/src/facade/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import './f-univer';
|
||||
import './f-event';
|
||||
|
||||
export type * from './f-event';
|
||||
export type * from './f-univer';
|
||||
2
packages/sheets-crosshair-highlight/src/global.css
Normal file
2
packages/sheets-crosshair-highlight/src/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
30
packages/sheets-crosshair-highlight/src/index.ts
Normal file
30
packages/sheets-crosshair-highlight/src/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from './commands/operations/operation';
|
||||
export type { ISetCrosshairHighlightColorOperationParams } from './commands/operations/operation';
|
||||
export type { IUniverSheetsCrosshairHighlightConfig } from './config/config';
|
||||
export { menuSchema as SheetsCrosshairHighlightMenuSchema } from './menu/schema';
|
||||
export { UniverSheetsCrosshairHighlightMobileUIPlugin } from './mobile-plugin';
|
||||
export { UniverSheetsCrosshairHighlightPlugin } from './plugin';
|
||||
export { SheetsCrosshairHighlightService } from './services/crosshair.service';
|
||||
27
packages/sheets-crosshair-highlight/src/locale/ar-SA.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/ar-SA.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'تسليط الضوء على التقاطع',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/ca-ES.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/ca-ES.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Ressaltat de creu',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/de-DE.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/de-DE.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Fadenkreuz-Hervorhebung',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
25
packages/sheets-crosshair-highlight/src/locale/en-US.ts
Normal file
25
packages/sheets-crosshair-highlight/src/locale/en-US.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
const locale = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Crosshair Highlight',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/es-ES.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/es-ES.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Resaltado de cruz',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/fa-IR.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/fa-IR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'برجسته کردن خط نشانه',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/fr-FR.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/fr-FR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Surlignage du réticule',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/id-ID.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/id-ID.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Sorot Penyilang',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/it-IT.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/it-IT.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Evidenziazione mirino',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/ja-JP.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/ja-JP.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: '十字線を強調表示',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/ko-KR.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/ko-KR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: '십자선 강조',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/pl-PL.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/pl-PL.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Podświetlenie krzyżowe',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/pt-BR.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/pt-BR.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Destaque de Mira Cruzada',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/ru-RU.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/ru-RU.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Перекрестный выделение',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/sk-SK.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/sk-SK.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Zvýraznenie krížika',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
20
packages/sheets-crosshair-highlight/src/locale/types.ts
Normal file
20
packages/sheets-crosshair-highlight/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>;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/vi-VN.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/vi-VN.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: 'Đánh dấu chéo',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/zh-CN.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/zh-CN.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: '十字高亮',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/zh-HK.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/zh-HK.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: '十字高亮',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
27
packages/sheets-crosshair-highlight/src/locale/zh-TW.ts
Normal file
27
packages/sheets-crosshair-highlight/src/locale/zh-TW.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type enUS from './en-US';
|
||||
|
||||
const locale: typeof enUS = {
|
||||
'sheets-crosshair-highlight': {
|
||||
button: {
|
||||
tooltip: '十字高亮',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default locale;
|
||||
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IAccessor } from '@univerjs/core';
|
||||
import type { IMenuSelectorItem } from '@univerjs/ui';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { UniverInstanceType } from '@univerjs/core';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
import {
|
||||
SetCrosshairHighlightColorOperation,
|
||||
ToggleCrosshairHighlightOperation,
|
||||
} from '../commands/operations/operation';
|
||||
import { SheetsCrosshairHighlightService } from '../services/crosshair.service';
|
||||
|
||||
export const CROSSHAIR_HIGHLIGHT_OVERLAY_COMPONENT = 'CROSSHAIR_HIGHLIGHT_OVERLAY_COMPONENT';
|
||||
|
||||
export function CrosshairHighlightMenuItemFactory(accessor: IAccessor): IMenuSelectorItem<LocaleKey> {
|
||||
const crosshairHighlightService = accessor.get(SheetsCrosshairHighlightService);
|
||||
|
||||
return {
|
||||
id: ToggleCrosshairHighlightOperation.id,
|
||||
tooltip: 'sheets-crosshair-highlight.button.tooltip',
|
||||
type: MenuItemType.BUTTON_SELECTOR,
|
||||
icon: 'CrossHighlightingIcon',
|
||||
selections: [
|
||||
{
|
||||
label: {
|
||||
name: CROSSHAIR_HIGHLIGHT_OVERLAY_COMPONENT,
|
||||
hoverable: false,
|
||||
selectable: false,
|
||||
props: { embedded: true },
|
||||
},
|
||||
},
|
||||
],
|
||||
selectionsCommandId: SetCrosshairHighlightColorOperation.id,
|
||||
activated$: crosshairHighlightService.enabled$,
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IAccessor } from '@univerjs/core';
|
||||
import type { IMenuSelectorItem } from '@univerjs/ui';
|
||||
import type { LocaleKey } from '../locale/types';
|
||||
import { UniverInstanceType } from '@univerjs/core';
|
||||
import { getMenuHiddenObservable, MenuItemType } from '@univerjs/ui';
|
||||
|
||||
export const MOBILE_CROSSHAIR_HIGHLIGHT_MENU_ID = 'sheet.menu.mobile.crosshair-highlight';
|
||||
export const MOBILE_CROSSHAIR_HIGHLIGHT_PANEL = 'MOBILE_CROSSHAIR_HIGHLIGHT_PANEL';
|
||||
|
||||
export function MobileCrosshairHighlightMenuItemFactory(accessor: IAccessor): IMenuSelectorItem<LocaleKey> {
|
||||
return {
|
||||
id: MOBILE_CROSSHAIR_HIGHLIGHT_MENU_ID,
|
||||
type: MenuItemType.SELECTOR,
|
||||
icon: 'CrossHighlightingIcon',
|
||||
title: 'sheets-crosshair-highlight.button.tooltip',
|
||||
tooltip: 'sheets-crosshair-highlight.button.tooltip',
|
||||
selections: [
|
||||
{
|
||||
label: {
|
||||
name: MOBILE_CROSSHAIR_HIGHLIGHT_PANEL,
|
||||
hoverable: false,
|
||||
selectable: false,
|
||||
},
|
||||
},
|
||||
],
|
||||
hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET),
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* 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 { RibbonDataGroup } from '@univerjs/ui';
|
||||
import {
|
||||
MOBILE_CROSSHAIR_HIGHLIGHT_MENU_ID,
|
||||
MobileCrosshairHighlightMenuItemFactory,
|
||||
} from './mobile-crosshair.menu';
|
||||
|
||||
export const mobileMenuSchema: MenuSchemaType = {
|
||||
[RibbonDataGroup.ORGANIZATION]: {
|
||||
[MOBILE_CROSSHAIR_HIGHLIGHT_MENU_ID]: {
|
||||
order: 21,
|
||||
menuItemFactory: MobileCrosshairHighlightMenuItemFactory,
|
||||
},
|
||||
},
|
||||
};
|
||||
38
packages/sheets-crosshair-highlight/src/menu/schema.ts
Normal file
38
packages/sheets-crosshair-highlight/src/menu/schema.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { MenuSchemaType } from '@univerjs/ui';
|
||||
import { ContextMenuGroup, ContextMenuPosition, RibbonViewGroup } from '@univerjs/ui';
|
||||
import { ToggleCrosshairHighlightOperation } from '../commands/operations/operation';
|
||||
import { CrosshairHighlightMenuItemFactory } from './crosshair.menu';
|
||||
|
||||
export const menuSchema: MenuSchemaType = {
|
||||
[RibbonViewGroup.VISIBILITY]: {
|
||||
[ToggleCrosshairHighlightOperation.id]: {
|
||||
order: 1,
|
||||
gridLayout: { row: 2, column: 1, showLabel: true },
|
||||
menuItemFactory: CrosshairHighlightMenuItemFactory,
|
||||
},
|
||||
},
|
||||
[ContextMenuPosition.FOOTER_MENU]: {
|
||||
[ContextMenuGroup.OTHERS]: {
|
||||
[ToggleCrosshairHighlightOperation.id]: {
|
||||
order: 0,
|
||||
menuItemFactory: CrosshairHighlightMenuItemFactory,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
67
packages/sheets-crosshair-highlight/src/mobile-plugin.ts
Normal file
67
packages/sheets-crosshair-highlight/src/mobile-plugin.ts
Normal file
@@ -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 { Dependency } from '@univerjs/core';
|
||||
import type { IUniverSheetsCrosshairHighlightConfig } from './config/config';
|
||||
import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsMobileUIPlugin } from '@univerjs/sheets-ui';
|
||||
import { UniverMobileUIPlugin } from '@univerjs/ui';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_NAME } from './const';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { SheetsCrosshairHighlightMobileController } from './controllers/mobile/crosshair.controller';
|
||||
import { SheetsCrosshairHighlightService } from './services/crosshair.service';
|
||||
import { SheetCrosshairHighlightRenderController } from './views/widgets/crosshair-highlight.render-controller';
|
||||
|
||||
@DependentOn(UniverRenderEnginePlugin, UniverSheetsPlugin, UniverSheetsMobileUIPlugin, UniverMobileUIPlugin)
|
||||
export class UniverSheetsCrosshairHighlightMobileUIPlugin extends Plugin {
|
||||
static override pluginName = SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsCrosshairHighlightConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
|
||||
const { ...rest } = merge({}, defaultPluginConfig, this._config);
|
||||
this._configService.setConfig(SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[ComponentsController],
|
||||
[SheetsCrosshairHighlightService],
|
||||
[SheetsCrosshairHighlightMobileController],
|
||||
] as Dependency[]).forEach((dependency) => this._injector.add(dependency));
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
this._injector.get(ComponentsController);
|
||||
this._injector.get(SheetsCrosshairHighlightMobileController);
|
||||
this.disposeWithMe(this._renderManagerService.registerRenderModule(
|
||||
UniverInstanceType.UNIVER_SHEET,
|
||||
[SheetCrosshairHighlightRenderController] as Dependency
|
||||
));
|
||||
}
|
||||
}
|
||||
71
packages/sheets-crosshair-highlight/src/plugin.ts
Normal file
71
packages/sheets-crosshair-highlight/src/plugin.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 { Dependency } from '@univerjs/core';
|
||||
import type { IUniverSheetsCrosshairHighlightConfig } from './config/config';
|
||||
import { DependentOn, IConfigService, Inject, Injector, merge, Plugin, UniverInstanceType } from '@univerjs/core';
|
||||
import { IRenderManagerService, UniverRenderEnginePlugin } from '@univerjs/engine-render';
|
||||
import { UniverSheetsPlugin } from '@univerjs/sheets';
|
||||
import { UniverSheetsUIPlugin } from '@univerjs/sheets-ui';
|
||||
import pkg from '../package.json';
|
||||
import { defaultPluginConfig, SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY } from './config/config';
|
||||
import { SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_NAME } from './const';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { SheetsCrosshairHighlightController } from './controllers/crosshair.controller';
|
||||
import { SheetsCrosshairHighlightService } from './services/crosshair.service';
|
||||
import { SheetCrosshairHighlightRenderController } from './views/widgets/crosshair-highlight.render-controller';
|
||||
|
||||
@DependentOn(UniverRenderEnginePlugin, UniverSheetsPlugin, UniverSheetsUIPlugin)
|
||||
export class UniverSheetsCrosshairHighlightPlugin extends Plugin {
|
||||
static override pluginName: string = SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_NAME;
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
static override type = UniverInstanceType.UNIVER_SHEET;
|
||||
|
||||
constructor(
|
||||
private readonly _config: Partial<IUniverSheetsCrosshairHighlightConfig> = defaultPluginConfig,
|
||||
@Inject(Injector) protected readonly _injector: Injector,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Manage the plugin configuration.
|
||||
const { ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
this._configService.setConfig(SHEETS_CROSSHAIR_HIGHLIGHT_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
([
|
||||
[ComponentsController],
|
||||
[SheetsCrosshairHighlightService],
|
||||
[SheetsCrosshairHighlightController],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
([
|
||||
[SheetCrosshairHighlightRenderController],
|
||||
] as Dependency[]).forEach((d) => this._injector.add(d));
|
||||
this._injector.get(ComponentsController);
|
||||
this._injector.get(SheetsCrosshairHighlightController);
|
||||
this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, [SheetCrosshairHighlightRenderController] as Dependency);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Injector, ThemeService } from '@univerjs/core';
|
||||
import { firstValueFrom, skip, take } from 'rxjs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS, SheetsCrosshairHighlightService } from '../crosshair.service';
|
||||
|
||||
function createService(): { service: SheetsCrosshairHighlightService; themeService: ThemeService } {
|
||||
const injector = new Injector();
|
||||
injector.add([ThemeService]);
|
||||
injector.add([SheetsCrosshairHighlightService]);
|
||||
return {
|
||||
service: injector.get(SheetsCrosshairHighlightService),
|
||||
themeService: injector.get(ThemeService),
|
||||
};
|
||||
}
|
||||
|
||||
describe('SheetsCrosshairHighlightService', () => {
|
||||
it('tracks enabled state and resolves selected highlight colors from theme tokens', async () => {
|
||||
const { service, themeService } = createService();
|
||||
themeService.setTheme({
|
||||
...themeService.getCurrentTheme(),
|
||||
highlight: {
|
||||
background: CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS.map((_path, index) => ({
|
||||
color: index === 2 ? '#00ff00' : '#ff0000',
|
||||
alpha: index === 2 ? 0.5 : 1,
|
||||
})),
|
||||
},
|
||||
} as never);
|
||||
|
||||
const enabled = firstValueFrom(service.enabled$.pipe(skip(1), take(1)));
|
||||
service.setEnabled(true);
|
||||
|
||||
expect(service.enabled).toBe(true);
|
||||
await expect(enabled).resolves.toBe(true);
|
||||
|
||||
service.setColor(CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[1]);
|
||||
await expect(firstValueFrom(service.color$)).resolves.toBe('rgba(0,255,0,0.5)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* 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 { Observable } from 'rxjs';
|
||||
import { ColorKit, Disposable, Inject, ThemeService } from '@univerjs/core';
|
||||
import { BehaviorSubject, combineLatest, map } from 'rxjs';
|
||||
|
||||
export const CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS = Array.from({ length: 16 }, (_, index) => `highlight.background.${index + 1}`);
|
||||
export const DEFAULT_CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATH = CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[0];
|
||||
|
||||
interface IThemeColorWithAlpha {
|
||||
color: string;
|
||||
alpha: number;
|
||||
}
|
||||
|
||||
export function resolveCrosshairHighlightColor(themeService: ThemeService, tokenPath: string): string {
|
||||
const token = themeService.getColorFromTheme<IThemeColorWithAlpha | undefined>(tokenPath);
|
||||
|
||||
if (token == null || typeof token.color !== 'string' || typeof token.alpha !== 'number') {
|
||||
throw new Error(`Theme token ${tokenPath} is required.`);
|
||||
}
|
||||
|
||||
const color = themeService.getColorFromTheme<string | undefined>(token.color) ?? token.color;
|
||||
return new ColorKit(color).setAlpha(token.alpha).toRgbString();
|
||||
}
|
||||
|
||||
export function resolveCrosshairHighlightColors(themeService: ThemeService): string[] {
|
||||
return CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS.map((tokenPath) => resolveCrosshairHighlightColor(themeService, tokenPath));
|
||||
}
|
||||
|
||||
export class SheetsCrosshairHighlightService extends Disposable {
|
||||
private readonly _enabled$ = new BehaviorSubject<boolean>(false);
|
||||
readonly enabled$ = this._enabled$.asObservable();
|
||||
get enabled(): boolean { return this._enabled$.getValue(); }
|
||||
|
||||
private readonly _colorToken$ = new BehaviorSubject<string>(DEFAULT_CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATH);
|
||||
readonly colorToken$ = this._colorToken$.asObservable();
|
||||
|
||||
readonly color$: Observable<string>;
|
||||
readonly highlightColor$: Observable<string>;
|
||||
|
||||
constructor(
|
||||
@Inject(ThemeService) private readonly _themeService: ThemeService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.color$ = combineLatest([this._colorToken$, this._themeService.currentTheme$]).pipe(
|
||||
map(([tokenPath]) => resolveCrosshairHighlightColor(this._themeService, tokenPath))
|
||||
);
|
||||
this.highlightColor$ = this.color$;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this._enabled$.complete();
|
||||
this._colorToken$.complete();
|
||||
}
|
||||
|
||||
setEnabled(value: boolean): void {
|
||||
this._enabled$.next(value);
|
||||
}
|
||||
|
||||
setColor(value: string): void {
|
||||
this._colorToken$.next(value);
|
||||
}
|
||||
}
|
||||
74
packages/sheets-crosshair-highlight/src/util.ts
Normal file
74
packages/sheets-crosshair-highlight/src/util.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRange } from '@univerjs/core';
|
||||
import { getIntersectRange, RANGE_TYPE, Rectangle } from '@univerjs/core';
|
||||
|
||||
export class CrossHairRangeCollection {
|
||||
private _selectedRanges: IRange[] = [];
|
||||
private _ranges: IRange[] = [];
|
||||
|
||||
public addRange(range: IRange) {
|
||||
if (range.rangeType === RANGE_TYPE.COLUMN || range.rangeType === RANGE_TYPE.ROW || range.rangeType === RANGE_TYPE.ALL) {
|
||||
return;
|
||||
}
|
||||
const intersects = this._getIntersects(range);
|
||||
|
||||
const splitRanges = this._getSplitRanges(range, intersects);
|
||||
if (splitRanges.length > 0) {
|
||||
this._ranges.push(...splitRanges);
|
||||
}
|
||||
}
|
||||
|
||||
public setSelectedRanges(selectedRange: IRange[]) {
|
||||
this._selectedRanges = selectedRange;
|
||||
}
|
||||
|
||||
private _getSplitRanges(range: IRange, intersects: IRange[]) {
|
||||
let splitRanges: IRange[] = [range];
|
||||
for (const intersect of intersects.concat(this._selectedRanges)) {
|
||||
const newRanges = [];
|
||||
for (const splitRange of splitRanges) {
|
||||
const split = Rectangle.subtract(splitRange, intersect);
|
||||
if (split && split.length > 0) {
|
||||
newRanges.push(...split);
|
||||
}
|
||||
}
|
||||
splitRanges = newRanges;
|
||||
}
|
||||
return splitRanges.filter((range) => range.startRow <= range.endRow && range.startColumn <= range.endColumn);
|
||||
}
|
||||
|
||||
private _getIntersects(addRange: IRange) {
|
||||
const intersects = [];
|
||||
for (const range of this._ranges) {
|
||||
const intersect = getIntersectRange(range, addRange);
|
||||
if (intersect) {
|
||||
intersects.push(intersect);
|
||||
}
|
||||
}
|
||||
return intersects;
|
||||
}
|
||||
|
||||
getRanges() {
|
||||
return this._ranges;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this._ranges = [];
|
||||
this._selectedRanges = [];
|
||||
}
|
||||
}
|
||||
@@ -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 { ThemeService } from '@univerjs/core';
|
||||
import { borderClassName, clsx } from '@univerjs/design';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { useCallback } from 'react';
|
||||
import { map } from 'rxjs';
|
||||
import {
|
||||
CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS,
|
||||
resolveCrosshairHighlightColors,
|
||||
SheetsCrosshairHighlightService,
|
||||
} from '../../services/crosshair.service';
|
||||
|
||||
export interface ICrosshairOverlayProps {
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export function CrosshairOverlay(props: ICrosshairOverlayProps) {
|
||||
const { onChange } = props;
|
||||
|
||||
const crosshairSrv = useDependency(SheetsCrosshairHighlightService);
|
||||
const themeService = useDependency(ThemeService);
|
||||
|
||||
const currentColor = useObservable(crosshairSrv.color$);
|
||||
const colors = useObservable(
|
||||
() => themeService.currentTheme$.pipe(map(() => resolveCrosshairHighlightColors(themeService))),
|
||||
resolveCrosshairHighlightColors(themeService),
|
||||
false,
|
||||
[themeService]
|
||||
);
|
||||
|
||||
const handleColorPicked = useCallback((tokenPath: string) => {
|
||||
onChange?.(tokenPath);
|
||||
}, [onChange]);
|
||||
|
||||
return (
|
||||
<div className="univer-grid univer-grid-cols-8 univer-gap-x-2 univer-gap-y-3 univer-p-1.5">
|
||||
{colors.map((color: string, index: number) => {
|
||||
return (
|
||||
<div
|
||||
key={CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[index]}
|
||||
className={clsx(`
|
||||
hover:univer-ring-primary-600/40
|
||||
univer-box-border univer-size-5 univer-cursor-pointer univer-rounded univer-ring-offset-1
|
||||
univer-transition-shadow
|
||||
hover:univer-ring-[1.5px]
|
||||
`, borderClassName, {
|
||||
'univer-ring-[1.5px] univer-ring-primary-600 hover:univer-ring-primary-600': color === currentColor,
|
||||
})}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => handleColorPicked(CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[index])}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IDisposable } from '@univerjs/core';
|
||||
import type { Root } from 'react-dom/client';
|
||||
import { Injector, ThemeService, toDisposable } from '@univerjs/core';
|
||||
import { RediContext } from '@univerjs/ui';
|
||||
import { act } from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
|
||||
import {
|
||||
CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS,
|
||||
SheetsCrosshairHighlightService,
|
||||
} from '../../../services/crosshair.service';
|
||||
import { CrosshairOverlay } from '../CrosshairHighlight';
|
||||
|
||||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
class TestState {
|
||||
static pickedTokens: string[] = [];
|
||||
|
||||
static reset(): void {
|
||||
this.pickedTokens = [];
|
||||
}
|
||||
}
|
||||
|
||||
function createOverlayTestBed() {
|
||||
const injector = new Injector();
|
||||
injector.add([ThemeService]);
|
||||
injector.add([SheetsCrosshairHighlightService]);
|
||||
|
||||
const themeService = injector.get(ThemeService);
|
||||
const highlightBackground = Object.fromEntries(CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS.map((path, index) => [
|
||||
path.replace('highlight.background.', ''),
|
||||
{
|
||||
color: index === 0 ? '#010203' : index === 1 ? '#040506' : `#${String(index + 10).padStart(2, '0')}0a0b`,
|
||||
alpha: index === 1 ? 0.15 : 0.3,
|
||||
},
|
||||
]));
|
||||
|
||||
themeService.setTheme({
|
||||
...themeService.getCurrentTheme(),
|
||||
highlight: {
|
||||
background: highlightBackground,
|
||||
},
|
||||
} as never);
|
||||
|
||||
return {
|
||||
injector,
|
||||
service: injector.get(SheetsCrosshairHighlightService),
|
||||
};
|
||||
}
|
||||
|
||||
function renderOverlay(injector: Injector) {
|
||||
const container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
const root = createRoot(container);
|
||||
|
||||
act(() => {
|
||||
root.render(
|
||||
<RediContext.Provider value={{ injector }}>
|
||||
<CrosshairOverlay onChange={(token) => TestState.pickedTokens.push(token)} />
|
||||
</RediContext.Provider>
|
||||
);
|
||||
});
|
||||
|
||||
return { container, root };
|
||||
}
|
||||
|
||||
function getColorCells(container: HTMLElement): HTMLElement[] {
|
||||
return Array.from(container.querySelectorAll<HTMLElement>('div[style*="background-color"]'));
|
||||
}
|
||||
|
||||
function hasClassToken(element: HTMLElement, token: string): boolean {
|
||||
return element.className.split(/\s+/).includes(token);
|
||||
}
|
||||
|
||||
function teardown(root?: Root, container?: HTMLElement): IDisposable {
|
||||
return toDisposable(() => {
|
||||
if (root) {
|
||||
act(() => root.unmount());
|
||||
}
|
||||
container?.remove();
|
||||
});
|
||||
}
|
||||
|
||||
describe('CrosshairOverlay', () => {
|
||||
let disposable: IDisposable | undefined;
|
||||
|
||||
beforeEach(() => {
|
||||
TestState.reset();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
disposable?.dispose();
|
||||
disposable = undefined;
|
||||
});
|
||||
|
||||
it('offers theme-backed highlight colors and reports the picked token', () => {
|
||||
const testBed = createOverlayTestBed();
|
||||
const rendered = renderOverlay(testBed.injector);
|
||||
disposable = teardown(rendered.root, rendered.container);
|
||||
|
||||
const cells = getColorCells(rendered.container);
|
||||
expect(cells).toHaveLength(CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS.length);
|
||||
expect(cells[0].style.backgroundColor).toBe('rgba(1, 2, 3, 0.3)');
|
||||
expect(cells[1].style.backgroundColor).toBe('rgba(4, 5, 6, 0.15)');
|
||||
|
||||
act(() => {
|
||||
cells[1].dispatchEvent(new MouseEvent('click', { bubbles: true }));
|
||||
});
|
||||
|
||||
expect(TestState.pickedTokens).toEqual([CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[1]]);
|
||||
});
|
||||
|
||||
it('marks the service-selected color as the active swatch', () => {
|
||||
const testBed = createOverlayTestBed();
|
||||
testBed.service.setColor(CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[1]);
|
||||
|
||||
const rendered = renderOverlay(testBed.injector);
|
||||
disposable = teardown(rendered.root, rendered.container);
|
||||
|
||||
const cells = getColorCells(rendered.container);
|
||||
expect(hasClassToken(cells[1], 'univer-ring-primary-600')).toBe(true);
|
||||
expect(hasClassToken(cells[0], 'univer-ring-primary-600')).toBe(false);
|
||||
});
|
||||
|
||||
it('moves the active swatch when the service color changes after render', () => {
|
||||
const testBed = createOverlayTestBed();
|
||||
const rendered = renderOverlay(testBed.injector);
|
||||
disposable = teardown(rendered.root, rendered.container);
|
||||
|
||||
let cells = getColorCells(rendered.container);
|
||||
expect(hasClassToken(cells[0], 'univer-ring-primary-600')).toBe(true);
|
||||
expect(hasClassToken(cells[2], 'univer-ring-primary-600')).toBe(false);
|
||||
|
||||
act(() => {
|
||||
testBed.service.setColor(CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[2]);
|
||||
});
|
||||
|
||||
cells = getColorCells(rendered.container);
|
||||
expect(hasClassToken(cells[0], 'univer-ring-primary-600')).toBe(false);
|
||||
expect(hasClassToken(cells[2], 'univer-ring-primary-600')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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, LocaleService, ThemeService } from '@univerjs/core';
|
||||
import { clsx, resetButtonClassName, Switch } from '@univerjs/design';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { map } from 'rxjs';
|
||||
import {
|
||||
DisableCrosshairHighlightOperation,
|
||||
EnableCrosshairHighlightOperation,
|
||||
SetCrosshairHighlightColorOperation,
|
||||
} from '../../commands/operations/operation';
|
||||
import {
|
||||
CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS,
|
||||
resolveCrosshairHighlightColors,
|
||||
SheetsCrosshairHighlightService,
|
||||
} from '../../services/crosshair.service';
|
||||
|
||||
export function MobileCrosshairHighlightPanel() {
|
||||
const commandService = useDependency(ICommandService);
|
||||
const crosshairHighlightService = useDependency(SheetsCrosshairHighlightService);
|
||||
const localeService = useDependency(LocaleService);
|
||||
const themeService = useDependency(ThemeService);
|
||||
const enabled = useObservable(crosshairHighlightService.enabled$, false);
|
||||
const currentColor = useObservable(crosshairHighlightService.color$);
|
||||
const colors = useObservable(
|
||||
() => themeService.currentTheme$.pipe(map(() => resolveCrosshairHighlightColors(themeService))),
|
||||
resolveCrosshairHighlightColors(themeService),
|
||||
false,
|
||||
[themeService]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="univer-grid univer-w-full univer-gap-4 univer-p-4">
|
||||
<div className="univer-flex univer-min-h-8 univer-items-center univer-gap-4">
|
||||
<span
|
||||
className="
|
||||
univer-min-w-0 univer-flex-1 univer-text-base univer-font-medium univer-text-gray-900
|
||||
dark:!univer-text-gray-100
|
||||
"
|
||||
>
|
||||
{localeService.t<LocaleKey>('sheets-crosshair-highlight.button.tooltip')}
|
||||
</span>
|
||||
<Switch
|
||||
defaultChecked={enabled}
|
||||
onChange={(checked) => {
|
||||
const operation = checked
|
||||
? EnableCrosshairHighlightOperation
|
||||
: DisableCrosshairHighlightOperation;
|
||||
commandService.executeCommand(operation.id).catch(() => undefined);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="univer-grid univer-grid-cols-8 univer-gap-3">
|
||||
{colors.map((color, index) => {
|
||||
const tokenPath = CROSSHAIR_HIGHLIGHT_COLOR_THEME_PATHS[index];
|
||||
const selected = color === currentColor;
|
||||
|
||||
return (
|
||||
<button
|
||||
key={tokenPath}
|
||||
type="button"
|
||||
data-color-token={tokenPath}
|
||||
title={color}
|
||||
aria-pressed={selected}
|
||||
className={clsx(resetButtonClassName, `
|
||||
univer-box-border univer-size-8 univer-rounded-lg univer-border univer-border-solid
|
||||
univer-border-gray-200 univer-ring-offset-2
|
||||
active:univer-opacity-70
|
||||
dark:!univer-border-gray-600
|
||||
`, {
|
||||
'univer-ring-2 univer-ring-primary-600': selected,
|
||||
})}
|
||||
style={{ backgroundColor: color }}
|
||||
onClick={() => {
|
||||
commandService.executeCommand(SetCrosshairHighlightColorOperation.id, {
|
||||
value: tokenPath,
|
||||
}).catch(() => undefined);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SheetCrossHairHighlightShape } from '../crosshair-highlight-shape';
|
||||
|
||||
class TestCanvasContext {
|
||||
fillStyle = '';
|
||||
readonly rects: Array<{ left: number; top: number; width: number; height: number }> = [];
|
||||
fillCount = 0;
|
||||
|
||||
save(): void {}
|
||||
|
||||
restore(): void {}
|
||||
|
||||
beginPath(): void {}
|
||||
|
||||
closePath(): void {}
|
||||
|
||||
rect(left: number, top: number, width: number, height: number): void {
|
||||
this.rects.push({ left, top, width, height });
|
||||
}
|
||||
|
||||
fill(): void {
|
||||
this.fillCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
function drawShape(shape: SheetCrossHairHighlightShape, context: TestCanvasContext): void {
|
||||
(shape as unknown as { _draw: (ctx: TestCanvasContext) => void })._draw(context);
|
||||
}
|
||||
|
||||
describe('SheetCrossHairHighlightShape', () => {
|
||||
it('draws the highlight rectangle with its configured rgba fill', () => {
|
||||
const shape = new SheetCrossHairHighlightShape('shape-1', {
|
||||
color: { r: 1, g: 2, b: 3, a: 0.4 },
|
||||
width: 20,
|
||||
height: 10,
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
evented: false,
|
||||
});
|
||||
shape.setShapeProps({ width: 30, height: 15 });
|
||||
const context = new TestCanvasContext();
|
||||
|
||||
drawShape(shape, context);
|
||||
|
||||
expect(context.rects).toEqual([{ left: 0, top: 0, width: 30, height: 15 }]);
|
||||
expect(context.fillStyle).toBe('rgba(1, 2, 3, 0.4)');
|
||||
expect(context.fillCount).toBe(1);
|
||||
});
|
||||
|
||||
it('uses the default half opacity when the color has no alpha channel', () => {
|
||||
const shape = new SheetCrossHairHighlightShape('shape-default-alpha');
|
||||
shape.setShapeProps({
|
||||
color: { r: 7, g: 8, b: 9 },
|
||||
width: 1,
|
||||
height: 1,
|
||||
});
|
||||
const context = new TestCanvasContext();
|
||||
|
||||
drawShape(shape, context);
|
||||
|
||||
expect(context.fillStyle).toBe('rgba(7, 8, 9, 0.5)');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRange } from '@univerjs/core';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { SheetCrossHairHighlightShape } from '../crosshair-highlight-shape';
|
||||
import { SheetCrosshairHighlightRenderController } from '../crosshair-highlight.render-controller';
|
||||
|
||||
class TestSheet {
|
||||
getRowCount(): number {
|
||||
return 20;
|
||||
}
|
||||
|
||||
getColumnCount(): number {
|
||||
return 10;
|
||||
}
|
||||
}
|
||||
|
||||
class TestWorkbook {
|
||||
readonly activeSheet$ = new BehaviorSubject(0);
|
||||
readonly sheet = new TestSheet();
|
||||
|
||||
getActiveSheet(): TestSheet {
|
||||
return this.sheet;
|
||||
}
|
||||
}
|
||||
|
||||
class TestScene {
|
||||
readonly objects: SheetCrossHairHighlightShape[] = [];
|
||||
readonly dirtyRequests: boolean[] = [];
|
||||
|
||||
addObject(object: SheetCrossHairHighlightShape): void {
|
||||
this.objects.push(object);
|
||||
}
|
||||
|
||||
makeDirty(value: boolean): void {
|
||||
this.dirtyRequests.push(value);
|
||||
}
|
||||
}
|
||||
|
||||
class TestSkeleton {
|
||||
getCellWithCoordByIndex(row: number, column: number) {
|
||||
return {
|
||||
startX: column * 100,
|
||||
startY: row * 20,
|
||||
endX: column * 100 + 100,
|
||||
endY: row * 20 + 20,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class TestSkeletonManagerService {
|
||||
readonly skeleton = new TestSkeleton();
|
||||
readonly currentSkeleton$ = new BehaviorSubject(this.skeleton);
|
||||
|
||||
getCurrentSkeleton(): TestSkeleton {
|
||||
return this.skeleton;
|
||||
}
|
||||
}
|
||||
|
||||
class TestSelectionsService {
|
||||
readonly selectionMoveStart$ = new Subject<ISelectionWithStyle[]>();
|
||||
readonly selectionMoving$ = new Subject<ISelectionWithStyle[]>();
|
||||
readonly selectionMoveEnd$ = new Subject<ISelectionWithStyle[]>();
|
||||
readonly selectionSet$ = new Subject<ISelectionWithStyle[]>();
|
||||
private _selections: ISelectionWithStyle[] = [];
|
||||
|
||||
setSelections(selections: ISelectionWithStyle[]): void {
|
||||
this._selections = selections;
|
||||
this.selectionSet$.next(selections);
|
||||
}
|
||||
|
||||
getCurrentSelections(): ISelectionWithStyle[] {
|
||||
return this._selections;
|
||||
}
|
||||
}
|
||||
|
||||
class TestCrosshairHighlightService {
|
||||
readonly enabled$ = new BehaviorSubject(false);
|
||||
readonly highlightColor$ = new BehaviorSubject('rgba(10,20,30,0.6)');
|
||||
}
|
||||
|
||||
class TestContextService {
|
||||
readonly refSelectionEnabled$ = new BehaviorSubject(false);
|
||||
|
||||
subscribeContextValue$() {
|
||||
return this.refSelectionEnabled$.asObservable();
|
||||
}
|
||||
}
|
||||
|
||||
function createSelection(range: IRange): ISelectionWithStyle {
|
||||
return { range } as ISelectionWithStyle;
|
||||
}
|
||||
|
||||
function getShapeRects(scene: TestScene) {
|
||||
return scene.objects.map((shape) => ({
|
||||
left: shape.left,
|
||||
top: shape.top,
|
||||
width: shape.width,
|
||||
height: shape.height,
|
||||
}));
|
||||
}
|
||||
|
||||
function getRenderedShapeCount(controller: SheetCrosshairHighlightRenderController): number {
|
||||
return (controller as unknown as { _shapes: SheetCrossHairHighlightShape[] })._shapes.length;
|
||||
}
|
||||
|
||||
describe('SheetCrosshairHighlightRenderController', () => {
|
||||
it('renders crosshair rectangles from the active normal selection and clears them when disabled', async () => {
|
||||
const workbook = new TestWorkbook();
|
||||
const scene = new TestScene();
|
||||
const skeletonManagerService = new TestSkeletonManagerService();
|
||||
const selectionsService = new TestSelectionsService();
|
||||
const refSelectionsService = new TestSelectionsService();
|
||||
const crosshairHighlightService = new TestCrosshairHighlightService();
|
||||
const contextService = new TestContextService();
|
||||
selectionsService.setSelections([
|
||||
createSelection({
|
||||
startRow: 1,
|
||||
endRow: 1,
|
||||
startColumn: 1,
|
||||
endColumn: 1,
|
||||
}),
|
||||
]);
|
||||
|
||||
const controller = new SheetCrosshairHighlightRenderController(
|
||||
{
|
||||
unit: workbook,
|
||||
scene,
|
||||
} as never,
|
||||
skeletonManagerService as never,
|
||||
selectionsService as never,
|
||||
crosshairHighlightService as never,
|
||||
contextService as never,
|
||||
refSelectionsService as never
|
||||
);
|
||||
|
||||
crosshairHighlightService.enabled$.next(true);
|
||||
|
||||
expect(getRenderedShapeCount(controller)).toBe(4);
|
||||
expect(scene.objects.every((object) => object instanceof SheetCrossHairHighlightShape)).toBe(true);
|
||||
expect(getShapeRects(scene)).toEqual([
|
||||
{ left: 0, top: 20, width: 100, height: 20 },
|
||||
{ left: 200, top: 20, width: 900, height: 20 },
|
||||
{ left: 100, top: 0, width: 100, height: 20 },
|
||||
{ left: 100, top: 40, width: 100, height: 380 },
|
||||
]);
|
||||
expect(scene.dirtyRequests).toEqual([true]);
|
||||
|
||||
crosshairHighlightService.enabled$.next(false);
|
||||
|
||||
expect(getRenderedShapeCount(controller)).toBe(0);
|
||||
|
||||
await controller.dispose();
|
||||
});
|
||||
|
||||
it('switches to reference selections and ignores full-row or full-column selections', async () => {
|
||||
const workbook = new TestWorkbook();
|
||||
const scene = new TestScene();
|
||||
const skeletonManagerService = new TestSkeletonManagerService();
|
||||
const selectionsService = new TestSelectionsService();
|
||||
const refSelectionsService = new TestSelectionsService();
|
||||
const crosshairHighlightService = new TestCrosshairHighlightService();
|
||||
const contextService = new TestContextService();
|
||||
|
||||
selectionsService.setSelections([
|
||||
createSelection({
|
||||
startRow: 2,
|
||||
endRow: 2,
|
||||
startColumn: 2,
|
||||
endColumn: 2,
|
||||
}),
|
||||
]);
|
||||
refSelectionsService.setSelections([
|
||||
createSelection({
|
||||
startRow: 0,
|
||||
endRow: 19,
|
||||
startColumn: 3,
|
||||
endColumn: 3,
|
||||
}),
|
||||
createSelection({
|
||||
startRow: 3,
|
||||
endRow: 3,
|
||||
startColumn: 4,
|
||||
endColumn: 4,
|
||||
}),
|
||||
]);
|
||||
|
||||
const controller = new SheetCrosshairHighlightRenderController(
|
||||
{
|
||||
unit: workbook,
|
||||
scene,
|
||||
} as never,
|
||||
skeletonManagerService as never,
|
||||
selectionsService as never,
|
||||
crosshairHighlightService as never,
|
||||
contextService as never,
|
||||
refSelectionsService as never
|
||||
);
|
||||
|
||||
crosshairHighlightService.enabled$.next(true);
|
||||
expect(getShapeRects(scene).slice(-4)).toEqual([
|
||||
{ left: 0, top: 40, width: 200, height: 20 },
|
||||
{ left: 300, top: 40, width: 800, height: 20 },
|
||||
{ left: 200, top: 0, width: 100, height: 40 },
|
||||
{ left: 200, top: 60, width: 100, height: 360 },
|
||||
]);
|
||||
|
||||
contextService.refSelectionEnabled$.next(true);
|
||||
|
||||
expect(getShapeRects(scene).slice(-4)).toEqual([
|
||||
{ left: 0, top: 60, width: 400, height: 20 },
|
||||
{ left: 500, top: 60, width: 600, height: 20 },
|
||||
{ left: 400, top: 0, width: 100, height: 60 },
|
||||
{ left: 400, top: 80, width: 100, height: 340 },
|
||||
]);
|
||||
expect(getRenderedShapeCount(controller)).toBe(4);
|
||||
|
||||
await controller.dispose();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRgbColor, Nullable } from '@univerjs/core';
|
||||
import type { IShapeProps, UniverRenderingContext } from '@univerjs/engine-render';
|
||||
import { Rect, Shape } from '@univerjs/engine-render';
|
||||
|
||||
export interface ISheetCrossHairHighlightShapeProps extends IShapeProps {
|
||||
color: IRgbColor;
|
||||
}
|
||||
|
||||
export class SheetCrossHairHighlightShape extends Shape<ISheetCrossHairHighlightShapeProps> {
|
||||
// protected _showHighLight = false;
|
||||
protected _color: Nullable<IRgbColor>;
|
||||
|
||||
constructor(key?: string, props?: ISheetCrossHairHighlightShapeProps) {
|
||||
super(key, props);
|
||||
|
||||
if (props) {
|
||||
this.setShapeProps(props);
|
||||
}
|
||||
}
|
||||
|
||||
setShapeProps(props: Partial<ISheetCrossHairHighlightShapeProps>): void {
|
||||
if (typeof props.color !== 'undefined') {
|
||||
this._color = props.color;
|
||||
}
|
||||
|
||||
this.transformByState({
|
||||
width: props.width!,
|
||||
height: props.height!,
|
||||
});
|
||||
}
|
||||
|
||||
protected override _draw(ctx: CanvasRenderingContext2D): void {
|
||||
const color = `rgba(${this._color!.r}, ${this._color!.g}, ${this._color!.b}, ${this._color?.a ?? 0.5})`;
|
||||
|
||||
Rect.drawWith(ctx as UniverRenderingContext, {
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
fill: color,
|
||||
stroke: undefined,
|
||||
strokeWidth: 0,
|
||||
evented: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { IRange, Nullable, Workbook, Worksheet } from '@univerjs/core';
|
||||
import type { IRenderContext, IRenderModule, Scene, SpreadsheetSkeleton } from '@univerjs/engine-render';
|
||||
import type { ISelectionWithStyle } from '@univerjs/sheets';
|
||||
import { ColorKit, Disposable, IContextService, Inject, RANGE_TYPE } from '@univerjs/core';
|
||||
import { IRefSelectionsService, REF_SELECTIONS_ENABLED, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { getCoordByCell, SheetSkeletonManagerService } from '@univerjs/sheets-ui';
|
||||
import { combineLatest, map, merge, startWith, tap } from 'rxjs';
|
||||
import { SHEETS_CROSSHAIR_HIGHLIGHT_Z_INDEX } from '../../const';
|
||||
import { SheetsCrosshairHighlightService } from '../../services/crosshair.service';
|
||||
import { CrossHairRangeCollection } from '../../util';
|
||||
import { SheetCrossHairHighlightShape } from './crosshair-highlight-shape';
|
||||
|
||||
export class SheetCrosshairHighlightRenderController extends Disposable implements IRenderModule {
|
||||
private _shapes: SheetCrossHairHighlightShape[] = [];
|
||||
private _rangeCollection = new CrossHairRangeCollection();
|
||||
private _color: string = 'rgba(255,0,0,0.5)';
|
||||
|
||||
constructor(
|
||||
private readonly _context: IRenderContext<Workbook>,
|
||||
@Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService,
|
||||
@Inject(SheetsSelectionsService) private readonly _sheetsSelectionsService: SheetsSelectionsService,
|
||||
@Inject(SheetsCrosshairHighlightService) private readonly _sheetsCrosshairHighlightService: SheetsCrosshairHighlightService,
|
||||
@Inject(IContextService) private readonly _contextService: IContextService,
|
||||
@IRefSelectionsService private readonly _refSelectionsService: SheetsSelectionsService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initRenderListener();
|
||||
}
|
||||
|
||||
private _transformSelection(selectionData: Readonly<Nullable<ISelectionWithStyle[]>>, sheet: Worksheet): void {
|
||||
if (!selectionData) {
|
||||
return;
|
||||
}
|
||||
const rowCount = sheet.getRowCount();
|
||||
const columnCount = sheet.getColumnCount();
|
||||
const ranges = [];
|
||||
// remove the selection that covers the whole row or column
|
||||
for (const selection of selectionData) {
|
||||
const { startRow, endRow, startColumn, endColumn } = selection.range;
|
||||
if (endRow - startRow + 1 === rowCount || endColumn - startColumn + 1 === columnCount) {
|
||||
continue;
|
||||
}
|
||||
ranges.push(selection.range);
|
||||
}
|
||||
|
||||
this._rangeCollection.setSelectedRanges(ranges);
|
||||
for (const range of ranges) {
|
||||
this.addSelection(range, sheet);
|
||||
}
|
||||
}
|
||||
|
||||
private _initRenderListener(): void {
|
||||
const workbook = this._context.unit;
|
||||
|
||||
this.disposeWithMe(combineLatest([
|
||||
this._contextService.subscribeContextValue$(REF_SELECTIONS_ENABLED).pipe(startWith(false)),
|
||||
this._sheetSkeletonManagerService.currentSkeleton$,
|
||||
this._sheetsCrosshairHighlightService.enabled$,
|
||||
this._sheetsCrosshairHighlightService.highlightColor$.pipe(tap((color) => (this._color = color))),
|
||||
|
||||
merge(
|
||||
this._sheetsSelectionsService.selectionMoveStart$,
|
||||
this._sheetsSelectionsService.selectionMoving$,
|
||||
this._sheetsSelectionsService.selectionMoveEnd$,
|
||||
this._sheetsSelectionsService.selectionSet$,
|
||||
workbook.activeSheet$.pipe(map(() => this._sheetsSelectionsService.getCurrentSelections()))
|
||||
),
|
||||
merge(
|
||||
this._refSelectionsService.selectionMoveStart$,
|
||||
this._refSelectionsService.selectionMoving$,
|
||||
this._refSelectionsService.selectionMoveEnd$,
|
||||
this._sheetsSelectionsService.selectionSet$,
|
||||
workbook.activeSheet$.pipe(map(() => this._refSelectionsService.getCurrentSelections()))
|
||||
),
|
||||
]).subscribe(([refSelectionEnabled, _, enabled, _color, normalSelections, refSelection]) => {
|
||||
this._clear();
|
||||
|
||||
if (!enabled) return;
|
||||
const selections = refSelectionEnabled ? refSelection : normalSelections;
|
||||
|
||||
this._rangeCollection.reset();
|
||||
this._transformSelection(selections, workbook.getActiveSheet());
|
||||
this.render(this._rangeCollection.getRanges());
|
||||
}));
|
||||
}
|
||||
|
||||
addSelection(range: IRange, sheet: Worksheet): void {
|
||||
if (range.rangeType === RANGE_TYPE.COLUMN || range.rangeType === RANGE_TYPE.ROW || range.rangeType === RANGE_TYPE.ALL) {
|
||||
return;
|
||||
}
|
||||
const maxRow = sheet.getRowCount();
|
||||
const maxColumn = sheet.getColumnCount();
|
||||
const { startRow, endRow, startColumn, endColumn } = range;
|
||||
const left = {
|
||||
startRow,
|
||||
endRow,
|
||||
startColumn: 0,
|
||||
endColumn: startColumn - 1,
|
||||
};
|
||||
const right = {
|
||||
startRow,
|
||||
endRow,
|
||||
startColumn: endColumn + 1,
|
||||
endColumn: maxColumn,
|
||||
};
|
||||
const top = {
|
||||
startRow: 0,
|
||||
endRow: startRow - 1,
|
||||
startColumn,
|
||||
endColumn,
|
||||
};
|
||||
const bottom = {
|
||||
startRow: endRow + 1,
|
||||
endRow: maxRow,
|
||||
startColumn,
|
||||
endColumn,
|
||||
};
|
||||
|
||||
for (const range of [left, right, top, bottom]) {
|
||||
// test if the range is valid
|
||||
if (range.startRow <= range.endRow && range.startColumn <= range.endColumn) {
|
||||
this._rangeCollection.addRange(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _clear(): void {
|
||||
this._shapes.forEach((shape) => {
|
||||
shape.dispose();
|
||||
});
|
||||
this._shapes = [];
|
||||
}
|
||||
|
||||
private _addShapes(range: IRange, index: number, scene: Scene, skeleton: SpreadsheetSkeleton): void {
|
||||
const { startRow, endRow, startColumn, endColumn } = range;
|
||||
const startPosition = getCoordByCell(startRow, startColumn, scene, skeleton);
|
||||
const endPosition = getCoordByCell(endRow, endColumn, scene, skeleton);
|
||||
const { startX, startY } = startPosition;
|
||||
const { endX, endY } = endPosition;
|
||||
const width = endX - startX;
|
||||
const height = endY - startY;
|
||||
|
||||
const shapeProps = {
|
||||
left: startX,
|
||||
top: startY,
|
||||
color: new ColorKit(this._color).toRgb(),
|
||||
width,
|
||||
height,
|
||||
zIndex: SHEETS_CROSSHAIR_HIGHLIGHT_Z_INDEX,
|
||||
evented: false,
|
||||
};
|
||||
const currentShapes = new SheetCrossHairHighlightShape(`crosshair-${index}`, shapeProps);
|
||||
this._shapes.push(currentShapes);
|
||||
scene.addObject(currentShapes);
|
||||
}
|
||||
|
||||
render(ranges: IRange[]): void {
|
||||
const skeleton = this._sheetSkeletonManagerService.getCurrentSkeleton();
|
||||
if (!skeleton) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { scene } = this._context;
|
||||
this._clear();
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const range = ranges[i];
|
||||
this._addShapes(range, i, scene, skeleton);
|
||||
}
|
||||
scene.makeDirty(true);
|
||||
}
|
||||
|
||||
override async dispose(): Promise<void> {
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
27
packages/sheets-crosshair-highlight/tailwind.config.ts
Normal file
27
packages/sheets-crosshair-highlight/tailwind.config.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Copyright 2023-present DreamNum Co., Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import type { Config } from 'tailwindcss';
|
||||
import preset from '@univerjs-infra/shared/tailwind';
|
||||
|
||||
const config: Config = {
|
||||
presets: [preset],
|
||||
content: [
|
||||
'./src/**/*.{js,ts,jsx,tsx}',
|
||||
],
|
||||
};
|
||||
|
||||
export default config;
|
||||
8
packages/sheets-crosshair-highlight/tsconfig.json
Normal file
8
packages/sheets-crosshair-highlight/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
18
packages/sheets-crosshair-highlight/tsconfig.node.json
Normal file
18
packages/sheets-crosshair-highlight/tsconfig.node.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/node",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"declaration": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"src/**/__tests__/**",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.ts"
|
||||
]
|
||||
}
|
||||
3
packages/sheets-crosshair-highlight/vitest.config.ts
Normal file
3
packages/sheets-crosshair-highlight/vitest.config.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import createConfig from '@univerjs-infra/shared/vitest';
|
||||
|
||||
export default createConfig();
|
||||
Reference in New Issue
Block a user