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:
32
common/debugger/README.md
Normal file
32
common/debugger/README.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# @univerjs/debugger
|
||||
|
||||
> [!CAUTION]
|
||||
> NEVER use this plugin in your production environment!
|
||||
|
||||
## Package Overview
|
||||
|
||||
| Package Name | UMD Namespace | Version | License | Downloads | Contains CSS | Contains i18n locales |
|
||||
| --- | --- | --- | --- | --- | :---: | :---: |
|
||||
| `@univerjs/debugger` | `UniverDebugger` | [![][npm-version-shield]][npm-version-link] | ![][npm-license-shield] | ![][npm-downloads-shield] | ⭕️ | ❌ |
|
||||
|
||||
## Introduction
|
||||
|
||||
> This plugin provides a lot of utilities to help you debug Univer.
|
||||
|
||||
## Usage
|
||||
|
||||
### Installation
|
||||
|
||||
```shell
|
||||
# Using npm
|
||||
npm install @univerjs/debugger
|
||||
|
||||
# Using pnpm
|
||||
pnpm add @univerjs/debugger
|
||||
```
|
||||
|
||||
<!-- Links -->
|
||||
[npm-version-shield]: https://img.shields.io/npm/v/@univerjs/debugger?style=flat-square
|
||||
[npm-version-link]: https://npmjs.com/package/@univerjs/debugger
|
||||
[npm-license-shield]: https://img.shields.io/npm/l/@univerjs/debugger?style=flat-square
|
||||
[npm-downloads-shield]: https://img.shields.io/npm/dm/@univerjs/debugger?style=flat-square
|
||||
54
common/debugger/package.json
Normal file
54
common/debugger/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "@univerjs/debugger",
|
||||
"version": "1.0.0-rc.0",
|
||||
"private": true,
|
||||
"description": "",
|
||||
"author": "DreamNum <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": [],
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./*": "./src/*"
|
||||
},
|
||||
"main": "./src/index.ts",
|
||||
"scripts": {
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"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/docs-drawing-ui": "workspace:*",
|
||||
"@univerjs/engine-render": "workspace:*",
|
||||
"@univerjs/icons": "1.38.0",
|
||||
"@univerjs/sheets": "workspace:*",
|
||||
"@univerjs/sheets-drawing-ui": "workspace:*",
|
||||
"@univerjs/themes": "workspace:*",
|
||||
"@univerjs/ui": "workspace:*",
|
||||
"@univerjs/watermark": "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",
|
||||
"vue": "^3.5.42"
|
||||
}
|
||||
}
|
||||
1
common/debugger/postcss.config.mjs
Normal file
1
common/debugger/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@univerjs-infra/shared/postcss';
|
||||
68
common/debugger/src/components/FloatButton.tsx
Normal file
68
common/debugger/src/components/FloatButton.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
export const FloatButton = () => {
|
||||
const divStyle = {
|
||||
width: '100px',
|
||||
height: '30px',
|
||||
backgroundColor: '#fff',
|
||||
border: '1px solid #ccc',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center' as const,
|
||||
};
|
||||
const clickHandler = () => {
|
||||
console.warn('click');
|
||||
};
|
||||
return (
|
||||
<div style={divStyle} onClick={clickHandler}>
|
||||
FloatButton
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const AIButton = () => {
|
||||
const divStyle = {
|
||||
width: '80px',
|
||||
height: '50px',
|
||||
backgroundColor: '#fff',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center' as const,
|
||||
borderRadius: '25px',
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.3s ease',
|
||||
|
||||
background: 'linear-gradient(90deg, #00C9FF 0%, #92FE9D 50%, #00C9FF 100%)',
|
||||
backgroundSize: '200% auto',
|
||||
animation: 'gradient 3s linear infinite',
|
||||
|
||||
':hover': {
|
||||
transform: 'translateY(-2px)',
|
||||
boxShadow: '0 10px 20px rgba(0, 201, 255, 0.3)',
|
||||
},
|
||||
};
|
||||
const clickHandler = () => {
|
||||
console.warn('click');
|
||||
};
|
||||
return (
|
||||
<button type="button" style={divStyle} onClick={clickHandler}>
|
||||
AI
|
||||
<style>
|
||||
{`
|
||||
@keyframes gradient {
|
||||
0% { background-position: 0% 50%; }
|
||||
50% { background-position: 100% 50%; }
|
||||
100% { background-position: 0% 50%; }
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 20px rgba(0, 201, 255, 0.3);
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
5
common/debugger/src/components/Image.tsx
Normal file
5
common/debugger/src/components/Image.tsx
Normal file
File diff suppressed because one or more lines are too long
15
common/debugger/src/components/RangeLoading.tsx
Normal file
15
common/debugger/src/components/RangeLoading.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { borderClassName, clsx } from '@univerjs/design';
|
||||
|
||||
export const RangeLoading = () => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(`
|
||||
univer-flex univer-size-full univer-origin-top-left univer-items-center univer-justify-center
|
||||
univer-bg-gray-0
|
||||
dark:!univer-bg-gray-900
|
||||
`, borderClassName)}
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
);
|
||||
};
|
||||
16
common/debugger/src/components/VueComponent.vue
Normal file
16
common/debugger/src/components/VueComponent.vue
Normal file
@@ -0,0 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
const state = ref<'sun' | 'moon'>('sun');
|
||||
|
||||
function toggleState() {
|
||||
state.value = state.value === 'sun' ? 'moon' : 'sun';
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div @click="toggleState">
|
||||
<span v-if="state === 'sun'">🌞</span>
|
||||
<span v-if="state === 'moon'">🌚</span>
|
||||
</div>
|
||||
</template>
|
||||
47
common/debugger/src/components/WebComponent.ts
Normal file
47
common/debugger/src/components/WebComponent.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
export class CounterComponent extends HTMLElement {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
const shadow = this.attachShadow({ mode: 'open' });
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.setAttribute('class', 'counter-container');
|
||||
|
||||
const countDisplay = document.createElement('span');
|
||||
countDisplay.setAttribute('class', 'count-display');
|
||||
countDisplay.textContent = '0';
|
||||
|
||||
const a = document.createElement('a');
|
||||
a.textContent = '🍔';
|
||||
|
||||
a.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
const currentCount = Number.parseInt(countDisplay.textContent || '0', 10);
|
||||
countDisplay.textContent = (currentCount + 1).toString();
|
||||
});
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.counter-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.count-display {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
a {
|
||||
font-size: 10px;
|
||||
}`;
|
||||
|
||||
shadow.appendChild(style);
|
||||
shadow.appendChild(container);
|
||||
container.appendChild(countDisplay);
|
||||
container.appendChild(a);
|
||||
}
|
||||
}
|
||||
25
common/debugger/src/config/config.ts
Normal file
25
common/debugger/src/config/config.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { ILanguagePack, LocaleType, UniverInstanceType } from '@univerjs/core';
|
||||
import type { MenuConfig } from '@univerjs/ui';
|
||||
|
||||
export const DEBUGGER_PLUGIN_CONFIG_KEY = 'debugger.config';
|
||||
|
||||
export const configSymbol = Symbol(DEBUGGER_PLUGIN_CONFIG_KEY);
|
||||
|
||||
export type UniverDebuggerLocaleLoader = (locale: LocaleType) => ILanguagePack | Promise<ILanguagePack>;
|
||||
|
||||
export interface IUniverDebuggerConfig {
|
||||
menu?: MenuConfig;
|
||||
fab?: boolean;
|
||||
fabEntryUnitType: UniverInstanceType;
|
||||
localeLoader: UniverDebuggerLocaleLoader;
|
||||
performanceMonitor?: {
|
||||
enabled: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const defaultPluginConfig: Pick<IUniverDebuggerConfig, 'fab' | 'performanceMonitor'> = {
|
||||
fab: true,
|
||||
performanceMonitor: {
|
||||
enabled: true,
|
||||
},
|
||||
};
|
||||
44
common/debugger/src/controllers/components.controller.ts
Normal file
44
common/debugger/src/controllers/components.controller.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Disposable, Inject } from '@univerjs/core';
|
||||
import { ComponentManager } from '@univerjs/ui';
|
||||
import { AIButton, FloatButton } from '../components/FloatButton';
|
||||
import { ImageDemo } from '../components/Image';
|
||||
import { RangeLoading } from '../components/RangeLoading';
|
||||
// @ts-ignore
|
||||
// import VueComponent from '../components/VueComponent.vue';
|
||||
// import { CounterComponent } from '../components/WebComponent';
|
||||
import { WatermarkPanel } from '../views/watermark/WatermarkPanel';
|
||||
import { WatermarkPanelFooter } from '../views/watermark/WatermarkPanelFooter';
|
||||
|
||||
export const WATERMARK_PANEL = 'WATERMARK_PANEL';
|
||||
export const WATERMARK_PANEL_FOOTER = 'WATERMARK_PANEL_FOOTER';
|
||||
|
||||
export class ComponentsController extends Disposable {
|
||||
constructor(
|
||||
@Inject(ComponentManager) private readonly _componentManager: ComponentManager
|
||||
) {
|
||||
super();
|
||||
|
||||
this._registerComponents();
|
||||
}
|
||||
|
||||
private _registerComponents(): void {
|
||||
([
|
||||
['ImageDemo', ImageDemo],
|
||||
['RangeLoading', RangeLoading],
|
||||
['FloatButton', FloatButton],
|
||||
['AIButton', AIButton],
|
||||
[WATERMARK_PANEL, WatermarkPanel],
|
||||
[WATERMARK_PANEL_FOOTER, WatermarkPanelFooter],
|
||||
] as const).forEach(([key, component]) => {
|
||||
this.disposeWithMe(this._componentManager.register(key, component));
|
||||
});
|
||||
|
||||
// this.disposeWithMe(this._componentManager.register('VueComponent', VueComponent, {
|
||||
// framework: 'vue3',
|
||||
// }));
|
||||
|
||||
// this.disposeWithMe(this._componentManager.register('counter-component', CounterComponent, {
|
||||
// framework: 'web-component',
|
||||
// }));
|
||||
}
|
||||
}
|
||||
62
common/debugger/src/controllers/debugger.controller.ts
Normal file
62
common/debugger/src/controllers/debugger.controller.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import type { IUniverDebuggerConfig } from '../config/config';
|
||||
import { Disposable, IConfigService, Inject, Injector } from '@univerjs/core';
|
||||
import {
|
||||
BuiltInUIPart,
|
||||
connectInjector,
|
||||
IMenuManagerService,
|
||||
IUIPartsService,
|
||||
// MenuItemType,
|
||||
// RibbonStartGroup,
|
||||
} from '@univerjs/ui';
|
||||
import { DEBUGGER_PLUGIN_CONFIG_KEY } from '../config/config';
|
||||
import { Fab } from '../views/Fab';
|
||||
import { RecordController } from './local-save/record.controller';
|
||||
|
||||
export class DebuggerController extends Disposable {
|
||||
constructor(
|
||||
@Inject(Injector) private readonly _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService,
|
||||
@IUIPartsService protected readonly _uiPartsService: IUIPartsService,
|
||||
@IMenuManagerService private readonly _menuManagerService: IMenuManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
this._initCustomComponents();
|
||||
// this._menuManagerService.mergeMenu({
|
||||
// [RibbonStartGroup.OTHERS]: {
|
||||
// EMOJI: {
|
||||
// order: 9999,
|
||||
// menuItemFactory: () => {
|
||||
// return {
|
||||
// id: 'EMOJI',
|
||||
// icon: 'VueComponent',
|
||||
// type: MenuItemType.BUTTON,
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
// COUNTER: {
|
||||
// order: 9999,
|
||||
// menuItemFactory: () => {
|
||||
// return {
|
||||
// id: 'COUNTER',
|
||||
// icon: 'counter-component',
|
||||
// type: MenuItemType.BUTTON,
|
||||
// };
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
|
||||
this._injector.add([RecordController]);
|
||||
}
|
||||
|
||||
private _initCustomComponents(): void {
|
||||
const configs = this._configService.getConfig<IUniverDebuggerConfig>(DEBUGGER_PLUGIN_CONFIG_KEY);
|
||||
|
||||
if (configs?.fab) {
|
||||
this.disposeWithMe(
|
||||
this._uiPartsService.registerComponent(BuiltInUIPart.GLOBAL, () => connectInjector(Fab, this._injector))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { CommandType, ICommandService, Inject } from '@univerjs/core';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
export class RecordController {
|
||||
constructor(@Inject(ICommandService) private _commandService: ICommandService) {
|
||||
// empty
|
||||
}
|
||||
|
||||
record() {
|
||||
return new Observable<{ type: 'start' } | { type: 'finish'; data: Blob }>((subscribe) => {
|
||||
navigator.mediaDevices.getDisplayMedia({ video: true }).then((stream) => {
|
||||
subscribe.next({ type: 'start' });
|
||||
const mime = MediaRecorder.isTypeSupported('video/webm; codecs=vp9')
|
||||
? 'video/webm; codecs=vp9'
|
||||
: 'video/webm';
|
||||
const mediaRecorder = new MediaRecorder(stream, { mimeType: mime });
|
||||
|
||||
const chunks: Blob[] = [];
|
||||
mediaRecorder.addEventListener('dataavailable', function (e) {
|
||||
chunks.push(e.data);
|
||||
});
|
||||
|
||||
mediaRecorder.addEventListener('stop', function () {
|
||||
const blob = new Blob(chunks, { type: chunks[0].type });
|
||||
subscribe.next({ type: 'finish', data: blob });
|
||||
subscribe.complete();
|
||||
});
|
||||
|
||||
mediaRecorder.start();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
startSaveCommands() {
|
||||
type SecondsString = string & {};
|
||||
type CommandString = string & {};
|
||||
type TypeString = string & {};
|
||||
type ParamsString = string & {};
|
||||
const result: Array<[SecondsString, CommandString, TypeString, ParamsString]> = [];
|
||||
const startTime = performance.now();
|
||||
const disposable = this._commandService.beforeCommandExecuted((commandInfo) => {
|
||||
try {
|
||||
result.push([
|
||||
String((performance.now() - startTime) / 1000),
|
||||
commandInfo.id,
|
||||
String(commandInfo.type || CommandType.COMMAND),
|
||||
JSON.stringify(commandInfo.params || ''),
|
||||
]);
|
||||
} catch (err) {
|
||||
console.error(`${commandInfo.id} unable to serialize`);
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
return result;
|
||||
};
|
||||
}
|
||||
}
|
||||
8
common/debugger/src/controllers/local-save/util.ts
Normal file
8
common/debugger/src/controllers/local-save/util.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export function createDownloadElement(fileName: string, blob: Blob) {
|
||||
const a = document.createElement('a');
|
||||
a.download = fileName;
|
||||
a.href = window.URL.createObjectURL(blob);
|
||||
return () => {
|
||||
a.click();
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { Nullable } from '@univerjs/core';
|
||||
import type { Subscription } from 'rxjs';
|
||||
import { Inject, IUniverInstanceService, LifecycleService, LifecycleStages, RxDisposable } from '@univerjs/core';
|
||||
import { IRenderManagerService } from '@univerjs/engine-render';
|
||||
import { distinctUntilChanged, filter, take, takeUntil } from 'rxjs';
|
||||
|
||||
export class PerformanceMonitorController extends RxDisposable {
|
||||
private _containerElement: HTMLDivElement | null;
|
||||
|
||||
private _currentUnitSub: Nullable<Subscription>;
|
||||
|
||||
constructor(
|
||||
@Inject(LifecycleService) lifecycleService: LifecycleService,
|
||||
@IUniverInstanceService private readonly _instanceService: IUniverInstanceService,
|
||||
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService
|
||||
) {
|
||||
super();
|
||||
|
||||
lifecycleService.subscribeWithPrevious()
|
||||
.pipe(
|
||||
filter((stage) => stage === LifecycleStages.Rendered),
|
||||
take(1)
|
||||
)
|
||||
.subscribe(() => this._listenDocumentTypeChange());
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
this._disposeCurrentObserver();
|
||||
}
|
||||
|
||||
private _disposeCurrentObserver(): void {
|
||||
this._currentUnitSub?.unsubscribe();
|
||||
this._currentUnitSub = null;
|
||||
}
|
||||
|
||||
private _listenDocumentTypeChange() {
|
||||
this._instanceService.focused$.pipe(takeUntil(this.dispose$), distinctUntilChanged()).subscribe((unitId) => {
|
||||
this._disposeCurrentObserver();
|
||||
if (unitId) {
|
||||
this._listenToRenderer(unitId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _listenToRenderer(unitId: string) {
|
||||
const renderer = this._renderManagerService.getRenderUnitById(unitId);
|
||||
if (renderer) {
|
||||
const { engine } = renderer;
|
||||
this._currentUnitSub = engine.endFrame$.subscribe(() => {
|
||||
if (!this._containerElement) {
|
||||
this._containerElement = document.querySelector('[data-u-comp=debugger-fps]');
|
||||
} else {
|
||||
this._containerElement.textContent = `FPS: ${Math.round(engine.getFps()).toString()}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
2
common/debugger/src/global.css
Normal file
2
common/debugger/src/global.css
Normal file
@@ -0,0 +1,2 @@
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
4
common/debugger/src/index.ts
Normal file
4
common/debugger/src/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import './global.css';
|
||||
|
||||
export type { IUniverDebuggerConfig, UniverDebuggerLocaleLoader } from './config/config';
|
||||
export { UniverDebuggerPlugin } from './plugin';
|
||||
66
common/debugger/src/plugin.ts
Normal file
66
common/debugger/src/plugin.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import type { Dependency } from '@univerjs/core';
|
||||
import type { IUniverDebuggerConfig } from './config/config';
|
||||
import { IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies } from '@univerjs/core';
|
||||
import pkg from '../package.json';
|
||||
import { DEBUGGER_PLUGIN_CONFIG_KEY, defaultPluginConfig } from './config/config';
|
||||
import { ComponentsController } from './controllers/components.controller';
|
||||
import { DebuggerController } from './controllers/debugger.controller';
|
||||
import { PerformanceMonitorController } from './controllers/performance-monitor.controller';
|
||||
|
||||
export class UniverDebuggerPlugin extends Plugin {
|
||||
static override pluginName = 'UNIVER_DEBUGGER_PLUGIN';
|
||||
static override packageName = pkg.name;
|
||||
static override version = pkg.version;
|
||||
|
||||
private _debuggerController!: DebuggerController;
|
||||
|
||||
constructor(
|
||||
private readonly _config: IUniverDebuggerConfig,
|
||||
@Inject(Injector) override readonly _injector: Injector,
|
||||
@IConfigService private readonly _configService: IConfigService
|
||||
) {
|
||||
super();
|
||||
|
||||
// Manage the plugin configuration.
|
||||
const { menu, ...rest } = merge(
|
||||
{},
|
||||
defaultPluginConfig,
|
||||
this._config
|
||||
);
|
||||
if (menu) {
|
||||
this._configService.setConfig('menu', menu, { merge: true });
|
||||
}
|
||||
this._configService.setConfig(DEBUGGER_PLUGIN_CONFIG_KEY, rest);
|
||||
}
|
||||
|
||||
override onStarting(): void {
|
||||
const dependencies: Dependency[] = [
|
||||
[DebuggerController],
|
||||
[ComponentsController],
|
||||
];
|
||||
|
||||
if (this._config.performanceMonitor?.enabled !== false) {
|
||||
dependencies.push([PerformanceMonitorController]);
|
||||
}
|
||||
|
||||
registerDependencies(this._injector, dependencies);
|
||||
}
|
||||
|
||||
override onReady(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[ComponentsController],
|
||||
[DebuggerController],
|
||||
]);
|
||||
}
|
||||
|
||||
override onRendered(): void {
|
||||
touchDependencies(this._injector, [
|
||||
[PerformanceMonitorController],
|
||||
]);
|
||||
}
|
||||
|
||||
getDebuggerController() {
|
||||
this._debuggerController = this._injector.get(DebuggerController);
|
||||
return this._debuggerController;
|
||||
}
|
||||
}
|
||||
116
common/debugger/src/views/Fab.tsx
Normal file
116
common/debugger/src/views/Fab.tsx
Normal file
@@ -0,0 +1,116 @@
|
||||
import type { IDropdownMenuProps } from '@univerjs/design';
|
||||
import type { IUniverDebuggerConfig } from '../config/config';
|
||||
import { IConfigService, UniverInstanceType } from '@univerjs/core';
|
||||
import { borderClassName, clsx, DropdownMenu } from '@univerjs/design';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { DEBUGGER_PLUGIN_CONFIG_KEY } from '../config/config';
|
||||
import { useCellContent } from './use-cell-content';
|
||||
import { useDarkMode } from './use-dark-mode';
|
||||
import { useDialog } from './use-dialog';
|
||||
import { useDispose } from './use-dispose';
|
||||
import { useEditable } from './use-editable';
|
||||
import { useFloatingDom } from './use-floating-dom';
|
||||
import { useLocale } from './use-locale';
|
||||
import { useMessage } from './use-message';
|
||||
import { useNotification } from './use-notification';
|
||||
import { useRTL } from './use-rtl';
|
||||
import { useSidebar } from './use-sidebar';
|
||||
import { useSnapshot } from './use-snapshot';
|
||||
import { useTheme } from './use-theme';
|
||||
import { useUnits } from './use-units';
|
||||
import { useUser } from './use-user';
|
||||
import { useWatermark } from './use-watermark';
|
||||
|
||||
export function Fab() {
|
||||
const configService = useDependency(IConfigService);
|
||||
const configs = configService.getConfig<IUniverDebuggerConfig>(DEBUGGER_PLUGIN_CONFIG_KEY);
|
||||
const performanceMonitor = configs?.performanceMonitor;
|
||||
const fabEntryUnitType = configs?.fabEntryUnitType;
|
||||
|
||||
const locale = useLocale();
|
||||
const rtl = useRTL();
|
||||
const darkMode = useDarkMode();
|
||||
const theme = useTheme();
|
||||
const watermark = useWatermark();
|
||||
const notification = useNotification();
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const sidebar = useSidebar();
|
||||
const floatingDom = useFloatingDom(fabEntryUnitType);
|
||||
const cellContent = useCellContent(fabEntryUnitType);
|
||||
const units = useUnits();
|
||||
const snapshot = useSnapshot();
|
||||
const editable = useEditable();
|
||||
const user = useUser();
|
||||
const dispose = useDispose();
|
||||
|
||||
const globalItems = [locale, rtl, darkMode, theme];
|
||||
const lightweightItems = [
|
||||
...globalItems,
|
||||
watermark,
|
||||
];
|
||||
const commonDebugItems = [
|
||||
watermark,
|
||||
{ type: 'separator' as const },
|
||||
notification,
|
||||
message,
|
||||
dialog,
|
||||
sidebar,
|
||||
{ type: 'separator' as const },
|
||||
snapshot,
|
||||
editable,
|
||||
dispose,
|
||||
];
|
||||
const sheetItems = [
|
||||
...globalItems,
|
||||
watermark,
|
||||
{ type: 'separator' as const },
|
||||
notification,
|
||||
message,
|
||||
dialog,
|
||||
sidebar,
|
||||
{ type: 'separator' as const },
|
||||
floatingDom,
|
||||
cellContent,
|
||||
units,
|
||||
snapshot,
|
||||
editable,
|
||||
user,
|
||||
dispose,
|
||||
];
|
||||
|
||||
const items: IDropdownMenuProps['items'] = fabEntryUnitType === UniverInstanceType.UNIVER_BASE || fabEntryUnitType === UniverInstanceType.UNIVER_SLIDE
|
||||
? lightweightItems
|
||||
: fabEntryUnitType === UniverInstanceType.UNIVER_DOC
|
||||
? [...globalItems, ...commonDebugItems, floatingDom].filter(Boolean) as IDropdownMenuProps['items']
|
||||
: sheetItems.filter(Boolean) as IDropdownMenuProps['items'];
|
||||
|
||||
return (
|
||||
<div
|
||||
data-u-comp="debugger-fab"
|
||||
className={`
|
||||
univer-fixed univer-bottom-12 univer-right-8 univer-z-[9999] univer-flex univer-select-none
|
||||
univer-flex-col univer-items-center univer-gap-1
|
||||
`}
|
||||
>
|
||||
<DropdownMenu align="end" items={items}>
|
||||
<button
|
||||
className={clsx(`
|
||||
univer-flex univer-size-9 univer-cursor-pointer univer-items-center univer-justify-center
|
||||
univer-rounded-full univer-bg-gray-0 univer-text-base univer-text-gray-900 univer-shadow
|
||||
univer-outline-none univer-transition-shadow
|
||||
hover:univer-ring-1 hover:univer-ring-primary-400
|
||||
dark:!univer-bg-gray-900 dark:!univer-text-gray-200
|
||||
`, borderClassName)}
|
||||
type="button"
|
||||
>
|
||||
🏗️
|
||||
</button>
|
||||
</DropdownMenu>
|
||||
|
||||
{performanceMonitor?.enabled && (
|
||||
<span data-u-comp="debugger-fps" className="univer-text-xs univer-text-gray-400" />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
common/debugger/src/views/test-editor/component-name.ts
Normal file
1
common/debugger/src/views/test-editor/component-name.ts
Normal file
@@ -0,0 +1 @@
|
||||
export const TEST_EDITOR_CONTAINER_COMPONENT = 'TestEditorContainer';
|
||||
28
common/debugger/src/views/use-cell-content.ts
Normal file
28
common/debugger/src/views/use-cell-content.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { ILogService, IUniverInstanceService, ObjectMatrix, Range, UniverInstanceType } from '@univerjs/core';
|
||||
import { getSheetCommandTarget, SheetsSelectionsService } from '@univerjs/sheets';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
export function useCellContent(fabEntryUnitType?: UniverInstanceType) {
|
||||
const logService = useDependency(ILogService);
|
||||
const selectionManagerService = fabEntryUnitType === UniverInstanceType.UNIVER_SHEET ? useDependency(SheetsSelectionsService) : null;
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
|
||||
const onSelect = () => {
|
||||
if (!selectionManagerService) return;
|
||||
const selections = selectionManagerService.getCurrentSelections();
|
||||
const target = getSheetCommandTarget(univerInstanceService);
|
||||
const matrix = new ObjectMatrix();
|
||||
selections.forEach((selection) => {
|
||||
Range.foreach(selection.range, (row, col) => {
|
||||
matrix.setValue(row, col, target?.worksheet.getCell(row, col));
|
||||
});
|
||||
});
|
||||
logService.debug('cell-content', matrix);
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'item' as const,
|
||||
children: '🗒️ Console cell content',
|
||||
onSelect,
|
||||
};
|
||||
}
|
||||
31
common/debugger/src/views/use-dark-mode.ts
Normal file
31
common/debugger/src/views/use-dark-mode.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { ThemeService } from '@univerjs/core';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useDarkMode() {
|
||||
const themeService = useDependency(ThemeService);
|
||||
|
||||
useEffect(() => {
|
||||
const darkMode = localStorage.getItem('local.darkMode');
|
||||
|
||||
if (darkMode === 'dark') {
|
||||
document.documentElement.classList.add('univer-dark');
|
||||
themeService.setDarkMode(true);
|
||||
} else if (darkMode === 'system') {
|
||||
themeService.setDarkMode(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onSelect = () => {
|
||||
const darkMode = themeService.darkMode ? 'light' : 'dark';
|
||||
|
||||
themeService.setDarkMode(darkMode === 'dark');
|
||||
localStorage.setItem('local.darkMode', darkMode);
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'item' as const,
|
||||
children: '🌓 Toggle dark mode',
|
||||
onSelect,
|
||||
};
|
||||
}
|
||||
89
common/debugger/src/views/use-dialog.ts
Normal file
89
common/debugger/src/views/use-dialog.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { IConfirmService } from '@univerjs/core';
|
||||
import { IDialogService, useDependency } from '@univerjs/ui';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Open dialog',
|
||||
value: 'dialog',
|
||||
},
|
||||
{
|
||||
label: 'Draggable dialog',
|
||||
value: 'draggable',
|
||||
},
|
||||
{
|
||||
label: 'Open confirm',
|
||||
value: 'confirm',
|
||||
},
|
||||
];
|
||||
|
||||
export function useDialog() {
|
||||
const dialogService = useDependency(IDialogService);
|
||||
const confirmService = useDependency(IConfirmService);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
if (value === 'draggable') {
|
||||
dialogService.open({
|
||||
id: 'draggable',
|
||||
children: { title: 'Draggable Dialog Content' },
|
||||
title: { title: 'Draggable Dialog' },
|
||||
draggable: true,
|
||||
preservePositionOnDestroy: true,
|
||||
width: 350,
|
||||
onClose() {
|
||||
// dialogService.close('draggable');
|
||||
},
|
||||
onOpenChange(open: boolean) {
|
||||
if (!open) {
|
||||
dialogService.close('draggable');
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (value === 'dialog') {
|
||||
dialogService.open({
|
||||
id: 'dialog1',
|
||||
children: { title: 'Dialog Content' },
|
||||
footer: { title: 'Dialog Footer' },
|
||||
title: { title: 'Dialog Title' },
|
||||
draggable: false,
|
||||
onClose() {
|
||||
// dialogService.close('dialog1');
|
||||
},
|
||||
onOpenChange(open: boolean) {
|
||||
if (!open) {
|
||||
dialogService.close('dialog1');
|
||||
}
|
||||
},
|
||||
});
|
||||
} else if (value === 'confirm') {
|
||||
confirmService.open({
|
||||
id: 'confirm1',
|
||||
children: { title: 'Confirm Content' },
|
||||
title: { title: 'Confirm Title' },
|
||||
confirmText: 'hello',
|
||||
cancelText: 'world',
|
||||
onClose() {
|
||||
confirmService.close('confirm1');
|
||||
},
|
||||
});
|
||||
|
||||
confirmService.open({
|
||||
id: 'confirm2',
|
||||
children: { title: 'Confirm2 Content' },
|
||||
title: { title: 'Confirm2 Title' },
|
||||
onClose() {
|
||||
confirmService.close('confirm2');
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '💬 Dialog & Confirm',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
42
common/debugger/src/views/use-dispose.ts
Normal file
42
common/debugger/src/views/use-dispose.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import type { Univer } from '@univerjs/core';
|
||||
import { IUniverInstanceService } from '@univerjs/core';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Dispose Univer',
|
||||
value: 'univer',
|
||||
},
|
||||
{
|
||||
label: 'Dispose current unit',
|
||||
value: 'unit',
|
||||
},
|
||||
];
|
||||
|
||||
export function useDispose() {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
if (value === 'univer') {
|
||||
const debuggerWindow = window as typeof window & { univer?: Univer };
|
||||
debuggerWindow.univer?.dispose();
|
||||
Reflect.deleteProperty(debuggerWindow, 'univer');
|
||||
Reflect.deleteProperty(debuggerWindow, 'univerAPI');
|
||||
} else if (value === 'unit') {
|
||||
const focused = univerInstanceService.getFocusedUnit();
|
||||
if (!focused) return false;
|
||||
|
||||
return univerInstanceService.disposeUnit(focused.getUnitId());
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '🗑️ Dispose',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
48
common/debugger/src/views/use-editable.ts
Normal file
48
common/debugger/src/views/use-editable.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { IPermissionService, IUniverInstanceService } from '@univerjs/core';
|
||||
import { getSheetCommandTarget, WorkbookEditablePermission, WorksheetEditPermission } from '@univerjs/sheets';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Change workbook editable',
|
||||
value: 'univer',
|
||||
},
|
||||
{
|
||||
label: 'Change worksheet editable',
|
||||
value: 'sheet',
|
||||
},
|
||||
];
|
||||
|
||||
export function useEditable() {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const permissionService = useDependency(IPermissionService);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
const target = getSheetCommandTarget(univerInstanceService);
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
const { workbook, worksheet, unitId, subUnitId } = target;
|
||||
if (!workbook || !worksheet) {
|
||||
return false;
|
||||
}
|
||||
if (value === 'sheet') {
|
||||
const editable = permissionService.getPermissionPoint(new WorksheetEditPermission(unitId, subUnitId).id);
|
||||
permissionService.updatePermissionPoint(new WorksheetEditPermission(unitId, subUnitId).id, !editable);
|
||||
} else {
|
||||
const unitId = workbook!.getUnitId();
|
||||
const editable = permissionService.getPermissionPoint(new WorkbookEditablePermission(unitId).id);
|
||||
permissionService.updatePermissionPoint(new WorkbookEditablePermission(unitId).id, !editable);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '✍️ Editable',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
50
common/debugger/src/views/use-floating-dom.ts
Normal file
50
common/debugger/src/views/use-floating-dom.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { DocFloatDomController } from '@univerjs/docs-drawing-ui';
|
||||
import { SheetCanvasFloatDomManagerService } from '@univerjs/sheets-drawing-ui';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
export function useFloatingDom(entryUnitType?: UniverInstanceType) {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const floatDomService = entryUnitType === UniverInstanceType.UNIVER_SHEET ? useDependency(SheetCanvasFloatDomManagerService) : null;
|
||||
const floatDomController = entryUnitType === UniverInstanceType.UNIVER_DOC ? useDependency(DocFloatDomController) : null;
|
||||
|
||||
const onSelect = () => {
|
||||
if (entryUnitType === UniverInstanceType.UNIVER_SHEET) {
|
||||
const currentSheet = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_SHEET);
|
||||
if (!currentSheet) return;
|
||||
|
||||
floatDomService?.addFloatDomToPosition({
|
||||
allowTransform: true,
|
||||
initPosition: {
|
||||
startX: 200,
|
||||
endX: 400,
|
||||
startY: 200,
|
||||
endY: 400,
|
||||
},
|
||||
componentKey: 'ImageDemo',
|
||||
data: {
|
||||
aa: '128',
|
||||
},
|
||||
});
|
||||
} else if (entryUnitType === UniverInstanceType.UNIVER_DOC) {
|
||||
const currentDoc = univerInstanceService.getCurrentUnitOfType(UniverInstanceType.UNIVER_DOC);
|
||||
if (!currentDoc) return;
|
||||
|
||||
floatDomController?.insertFloatDom({
|
||||
allowTransform: true,
|
||||
componentKey: 'ImageDemo',
|
||||
data: {
|
||||
aa: '128',
|
||||
},
|
||||
}, {
|
||||
height: 300,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'item' as const,
|
||||
children: '☁️ Create floating DOM',
|
||||
onSelect,
|
||||
};
|
||||
}
|
||||
78
common/debugger/src/views/use-locale.ts
Normal file
78
common/debugger/src/views/use-locale.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { IUniverDebuggerConfig } from '../config/config';
|
||||
import { IConfigService, LocaleService, LocaleType } from '@univerjs/core';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { DEBUGGER_PLUGIN_CONFIG_KEY } from '../config/config';
|
||||
|
||||
const localeLabels: Record<LocaleType, string> = {
|
||||
[LocaleType.AR_SA]: 'العربية',
|
||||
[LocaleType.CA_ES]: 'Català',
|
||||
[LocaleType.DE_DE]: 'Deutsch',
|
||||
[LocaleType.EN_US]: 'English',
|
||||
[LocaleType.ES_ES]: 'Español',
|
||||
[LocaleType.FA_IR]: 'فارسی',
|
||||
[LocaleType.FR_FR]: 'Français',
|
||||
[LocaleType.ID_ID]: 'Bahasa Indonesia',
|
||||
[LocaleType.IT_IT]: 'Italiano',
|
||||
[LocaleType.JA_JP]: '日本語',
|
||||
[LocaleType.KO_KR]: '한국어',
|
||||
[LocaleType.PL_PL]: 'Polski',
|
||||
[LocaleType.PT_BR]: 'Português (Brasil)',
|
||||
[LocaleType.RU_RU]: 'Русский',
|
||||
[LocaleType.SK_SK]: 'Slovenčina',
|
||||
[LocaleType.VI_VN]: 'Tiếng Việt',
|
||||
[LocaleType.ZH_CN]: '简体中文',
|
||||
[LocaleType.ZH_HK]: '繁體中文(香港)',
|
||||
[LocaleType.ZH_TW]: '繁體中文(台灣)',
|
||||
};
|
||||
|
||||
export function useLocale() {
|
||||
const configService = useDependency(IConfigService);
|
||||
const localeService = useDependency(LocaleService);
|
||||
const configs = configService.getConfig<IUniverDebuggerConfig>(DEBUGGER_PLUGIN_CONFIG_KEY);
|
||||
const localeLoader = configs?.localeLoader;
|
||||
|
||||
const loadLocales = useCallback(async (value: string) => {
|
||||
const locale = value as LocaleType;
|
||||
if (!localeLoader) {
|
||||
throw new Error('[UniverDebuggerPlugin]: localeLoader is required.');
|
||||
}
|
||||
const localePack = await localeLoader(locale);
|
||||
|
||||
localeService.load({
|
||||
[locale]: localePack,
|
||||
});
|
||||
}, [localeLoader, localeService]);
|
||||
|
||||
useEffect(() => {
|
||||
const locale = localStorage.getItem('local.locale');
|
||||
|
||||
if (locale) {
|
||||
loadLocales(locale).then(() => {
|
||||
localeService.setLocale(locale as LocaleType);
|
||||
});
|
||||
}
|
||||
}, [loadLocales, localeService]);
|
||||
|
||||
const onSelect = async (value: string) => {
|
||||
await loadLocales(value);
|
||||
|
||||
localeService.setLocale(value as LocaleType);
|
||||
|
||||
localStorage.setItem('local.locale', value);
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '🌐 Languages',
|
||||
options: Object.values(LocaleType).map((locale) => ({
|
||||
type: 'radio' as const,
|
||||
value: localeService.getCurrentLocale(),
|
||||
options: [{
|
||||
label: localeLabels[locale],
|
||||
value: locale,
|
||||
}],
|
||||
onSelect,
|
||||
})),
|
||||
};
|
||||
}
|
||||
43
common/debugger/src/views/use-message.ts
Normal file
43
common/debugger/src/views/use-message.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { MessageType } from '@univerjs/design';
|
||||
import { IMessageService, useDependency } from '@univerjs/ui';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Open message',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'Open loading message',
|
||||
value: 'loading',
|
||||
},
|
||||
];
|
||||
|
||||
export function useMessage() {
|
||||
const messageService = useDependency(IMessageService);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
if (value === 'loading') {
|
||||
messageService.show({
|
||||
type: MessageType.Loading,
|
||||
content: 'Loading message',
|
||||
duration: 3000,
|
||||
});
|
||||
} else {
|
||||
messageService.show({
|
||||
type: MessageType.Success,
|
||||
content: 'Demo message',
|
||||
duration: 1500,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '✉️ Message',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
43
common/debugger/src/views/use-notification.ts
Normal file
43
common/debugger/src/views/use-notification.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { INotificationOptions } from '@univerjs/ui';
|
||||
import { INotificationService, useDependency } from '@univerjs/ui';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Notification success',
|
||||
value: 'success',
|
||||
},
|
||||
{
|
||||
label: 'Notification info',
|
||||
value: 'info',
|
||||
},
|
||||
{
|
||||
label: 'Notification warning',
|
||||
value: 'warning',
|
||||
},
|
||||
{
|
||||
label: 'Notification error',
|
||||
value: 'error',
|
||||
},
|
||||
];
|
||||
|
||||
export function useNotification() {
|
||||
const notificationService = useDependency(INotificationService);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
notificationService.show({
|
||||
type: value as INotificationOptions['type'],
|
||||
content: 'Lorem Ipusm dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
title: 'Notification Title',
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '🔔 Notification',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
29
common/debugger/src/views/use-rtl.ts
Normal file
29
common/debugger/src/views/use-rtl.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { LocaleService } from '@univerjs/core';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useRTL() {
|
||||
const localeService = useDependency(LocaleService);
|
||||
|
||||
useEffect(() => {
|
||||
const dir = localStorage.getItem('local.direction');
|
||||
|
||||
if (dir === 'rtl' || dir === 'ltr') {
|
||||
localeService.setDirection(dir);
|
||||
}
|
||||
}, [localeService]);
|
||||
|
||||
const onSelect = () => {
|
||||
const current = localeService.getDirection();
|
||||
const nextDirection = current === 'rtl' ? 'ltr' : 'rtl';
|
||||
|
||||
localeService.setDirection(nextDirection);
|
||||
localStorage.setItem('local.direction', nextDirection);
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'item' as const,
|
||||
children: '↔️ Toggle RTL',
|
||||
onSelect,
|
||||
};
|
||||
}
|
||||
41
common/debugger/src/views/use-sidebar.ts
Normal file
41
common/debugger/src/views/use-sidebar.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { ISidebarService, useDependency } from '@univerjs/ui';
|
||||
import { TEST_EDITOR_CONTAINER_COMPONENT } from './test-editor/component-name';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Open sidebar',
|
||||
value: 'open',
|
||||
},
|
||||
{
|
||||
label: 'Close sidebar',
|
||||
value: 'close',
|
||||
},
|
||||
];
|
||||
|
||||
export function useSidebar() {
|
||||
const sidebarService = useDependency(ISidebarService);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
if (value === 'open') {
|
||||
sidebarService.open({
|
||||
header: { title: 'Sidebar title' },
|
||||
children: { label: TEST_EDITOR_CONTAINER_COMPONENT },
|
||||
footer: { title: 'Sidebar Footer' },
|
||||
onClose: () => {
|
||||
},
|
||||
});
|
||||
} else if (value === 'close') {
|
||||
sidebarService.close();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '🧩 Sidebar',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
128
common/debugger/src/views/use-snapshot.ts
Normal file
128
common/debugger/src/views/use-snapshot.ts
Normal file
@@ -0,0 +1,128 @@
|
||||
/* eslint-disable node/prefer-global/process */
|
||||
import type { DocumentDataModel, IStyleData, IWorkbookData, Workbook } from '@univerjs/core';
|
||||
import { IResourceLoaderService, IUniverInstanceService, ObjectMatrix, UniverInstanceType } from '@univerjs/core';
|
||||
import { ILocalFileService, useDependency } from '@univerjs/ui';
|
||||
import { RecordController } from '../controllers/local-save/record.controller';
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Save workbook',
|
||||
value: 'workbook',
|
||||
},
|
||||
{
|
||||
label: 'Save worksheet',
|
||||
value: 'sheet',
|
||||
},
|
||||
{
|
||||
label: 'Record',
|
||||
value: 'record',
|
||||
},
|
||||
{
|
||||
label: 'Load snapshot',
|
||||
value: 'load',
|
||||
},
|
||||
];
|
||||
|
||||
export interface ISaveSnapshotParams {
|
||||
value: 'sheet' | 'workbook' | 'record';
|
||||
}
|
||||
|
||||
const filterStyle = (workbookData: IWorkbookData) => {
|
||||
const sheets = workbookData.sheets;
|
||||
const cacheStyle: Record<string, IStyleData> = {};
|
||||
Object.keys(sheets).forEach((sheetId) => {
|
||||
const sheet = sheets[sheetId];
|
||||
new ObjectMatrix(sheet.cellData).forValue((_r, _c, value) => {
|
||||
const s = value?.s;
|
||||
if (s && typeof s === 'string') {
|
||||
const style = workbookData.styles[s];
|
||||
if (style) {
|
||||
cacheStyle[s] = style;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
workbookData.styles = cacheStyle;
|
||||
return workbookData;
|
||||
};
|
||||
|
||||
export function useSnapshot() {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const resourceLoaderService = useDependency(IResourceLoaderService);
|
||||
const localFileService = useDependency(ILocalFileService);
|
||||
const recordController = useDependency(RecordController);
|
||||
|
||||
const onSelect = async (value: string) => {
|
||||
const preName = new Date().toLocaleString();
|
||||
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
if (!workbook) {
|
||||
const doc = univerInstanceService.getCurrentUnitOfType<DocumentDataModel>(UniverInstanceType.UNIVER_DOC)!;
|
||||
const snapshot = resourceLoaderService.saveUnit(doc.getUnitId());
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const gitHash = process.env.GIT_COMMIT_HASH;
|
||||
const gitBranch = process.env.GIT_REF_NAME;
|
||||
const buildTime = process.env.BUILD_TIME;
|
||||
(snapshot as any).__env__ = { gitHash, gitBranch, buildTime };
|
||||
}
|
||||
const text = JSON.stringify(snapshot, null, 2);
|
||||
localFileService.downloadFile(new Blob([text]), `${preName} snapshot.json`);
|
||||
return true;
|
||||
}
|
||||
|
||||
const worksheet = workbook.getActiveSheet();
|
||||
if (!worksheet) {
|
||||
return false;
|
||||
}
|
||||
const snapshot = resourceLoaderService.saveUnit<ReturnType<typeof workbook.getSnapshot>>(workbook.getUnitId())!;
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const gitHash = process.env.GIT_COMMIT_HASH;
|
||||
const gitBranch = process.env.GIT_REF_NAME;
|
||||
const buildTime = process.env.BUILD_TIME;
|
||||
(snapshot as any).__env__ = { gitHash, gitBranch, buildTime };
|
||||
}
|
||||
|
||||
if (value === 'sheet') {
|
||||
const sheetId = worksheet.getSheetId();
|
||||
const sheet = snapshot.sheets[sheetId];
|
||||
snapshot.sheets = { [sheetId]: sheet };
|
||||
snapshot.sheetOrder = [sheetId];
|
||||
const text = JSON.stringify(filterStyle(snapshot), null, 2);
|
||||
localFileService.downloadFile(new Blob([text]), `${preName} snapshot.json`);
|
||||
} else if (value === 'workbook') {
|
||||
const text = JSON.stringify(filterStyle(snapshot), null, 2);
|
||||
localFileService.downloadFile(new Blob([text]), `${preName} snapshot.json`);
|
||||
} else if (value === 'record') {
|
||||
let endCommands = () => [];
|
||||
|
||||
recordController.record().subscribe((v) => {
|
||||
if (v.type === 'start') {
|
||||
endCommands = recordController.startSaveCommands() as any;
|
||||
}
|
||||
if (v.type === 'finish') {
|
||||
const commands = endCommands();
|
||||
localFileService.downloadFile(v.data, `${preName} video.webm`);
|
||||
localFileService.downloadFile(new Blob([JSON.stringify(commands, null, 2)]), `${preName} commands.json`);
|
||||
}
|
||||
});
|
||||
} else if (value === 'load') {
|
||||
const snapshotFile = await localFileService.openFile({ multiple: false, accept: '.json' });
|
||||
if (snapshotFile.length !== 1) return false;
|
||||
|
||||
const text = await snapshotFile[0].text();
|
||||
univerInstanceService.createUnit<IWorkbookData, Workbook>(UniverInstanceType.UNIVER_SHEET, JSON.parse(text));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '📷 Snapshot',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
77
common/debugger/src/views/use-theme.ts
Normal file
77
common/debugger/src/views/use-theme.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { ThemeService } from '@univerjs/core';
|
||||
import {
|
||||
blueTheme,
|
||||
darkBlueTheme,
|
||||
greenTheme,
|
||||
orangeTheme,
|
||||
purpleTheme,
|
||||
redTheme,
|
||||
yellowTheme,
|
||||
} from '@univerjs/themes';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const themes = [
|
||||
{
|
||||
label: '🟢',
|
||||
value: greenTheme,
|
||||
},
|
||||
{
|
||||
label: '🔵',
|
||||
value: blueTheme,
|
||||
},
|
||||
{
|
||||
label: '🔵🌑',
|
||||
value: darkBlueTheme,
|
||||
},
|
||||
{
|
||||
label: '🟠',
|
||||
value: orangeTheme,
|
||||
},
|
||||
{
|
||||
label: '🔴',
|
||||
value: redTheme,
|
||||
},
|
||||
{
|
||||
label: '🟣',
|
||||
value: purpleTheme,
|
||||
},
|
||||
{
|
||||
label: '🟡',
|
||||
value: yellowTheme,
|
||||
},
|
||||
];
|
||||
|
||||
export function useTheme() {
|
||||
const themeService = useDependency(ThemeService);
|
||||
|
||||
useEffect(() => {
|
||||
const themeKey = localStorage.getItem('local.theme');
|
||||
|
||||
const theme = themes.find((theme) => theme.label === themeKey);
|
||||
|
||||
if (theme) {
|
||||
themeService.setTheme(theme.value);
|
||||
}
|
||||
}, [themeService]);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
localStorage.setItem('local.theme', value);
|
||||
|
||||
const theme = themes.find((theme) => theme.label === value);
|
||||
|
||||
if (theme) {
|
||||
themeService.setTheme(theme.value);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '🎨 Themes',
|
||||
options: themes.map((theme) => ({
|
||||
type: 'item' as const,
|
||||
children: theme.label,
|
||||
onSelect: () => onSelect(theme.label),
|
||||
})),
|
||||
};
|
||||
}
|
||||
50
common/debugger/src/views/use-units.ts
Normal file
50
common/debugger/src/views/use-units.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import type { Workbook } from '@univerjs/core';
|
||||
import { IUniverInstanceService, UniverInstanceType } from '@univerjs/core';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
const defaultMenu = [
|
||||
{
|
||||
label: 'Create another sheet',
|
||||
value: 'create',
|
||||
},
|
||||
];
|
||||
|
||||
export function useUnits() {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const [menu, setMenu] = useState<{ label: string; value: string }[]>([...defaultMenu]);
|
||||
const unitAdded = useObservable(univerInstanceService.unitAdded$);
|
||||
const unitDisposed = useObservable(univerInstanceService.unitDisposed$);
|
||||
|
||||
useEffect(() => {
|
||||
const sheets = univerInstanceService.getAllUnitsForType<Workbook>(UniverInstanceType.UNIVER_SHEET);
|
||||
const options = sheets.map((sheet) => ({
|
||||
label: sheet.name || sheet.getUnitId(),
|
||||
value: sheet.getUnitId(),
|
||||
}));
|
||||
|
||||
setMenu([
|
||||
...defaultMenu,
|
||||
...(options as any[]),
|
||||
]);
|
||||
}, [unitAdded, unitDisposed]);
|
||||
|
||||
const onSelect = (value: string) => {
|
||||
if (value === 'create') {
|
||||
univerInstanceService.createUnit(UniverInstanceType.UNIVER_SHEET, {});
|
||||
} else {
|
||||
if (!univerInstanceService.getUnit(value)) return false;
|
||||
univerInstanceService.setCurrentUnitForType(value);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '🪸 Units',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
54
common/debugger/src/views/use-user.ts
Normal file
54
common/debugger/src/views/use-user.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type { Workbook } from '@univerjs/core';
|
||||
import { createDefaultUser, IPermissionService, IUniverInstanceService, UniverInstanceType, UserManagerService } from '@univerjs/core';
|
||||
import { WorkbookManageCollaboratorPermission } from '@univerjs/sheets';
|
||||
import { useDependency } from '@univerjs/ui';
|
||||
|
||||
enum UnitRole {
|
||||
Reader = 0,
|
||||
Editor = 1,
|
||||
Owner = 2,
|
||||
UNRECOGNIZED = -1,
|
||||
}
|
||||
|
||||
const menu = [
|
||||
{
|
||||
label: 'Owner',
|
||||
value: UnitRole.Owner,
|
||||
},
|
||||
{
|
||||
label: 'Editor',
|
||||
value: UnitRole.Editor,
|
||||
},
|
||||
{
|
||||
label: 'Reader',
|
||||
value: UnitRole.Reader,
|
||||
},
|
||||
|
||||
];
|
||||
|
||||
export function useUser() {
|
||||
const univerInstanceService = useDependency(IUniverInstanceService);
|
||||
const userManagerService = useDependency(UserManagerService);
|
||||
const permissionService = useDependency(IPermissionService);
|
||||
|
||||
const onSelect = (value: UnitRole) => {
|
||||
userManagerService.setCurrentUser(createDefaultUser(value));
|
||||
const workbook = univerInstanceService.getCurrentUnitOfType<Workbook>(UniverInstanceType.UNIVER_SHEET)!;
|
||||
const unitId = workbook.getUnitId();
|
||||
if (value === UnitRole.Owner) {
|
||||
permissionService.updatePermissionPoint(new WorkbookManageCollaboratorPermission(unitId).id, true);
|
||||
} else {
|
||||
permissionService.updatePermissionPoint(new WorkbookManageCollaboratorPermission(unitId).id, false);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'subItem' as const,
|
||||
children: '👥 Change user\'s role',
|
||||
options: menu.map((item) => ({
|
||||
type: 'item' as const,
|
||||
children: item.label,
|
||||
onSelect: () => onSelect(item.value),
|
||||
})),
|
||||
};
|
||||
}
|
||||
22
common/debugger/src/views/use-watermark.ts
Normal file
22
common/debugger/src/views/use-watermark.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ISidebarService, useDependency } from '@univerjs/ui';
|
||||
import { WATERMARK_PANEL, WATERMARK_PANEL_FOOTER } from '../controllers/components.controller';
|
||||
|
||||
export function useWatermark() {
|
||||
const sidebarService = useDependency(ISidebarService);
|
||||
|
||||
const onSelect = () => {
|
||||
sidebarService.open({
|
||||
header: { title: 'Watermark' },
|
||||
children: { label: WATERMARK_PANEL },
|
||||
footer: { label: WATERMARK_PANEL_FOOTER },
|
||||
onClose: () => { },
|
||||
width: 330,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
type: 'item' as const,
|
||||
children: '🌊 Watermark Settings',
|
||||
onSelect,
|
||||
};
|
||||
}
|
||||
224
common/debugger/src/views/watermark/WatermarkImageSetting.tsx
Normal file
224
common/debugger/src/views/watermark/WatermarkImageSetting.tsx
Normal file
@@ -0,0 +1,224 @@
|
||||
import type { IImageWatermarkConfig } from '@univerjs/engine-render';
|
||||
import { Button, Checkbox, InputNumber } from '@univerjs/design';
|
||||
import { ILocalFileService, useDependency } from '@univerjs/ui';
|
||||
import { WATERMARK_IMAGE_ALLOW_IMAGE_LIST } from '@univerjs/watermark';
|
||||
|
||||
interface IWatermarkImageSettingProps {
|
||||
config?: IImageWatermarkConfig;
|
||||
onChange: (config: IImageWatermarkConfig) => void;
|
||||
}
|
||||
|
||||
export function WatermarkImageSetting({ config, onChange }: IWatermarkImageSettingProps) {
|
||||
const fileOpenService = useDependency(ILocalFileService);
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
const handleUpdateImageUrl = async () => {
|
||||
const files = await fileOpenService.openFile({
|
||||
multiple: false,
|
||||
accept: WATERMARK_IMAGE_ALLOW_IMAGE_LIST.map((image) => `.${image.replace('image/', '')}`).join(','),
|
||||
});
|
||||
|
||||
const fileLength = files.length;
|
||||
if (fileLength === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const file = files[0];
|
||||
|
||||
const reader = new FileReader();
|
||||
|
||||
reader.onload = function (event) {
|
||||
if (event.target?.result) {
|
||||
const base64String = event.target.result;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
onChange({ ...config, url: base64String as string, width: Math.max(20, img.width), height: Math.max(img.height, 20), originRatio: img.width / img.height });
|
||||
};
|
||||
|
||||
img.src = base64String as string;
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="univer-grid univer-gap-2">
|
||||
<div className="univer-text-gray-400">Image</div>
|
||||
|
||||
<div className="univer-mb-4 univer-grid univer-gap-1">
|
||||
<Button
|
||||
className="univer-mb-2"
|
||||
onClick={handleUpdateImageUrl}
|
||||
>
|
||||
{config.url ? 'Replace Image' : 'Upload Image'}
|
||||
</Button>
|
||||
|
||||
<div className="univer-flex univer-gap-2 univer-text-center">
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Opacity</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.opacity}
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.05}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, opacity: Number.parseFloat(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Keep Ratio</div>
|
||||
<Checkbox
|
||||
className="univer-justify-center univer-self-baseline"
|
||||
checked={config.maintainAspectRatio}
|
||||
onChange={(val) => {
|
||||
if (val === true) {
|
||||
onChange({ ...config, maintainAspectRatio: val as boolean, height: Math.round(config.width / config.originRatio) });
|
||||
} else {
|
||||
onChange({ ...config, maintainAspectRatio: val as boolean });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-2 univer-text-center">
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Width</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.width}
|
||||
min={20}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
const newWidth = Math.max(20, Number.parseInt(val.toString()));
|
||||
if (config.maintainAspectRatio) {
|
||||
onChange({ ...config, width: newWidth, height: Math.round(newWidth / config.originRatio) });
|
||||
} else {
|
||||
onChange({ ...config, width: newWidth });
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Height</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.height}
|
||||
min={20}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
const newHeight = Math.max(20, Number.parseInt(val.toString()));
|
||||
if (config.maintainAspectRatio) {
|
||||
onChange({ ...config, height: newHeight, width: Math.round(newHeight * config.originRatio) });
|
||||
} else {
|
||||
onChange({ ...config, height: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="univer-text-gray-400">Layout Settings</div>
|
||||
|
||||
<div className="univer-grid univer-gap-2 univer-text-center">
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Rotate</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.rotate}
|
||||
max={360}
|
||||
min={-360}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, rotate: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Repeat</div>
|
||||
<Checkbox
|
||||
className="univer-justify-center univer-self-baseline"
|
||||
checked={config.repeat}
|
||||
onChange={(val) => onChange({ ...config, repeat: val as boolean })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Horizontal Spacing</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.spacingX}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, spacingX: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Vertical Spacing</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.spacingY}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, spacingY: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Horizontal Start Position</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.x}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, x: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Vertical Start Position</div>
|
||||
<InputNumber
|
||||
className="univer-box-border univer-h-7"
|
||||
value={config.y}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, y: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
65
common/debugger/src/views/watermark/WatermarkPanel.tsx
Normal file
65
common/debugger/src/views/watermark/WatermarkPanel.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { IWatermarkConfig, IWatermarkConfigWithType } from '@univerjs/engine-render';
|
||||
import { ILocalStorageService } from '@univerjs/core';
|
||||
import { Select } from '@univerjs/design';
|
||||
import { IWatermarkTypeEnum, UNIVER_WATERMARK_STORAGE_KEY } from '@univerjs/engine-render';
|
||||
import { useDependency, useObservable } from '@univerjs/ui';
|
||||
import { WatermarkImageBaseConfig, WatermarkService, WatermarkTextBaseConfig } from '@univerjs/watermark';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { WatermarkImageSetting } from './WatermarkImageSetting';
|
||||
import { WatermarkTextSetting } from './WatermarkTextSetting';
|
||||
|
||||
export function WatermarkPanel() {
|
||||
const [watermarkType, setWatermarkType] = useState<IWatermarkTypeEnum>(IWatermarkTypeEnum.Text);
|
||||
const [config, setConfig] = useState<IWatermarkConfig>();
|
||||
const watermarkService = useDependency(WatermarkService);
|
||||
const localStorageService = useDependency(ILocalStorageService);
|
||||
const _refresh = useObservable(watermarkService.refresh$);
|
||||
|
||||
function handleConfigChange(config: IWatermarkConfig, type?: IWatermarkTypeEnum) {
|
||||
setConfig(config);
|
||||
watermarkService.updateWatermarkConfig({ type: type ?? watermarkType, config });
|
||||
}
|
||||
|
||||
const getWatermarkConfig = useCallback(async () => {
|
||||
const watermarkConfig = await localStorageService.getItem<IWatermarkConfigWithType>(UNIVER_WATERMARK_STORAGE_KEY);
|
||||
if (watermarkConfig) {
|
||||
setWatermarkType(watermarkConfig.type);
|
||||
setConfig(watermarkConfig.config);
|
||||
} else {
|
||||
setConfig({ text: WatermarkTextBaseConfig });
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
getWatermarkConfig();
|
||||
}, [_refresh, getWatermarkConfig]);
|
||||
|
||||
return (
|
||||
<div className="univer-grid univer-gap-3 univer-text-sm">
|
||||
{/* Watermark type */}
|
||||
<div className="univer-grid univer-gap-2">
|
||||
<div className="univer-text-gray-400">Type</div>
|
||||
<Select
|
||||
value={watermarkType}
|
||||
options={[
|
||||
{ label: 'Text', value: IWatermarkTypeEnum.Text },
|
||||
{ label: 'Image', value: IWatermarkTypeEnum.Image },
|
||||
]}
|
||||
onChange={(v) => {
|
||||
setWatermarkType(v as IWatermarkTypeEnum);
|
||||
if (v === IWatermarkTypeEnum.Text) {
|
||||
handleConfigChange({ text: WatermarkTextBaseConfig }, IWatermarkTypeEnum.Text);
|
||||
} else if (v === IWatermarkTypeEnum.Image) {
|
||||
handleConfigChange({ image: WatermarkImageBaseConfig }, IWatermarkTypeEnum.Image);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-2">
|
||||
{watermarkType === IWatermarkTypeEnum.Text && <WatermarkTextSetting config={config?.text} onChange={(v) => handleConfigChange({ text: v })} />}
|
||||
{watermarkType === IWatermarkTypeEnum.Image && <WatermarkImageSetting config={config?.image} onChange={(v) => handleConfigChange({ image: v })} />}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
57
common/debugger/src/views/watermark/WatermarkPanelFooter.tsx
Normal file
57
common/debugger/src/views/watermark/WatermarkPanelFooter.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Button } from '@univerjs/design';
|
||||
import { IWatermarkTypeEnum } from '@univerjs/engine-render';
|
||||
import { IClipboardInterfaceService, ISidebarService, useDependency } from '@univerjs/ui';
|
||||
import { WatermarkService, WatermarkTextBaseConfig } from '@univerjs/watermark';
|
||||
|
||||
export function WatermarkPanelFooter() {
|
||||
const sidebarService = useDependency(ISidebarService);
|
||||
const watermarkService = useDependency(WatermarkService);
|
||||
const clipboardService = useDependency(IClipboardInterfaceService);
|
||||
|
||||
return (
|
||||
<div className="univer-flex univer-items-center univer-justify-between">
|
||||
<a
|
||||
className="univer-text-sm univer-text-primary-600 univer-underline"
|
||||
onClick={() => {
|
||||
watermarkService.updateWatermarkConfig({
|
||||
type: IWatermarkTypeEnum.Text,
|
||||
config: { text: WatermarkTextBaseConfig },
|
||||
});
|
||||
watermarkService.refresh();
|
||||
}}
|
||||
>
|
||||
Cancel Watermark
|
||||
</a>
|
||||
|
||||
<div className="univer-flex univer-items-center univer-gap-2">
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const watermarkConfig = await watermarkService.getWatermarkConfig();
|
||||
let config;
|
||||
if (watermarkConfig?.type === IWatermarkTypeEnum.Text) {
|
||||
config = watermarkConfig.config.text;
|
||||
} else if (watermarkConfig?.type === IWatermarkTypeEnum.Image) {
|
||||
config = watermarkConfig.config.image;
|
||||
}
|
||||
clipboardService.writeText(JSON.stringify(config));
|
||||
}}
|
||||
>
|
||||
Copy Config
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const watermarkConfig = await watermarkService.getWatermarkConfig();
|
||||
if (watermarkConfig?.type === IWatermarkTypeEnum.Text && !watermarkConfig.config.text?.content) {
|
||||
watermarkService.deleteWatermarkConfig();
|
||||
} else if (watermarkConfig?.type === IWatermarkTypeEnum.Image && !watermarkConfig.config.image?.url) {
|
||||
watermarkService.deleteWatermarkConfig();
|
||||
}
|
||||
sidebarService.close();
|
||||
}}
|
||||
>
|
||||
Close Panel
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
205
common/debugger/src/views/watermark/WatermarkTextSetting.tsx
Normal file
205
common/debugger/src/views/watermark/WatermarkTextSetting.tsx
Normal file
@@ -0,0 +1,205 @@
|
||||
import type { ITextWatermarkConfig } from '@univerjs/engine-render';
|
||||
import { Checkbox, clsx, ColorPicker, Dropdown, Input, InputNumber, Select } from '@univerjs/design';
|
||||
import { BoldIcon, FontColorDoubleIcon, ItalicIcon } from '@univerjs/icons';
|
||||
|
||||
interface IWatermarkTextSettingProps {
|
||||
config?: ITextWatermarkConfig;
|
||||
onChange: (config: ITextWatermarkConfig) => void;
|
||||
}
|
||||
|
||||
export function WatermarkTextSetting(props: IWatermarkTextSettingProps) {
|
||||
const { config, onChange } = props;
|
||||
|
||||
if (!config) return null;
|
||||
|
||||
return (
|
||||
<div className="univer-grid univer-gap-2">
|
||||
<div className="univer-text-gray-400">Style Settings</div>
|
||||
|
||||
<div className="univer-mb-4 univer-grid univer-gap-1">
|
||||
<div>Content</div>
|
||||
<Input
|
||||
value={config.content}
|
||||
onChange={(val) => onChange({ ...config, content: val })}
|
||||
placeholder="Enter text"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-4">
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Font Size</div>
|
||||
<InputNumber
|
||||
value={config.fontSize}
|
||||
max={72}
|
||||
min={12}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, fontSize: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Direction</div>
|
||||
<Select
|
||||
value={config.direction}
|
||||
options={[
|
||||
{ label: 'Left to Right', value: 'ltr' },
|
||||
{ label: 'Right to Left', value: 'rtl' },
|
||||
]}
|
||||
onChange={(v) => onChange({ ...config, direction: v as 'ltr' | 'rtl' })}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Opacity</div>
|
||||
<InputNumber
|
||||
max={1}
|
||||
min={0}
|
||||
step={0.05}
|
||||
value={config.opacity}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, opacity: Number.parseFloat(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={`
|
||||
univer-flex univer-justify-around univer-gap-4
|
||||
[&_a]:univer-flex [&_a]:univer-size-6 [&_a]:univer-items-center [&_a]:univer-justify-center
|
||||
[&_a]:univer-rounded
|
||||
`}
|
||||
>
|
||||
<Dropdown
|
||||
overlay={(
|
||||
<div className="univer-rounded-lg univer-p-4">
|
||||
<ColorPicker value={config.color} onChange={(val) => onChange({ ...config, color: val })} />
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
<a
|
||||
className={`
|
||||
hover:univer-bg-gray-100
|
||||
dark:hover:!univer-bg-gray-700
|
||||
`}
|
||||
>
|
||||
<FontColorDoubleIcon extend={{ colorChannel1: config.color ?? '#2c53f1' }} />
|
||||
</a>
|
||||
</Dropdown>
|
||||
<a
|
||||
className={clsx(`
|
||||
hover:univer-bg-gray-100
|
||||
dark:hover:!univer-bg-gray-700
|
||||
`, {
|
||||
'univer-bg-gray-200 dark:!univer-bg-gray-600': config.bold,
|
||||
})}
|
||||
onClick={() => { onChange({ ...config, bold: !config.bold }); }}
|
||||
>
|
||||
<BoldIcon />
|
||||
</a>
|
||||
<a
|
||||
className={clsx(`
|
||||
hover:univer-bg-gray-100
|
||||
dark:hover:!univer-bg-gray-700
|
||||
`, {
|
||||
'univer-bg-gray-200 dark:!univer-bg-gray-600': config.italic,
|
||||
})}
|
||||
onClick={() => { onChange({ ...config, italic: !config.italic }); }}
|
||||
>
|
||||
<ItalicIcon />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Layout */}
|
||||
<div className="univer-text-gray-400">Layout Settings</div>
|
||||
|
||||
<div className="univer-grid univer-gap-2 univer-text-center">
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Rotate</div>
|
||||
<InputNumber
|
||||
value={config.rotate}
|
||||
max={360}
|
||||
min={-360}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, rotate: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="univer-grid univer-flex-1 univer-gap-1">
|
||||
<div>Repeat</div>
|
||||
<Checkbox
|
||||
className="univer-justify-center univer-self-baseline"
|
||||
checked={config.repeat}
|
||||
onChange={(val) => onChange({ ...config, repeat: val as boolean })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Horizontal Spacing</div>
|
||||
<InputNumber
|
||||
value={config.spacingX}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, spacingX: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Vertical Spacing</div>
|
||||
<InputNumber
|
||||
value={config.spacingY}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, spacingY: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="univer-flex univer-gap-2">
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Horizontal Start Position</div>
|
||||
<InputNumber
|
||||
value={config.x}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, x: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="univer-grid univer-gap-1">
|
||||
<div>Vertical Start Position</div>
|
||||
<InputNumber
|
||||
value={config.y}
|
||||
min={0}
|
||||
onChange={(val) => {
|
||||
if (val != null) {
|
||||
onChange({ ...config, y: Number.parseInt(val.toString()) });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
11
common/debugger/tailwind.config.ts
Normal file
11
common/debugger/tailwind.config.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
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
common/debugger/tsconfig.json
Normal file
8
common/debugger/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "@univerjs-infra/shared/tsconfigs/base",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib/types"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
Reference in New Issue
Block a user