diff --git a/.eslintrc.js b/.eslintrc.js index 72bc8eacff..6f62557860 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -93,19 +93,19 @@ module.exports = { }, ], // eslint-plugin-unused-imports - '@typescript-eslint/no-unused-vars': 'off', - 'unused-imports/no-unused-imports': 'error', - 'prefer-regex-literals': 'off', + '@typescript-eslint/consistent-type-assertions': 'off', '@typescript-eslint/default-param-last': 'off', - 'grouped-accessor-pairs': 'off', '@typescript-eslint/member-ordering': 'error', '@typescript-eslint/no-explicit-any': 'warn', - 'no-unsafe-optional-chaining': 'off', - '@typescript-eslint/consistent-type-assertions': 'off', '@typescript-eslint/no-redeclare': 'off', // dependency interface and dependency token share the same name + '@typescript-eslint/no-unused-vars': 'off', + 'grouped-accessor-pairs': 'off', 'no-magic-numbers': ['warn', { ignore: [0, 1, -1, 2] }], - 'simple-import-sort/imports': 'error', + 'no-unsafe-optional-chaining': 'off', + 'prefer-regex-literals': 'off', 'simple-import-sort/exports': 'error', + 'simple-import-sort/imports': 'error', + 'unused-imports/no-unused-imports': 'error', }, // https://www.npmjs.com/package/eslint-import-resolver-typescript settings: { diff --git a/examples/univer-doc-ts/esbuild.config.js b/examples/univer-doc-ts/esbuild.config.js index d73e8ff8e6..d73021804d 100644 --- a/examples/univer-doc-ts/esbuild.config.js +++ b/examples/univer-doc-ts/esbuild.config.js @@ -5,7 +5,7 @@ const args = require('minimist')(process.argv.slice(2)); bundle: true, color: true, loader: { '.svg': 'file' }, - sourcemap: false, + sourcemap: true, plugins: [ require('esbuild-plugin-clean').clean({ patterns: ['./local'], diff --git a/examples/univer-doc-ts/index.html b/examples/univer-doc-ts/index.html deleted file mode 100644 index a14c559a5e..0000000000 --- a/examples/univer-doc-ts/index.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - UniverDoc - - - - -
-
- - - diff --git a/examples/univer-doc-ts/public/index.html b/examples/univer-doc-ts/public/index.html index 251fb2bb00..d05c4c6bdb 100644 --- a/examples/univer-doc-ts/public/index.html +++ b/examples/univer-doc-ts/public/index.html @@ -1,33 +1,30 @@ - - - - - - - UniverDoc - - - - - - - - - -
-
- - - - - \ No newline at end of file + + + + + + UniverDoc + + + + + + + + +
+
+ + + + diff --git a/examples/univer-doc-ts/src/main.tsx b/examples/univer-doc-ts/src/main.tsx index c6e18d40de..463b2fc930 100644 --- a/examples/univer-doc-ts/src/main.tsx +++ b/examples/univer-doc-ts/src/main.tsx @@ -1,17 +1,25 @@ import { DocPlugin } from '@univerjs/base-docs'; import { RenderEngine } from '@univerjs/base-render'; +import { UIPlugin } from '@univerjs/base-ui'; import { DEFAULT_DOCUMENT_DATA_EN } from '@univerjs/common-plugin-data'; import { LocaleType, Univer } from '@univerjs/core'; import { DocUIPlugin } from '@univerjs/ui-plugin-docs'; +// univer const univer = new Univer({ locale: LocaleType.EN, }); -univer.createUniverDoc(DEFAULT_DOCUMENT_DATA_EN); - +// core plugins univer.registerPlugin(RenderEngine); -univer.registerPlugin(DocPlugin); +univer.registerPlugin(UIPlugin, { + container: 'univer-container', + header: true, + toolbar: true, +}); +univer.registerPlugin(DocPlugin, { + standalone: true, +}); univer.registerPlugin(DocUIPlugin, { container: 'univerdoc', layout: { @@ -20,3 +28,5 @@ univer.registerPlugin(DocUIPlugin, { }, }, }); + +univer.createUniverDoc(DEFAULT_DOCUMENT_DATA_EN); diff --git a/examples/univer-sheet-ts/esbuild.config.js b/examples/univer-sheet-ts/esbuild.config.js index 9e0ce376ea..be3a16d705 100644 --- a/examples/univer-sheet-ts/esbuild.config.js +++ b/examples/univer-sheet-ts/esbuild.config.js @@ -5,7 +5,7 @@ const args = require('minimist')(process.argv.slice(2)); bundle: true, color: true, loader: { '.svg': 'file' }, - sourcemap: false, + sourcemap: true, plugins: [ require('esbuild-plugin-clean').clean({ patterns: ['./local'], diff --git a/examples/univer-sheet-ts/index.html b/examples/univer-sheet-ts/index.html deleted file mode 100644 index e471eecb52..0000000000 --- a/examples/univer-sheet-ts/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - UniverSheet - - - - -
-
- - - - \ No newline at end of file diff --git a/examples/univer-sheet-ts/package.json b/examples/univer-sheet-ts/package.json index a47f723db2..08c475e67e 100644 --- a/examples/univer-sheet-ts/package.json +++ b/examples/univer-sheet-ts/package.json @@ -29,6 +29,7 @@ "ts-node": "^10.9.1" }, "dependencies": { + "@univerjs/base-docs": "workspace:^0.0.1", "@univerjs/base-render": "workspace:^0.0.1", "@univerjs/base-sheets": "workspace:^0.0.1", "@univerjs/base-ui": "workspace:^0.0.1", @@ -40,8 +41,9 @@ "@univerjs/sheets-plugin-import-xlsx": "workspace:^0.0.1", "@univerjs/sheets-plugin-numfmt": "workspace:^0.0.1", "@univerjs/sheets-plugin-operation": "workspace:^0.0.1", + "@univerjs/ui-plugin-docs": "workspace:^0.0.1", "@univerjs/ui-plugin-sheets": "workspace:^0.0.1", - "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react": "^18.2.0" } } diff --git a/examples/univer-sheet-ts/public/index.html b/examples/univer-sheet-ts/public/index.html index c804d874ee..8d7a44503e 100644 --- a/examples/univer-sheet-ts/public/index.html +++ b/examples/univer-sheet-ts/public/index.html @@ -1,35 +1,31 @@ + + + + Univer Sheet - - - - Univer Sheet + + - - + + + +
+
- - - - -
- -
- - - - - \ No newline at end of file + + + diff --git a/examples/univer-sheet-ts/src/main.tsx b/examples/univer-sheet-ts/src/main.tsx index f5eb58d5bb..0121f81af3 100644 --- a/examples/univer-sheet-ts/src/main.tsx +++ b/examples/univer-sheet-ts/src/main.tsx @@ -1,13 +1,15 @@ +import { DocPlugin } from '@univerjs/base-docs'; import { RenderEngine } from '@univerjs/base-render'; import { SheetPlugin } from '@univerjs/base-sheets'; +import { UIPlugin } from '@univerjs/base-ui'; import { DEFAULT_WORKBOOK_DATA_DEMO } from '@univerjs/common-plugin-data'; import { LocaleType, Univer } from '@univerjs/core'; import { FindPlugin } from '@univerjs/sheets-plugin-find'; import { DEFAULT_FORMULA_DATA_DEMO, FormulaPlugin } from '@univerjs/sheets-plugin-formula'; -// import { OperationPlugin } from '@univerjs/sheets-plugin-operation'; import { ImagePlugin } from '@univerjs/sheets-plugin-image'; import { ImportXlsxPlugin } from '@univerjs/sheets-plugin-import-xlsx'; import { NumfmtPlugin } from '@univerjs/sheets-plugin-numfmt'; +import { DocUIPlugin } from '@univerjs/ui-plugin-docs'; import { SheetUIPlugin } from '@univerjs/ui-plugin-sheets'; // univer @@ -15,32 +17,29 @@ const univer = new Univer({ locale: LocaleType.EN, }); -// create univer sheet instance -univer.createUniverSheet(DEFAULT_WORKBOOK_DATA_DEMO); - -// register plugins -univer.registerPlugin(RenderEngine); -univer.registerPlugin(SheetPlugin); -univer.registerPlugin(SheetUIPlugin, { - container: 'universheet', - layout: { - sheetContainerConfig: { - infoBar: true, - formulaBar: true, - toolbar: true, - sheetBar: true, - countBar: true, - rightMenu: true, - }, - }, +// core plugins +univer.registerPlugin(DocPlugin, { + standalone: false, }); +univer.registerPlugin(DocUIPlugin); +univer.registerPlugin(RenderEngine); +univer.registerPlugin(UIPlugin, { + container: 'univer-container', + header: true, + toolbar: true, +}); +univer.registerPlugin(SheetPlugin); +univer.registerPlugin(SheetUIPlugin); +// feature plugins univer.registerPlugin(NumfmtPlugin); univer.registerPlugin(FindPlugin); univer.registerPlugin(FormulaPlugin, DEFAULT_FORMULA_DATA_DEMO); univer.registerPlugin(ImportXlsxPlugin); univer.registerPlugin(ImagePlugin); -// univer.registerPlugin(OperationPlugin); + +// create univer sheet instance +univer.createUniverSheet(DEFAULT_WORKBOOK_DATA_DEMO); // use for console test declare global { diff --git a/packages/base-docs/package.json b/packages/base-docs/package.json index 95a3f40fd7..ce9bbceb19 100644 --- a/packages/base-docs/package.json +++ b/packages/base-docs/package.json @@ -55,6 +55,7 @@ "@wendellhu/redi": "^0.12.7", "css-vars-ponyfill": "^2.4.6", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "rxjs": "^7.8.1" } -} \ No newline at end of file +} diff --git a/packages/base-docs/src/Controller/InputController.ts b/packages/base-docs/src/Controller/InputController.ts index 1c20603d66..4bab8838ac 100644 --- a/packages/base-docs/src/Controller/InputController.ts +++ b/packages/base-docs/src/Controller/InputController.ts @@ -13,31 +13,76 @@ import { isSameLine, TextSelection, } from '@univerjs/base-render'; -import { DataStreamTreeTokenType, Direction, DocumentModel, ICommandService, ICurrentUniverService, IParagraph, Nullable, Observable } from '@univerjs/core'; -import { Inject } from '@wendellhu/redi'; +import { + DataStreamTreeTokenType, + Direction, + Disposable, + DisposableCollection, + DocumentModel, + fromObservable, + ICommandService, + ICurrentUniverService, + IParagraph, + Nullable, + Observable, + toDisposable, +} from '@univerjs/core'; +import { IDisposable, Inject } from '@wendellhu/redi'; import { DeleteCommand, IMEInputCommand, InsertCommand, UpdateCommand } from '../commands/commands/core-editing.command'; -import { CanvasView } from '../View/Render/CanvasView'; +import { DocsViewManagerService } from '../services/docs-view-manager/docs-view-manager.service'; import { DocsView } from '../View/Render/Views'; -export class InputController { +/** + * InputController handles inputting events on the currently focused DocsView and apply edits to corresponding UniverDoc. + */ +export class InputController extends Disposable { private _previousIMEContent: string = ''; private _previousIMEStart: number; private _currentNodePosition: Nullable; + private _editorDisposables: DisposableCollection | null; + + private _currentDocsView: DocsView | null; + constructor( - @Inject(CanvasView) private readonly _canvasView: CanvasView, + @Inject(DocsViewManagerService) private readonly _docsViewManager: DocsViewManagerService, @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, @ICommandService private readonly _commandService: ICommandService ) { - this._initialize(); + super(); + + this.disposeWithMe( + fromObservable( + this._docsViewManager.current$.subscribe((docsView) => { + this._currentDocsView?.getDocs().disableEditor(); + this._editorDisposables?.dispose(); + this._editorDisposables = null; + + if (docsView) { + this._currentDocsView = docsView; + this._editorDisposables = this._initialize(docsView); + } + }) + ) + ); + } + + override dispose(): void { + super.dispose(); + + this._currentDocsView = null; + this._editorDisposables?.dispose(); } - // eslint-disable-next-line @typescript-eslint/member-ordering moveCursor(_docModel: DocumentModel, direction: Direction) { - const documents = (this._canvasView.getDocsView() as DocsView).getDocs(); + if (!this._currentDocsView) { + return; + } + + const documents = this._currentDocsView.getDocs(); const activeSelection = documents.getActiveSelection(); if (!activeSelection) { return false; @@ -115,7 +160,11 @@ export class InputController { // eslint-disable-next-line max-lines-per-function deleteLeft() { - const document = (this._canvasView.getDocsView() as DocsView).getDocs(); + if (!this._currentDocsView) { + return; + } + + const document = this._currentDocsView.getDocs(); const activeSelection = document.getActiveSelection(); if (!activeSelection) { return; @@ -232,7 +281,11 @@ export class InputController { } breakLine() { - const document = (this._canvasView.getDocsView() as DocsView).getDocs(); + if (!this._currentDocsView) { + return; + } + + const document = this._currentDocsView.getDocs(); const activeSelection = document.getActiveSelection(); if (!activeSelection) { return; @@ -272,8 +325,8 @@ export class InputController { this._adjustSelection(document as Documents, selectionRemain, span, true); } - private _initialInput(onInputObservable: Observable, docsModel: DocumentModel) { - onInputObservable.add((config) => { + private _initialInput(onInputObservable: Observable, docsModel: DocumentModel): IDisposable { + const observer = onInputObservable.add((config) => { const { event, content = '', document, activeSelection, selectionList } = config; const e = event as InputEvent; @@ -319,6 +372,8 @@ export class InputController { this._adjustSelection(document as Documents, selectionRemain, span); }); + + return toDisposable(() => onInputObservable.remove(observer)); } private _initialComposition( @@ -326,8 +381,8 @@ export class InputController { onCompositionupdateObservable: Observable, onCompositionendObservable: Observable, docsModel: DocumentModel - ) { - onCompositionstartObservable.add((config) => { + ): IDisposable { + const startObserver = onCompositionstartObservable.add((config) => { const { activeSelection, selectionList } = config; const activeRange = activeSelection?.getRange(); @@ -346,7 +401,7 @@ export class InputController { this._previousIMEStart = cursor; }); - onCompositionupdateObservable.add(async (config) => { + const updateObserver = onCompositionupdateObservable.add(async (config) => { const { event, document, activeSelection } = config; let cursor = this._previousIMEStart; @@ -388,9 +443,15 @@ export class InputController { this._previousIMEContent = content; }); - onCompositionendObservable.add((config) => { + const endObserver = onCompositionendObservable.add((config) => { this._resetIME(); }); + + return toDisposable(() => { + onCompositionstartObservable.remove(startObserver); + onCompositionupdateObservable.remove(updateObserver); + onCompositionendObservable.remove(endObserver); + }); } private _getTopOrBottomPosition(document: Documents, span: Nullable, direction: boolean): Nullable { @@ -661,23 +722,26 @@ export class InputController { document.syncSelection(); } - private _initialize() { - const docsView = this._canvasView.getDocsView() as DocsView; + private _initialize(docsView: DocsView): DisposableCollection { + docsView.getDocs().enableEditor(); const events = docsView.getDocs().getEditorInputEvent(); - const docsModel = this._currentUniverService.getCurrentUniverDocInstance().getDocument(); + const disposableCollection = new DisposableCollection(); if (!events) { - return; + return disposableCollection; } + const { onInputObservable, onCompositionstartObservable, onCompositionupdateObservable, onCompositionendObservable, onSelectionStartObservable } = events; - this._initialInput(onInputObservable, docsModel); + disposableCollection.add(this._initialInput(onInputObservable, docsModel)); + disposableCollection.add(this._initialComposition(onCompositionstartObservable, onCompositionupdateObservable, onCompositionendObservable, docsModel)); - this._initialComposition(onCompositionstartObservable, onCompositionupdateObservable, onCompositionendObservable, docsModel); - - onSelectionStartObservable.add((nodePosition) => { + const observer = onSelectionStartObservable.add((nodePosition) => { this._currentNodePosition = nodePosition; }); + disposableCollection.add(toDisposable(() => onSelectionStartObservable.remove(observer))); + + return disposableCollection; } } diff --git a/packages/base-docs/src/DocPlugin.ts b/packages/base-docs/src/DocPlugin.ts index 90c587bc53..f1722d3e1b 100644 --- a/packages/base-docs/src/DocPlugin.ts +++ b/packages/base-docs/src/DocPlugin.ts @@ -1,21 +1,24 @@ import { Engine } from '@univerjs/base-render'; -import { DesktopPlatformService, DesktopShortcutService, IPlatformService, IShortcutService } from '@univerjs/base-ui'; -import { ICommandService, LocaleService, ObserverManager, Plugin, PLUGIN_NAMES, PluginType, UIObserver } from '@univerjs/core'; -import { Dependency, Inject, Injector } from '@wendellhu/redi'; +import { ContextService, DesktopPlatformService, IContextService, IPlatformService, IShortcutService } from '@univerjs/base-ui'; +import { ICommand, ICommandService, ICurrentUniverService, LocaleService, Plugin, PLUGIN_NAMES, PluginType } from '@univerjs/core'; +import { Dependency, Inject, Injector, SkipSelf } from '@wendellhu/redi'; import { DocPluginObserve, install } from './Basics/Observer'; -import { BreakLineCommand, DeleteCommand, DeleteLeftCommand, IMEInputCommand, InsertCommand, UpdateCommand } from './commands/commands/core-editing.command'; +import { BreakLineCommand, CoverCommand, DeleteCommand, DeleteLeftCommand, IMEInputCommand, InsertCommand, UpdateCommand } from './commands/commands/core-editing.command'; import { RichTextEditingMutation } from './commands/mutations/core-editing.mutation'; import { MoveCursorOperation } from './commands/operations/cursor.operation'; import { DocumentController } from './Controller/DocumentController'; import { en, zh } from './Locale'; +import { DocsViewManagerService } from './services/docs-view-manager/docs-view-manager.service'; import { BreakLineShortcut, DeleteLeftShortcut } from './shortcuts/core-editing.shortcut'; import { MoveCursorDownShortcut, MoveCursorLeftShortcut, MoveCursorRightShortcut, MoveCursorUpShortcut } from './shortcuts/cursor.shortcut'; import { CANVAS_VIEW_KEY } from './View/Render'; import { CanvasView } from './View/Render/CanvasView'; import { DocsView } from './View/Render/Views'; -export interface IDocPluginConfig {} +export interface IDocPluginConfig { + standalone?: boolean; +} const DEFAULT_DOCUMENT_PLUGIN_DATA = {}; @@ -32,14 +35,15 @@ export class DocPlugin extends Plugin { constructor( config: Partial = {}, - @Inject(ObserverManager) private readonly _globalObserverManager: ObserverManager, + @SkipSelf() @Inject(Injector) _univerInjector: Injector, @Inject(Injector) override _injector: Injector, - @Inject(LocaleService) private readonly _localeService: LocaleService + @Inject(LocaleService) private readonly _localeService: LocaleService, + @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService ) { super(PLUGIN_NAMES.DOCUMENT); this._config = Object.assign(DEFAULT_DOCUMENT_PLUGIN_DATA, config); - this._initializeDependencies(_injector); + this._initializeDependencies(_injector, _univerInjector); this.initializeCommands(); } @@ -51,11 +55,28 @@ export class DocPlugin extends Plugin { install(this); - this.listenEventManager(); + if (this._config.standalone) { + this.initCanvasView(); + } + + this._initController(); + this._markDocAsFocused(); } initializeCommands(): void { - [MoveCursorOperation, DeleteLeftCommand, BreakLineCommand, InsertCommand, DeleteCommand, UpdateCommand, IMEInputCommand, RichTextEditingMutation].forEach((command) => { + ( + [ + MoveCursorOperation, + DeleteLeftCommand, + BreakLineCommand, + InsertCommand, + DeleteCommand, + UpdateCommand, + IMEInputCommand, + RichTextEditingMutation, + CoverCommand, + ] as ICommand[] + ).forEach((command) => { this._injector.get(ICommandService).registerCommand(command); }); @@ -64,12 +85,7 @@ export class DocPlugin extends Plugin { }); } - initializeAfterUI() { - this.initCanvasView(); - this.initController(); - } - - initController() { + _initController() { this._documentController = new DocumentController(this._injector); } @@ -77,14 +93,6 @@ export class DocPlugin extends Plugin { this._canvasView = this._injector.get(CanvasView); } - listenEventManager() { - // FIXME@wzhudev: this looks strange to be. It should not rely on the event created by a upper layer plugin. - // Instead, upper layer plugin should call it. - this._getCoreObserver('onUIDidMountObservable').add(() => { - this.initializeAfterUI(); - }); - } - getConfig() { return this._config; } @@ -151,16 +159,23 @@ export class DocPlugin extends Plugin { override onDestroy(): void {} - /** @deprecated This will be removed. Modules should inject `ObserverManager` instead of getting it from plugin. */ - protected _getCoreObserver(type: string) { - return this._globalObserverManager.requiredObserver>(type, 'core'); + private _initializeDependencies(docInjector: Injector, univerInjector: Injector) { + ( + [ + [CanvasView, { useFactory: () => docInjector.createInstance(CanvasView, this._config.standalone ?? true) }], // FIXME: CanvasView shouldn't be a dependency of DocPlugin. Because it maybe created dynamically. + [IPlatformService, { useClass: DesktopPlatformService }], + [IContextService, { useClass: ContextService }], + ] as Dependency[] + ).forEach((d) => docInjector.add(d)); + + // add docs view manager to univer-level injector + univerInjector.add([DocsViewManagerService]); } - private _initializeDependencies(docInjector: Injector) { - const dependencies: Dependency[] = [[CanvasView], [IShortcutService, { useClass: DesktopShortcutService }], [IPlatformService, { useClass: DesktopPlatformService }]]; - - dependencies.forEach((d) => { - docInjector.add(d); - }); + private _markDocAsFocused() { + if (this._config.standalone) { + const c = this._currentUniverService.getCurrentUniverDocInstance(); + this._currentUniverService.focusUniverInstance(c.getUnitId()); + } } } diff --git a/packages/base-docs/src/View/Render/BaseView.ts b/packages/base-docs/src/View/Render/BaseView.ts index 1b0a8ada4c..7e11cdc79a 100644 --- a/packages/base-docs/src/View/Render/BaseView.ts +++ b/packages/base-docs/src/View/Render/BaseView.ts @@ -28,7 +28,7 @@ export class BaseView { } export enum CANVAS_VIEW_KEY { - MAIN_SCENE = 'mainScene', + MAIN_SCENE = 'docMainScene', DOCS_VIEW = 'docsView', } diff --git a/packages/base-docs/src/View/Render/CanvasView.ts b/packages/base-docs/src/View/Render/CanvasView.ts index edd0dbb7d4..71dd547921 100644 --- a/packages/base-docs/src/View/Render/CanvasView.ts +++ b/packages/base-docs/src/View/Render/CanvasView.ts @@ -11,7 +11,13 @@ export class CanvasView { private _views: BaseView[] = []; - constructor(@IRenderingEngine private readonly _engine: Engine, @Inject(Injector) private readonly _injector: Injector) { + constructor( + // TODO@wzhudev: CanvasView should not be a singleton + /** @deprecated This a temporary solution. CanvasView should not be a singleton. */ + private standalone = true, + @IRenderingEngine private readonly _engine: Engine, + @Inject(Injector) private readonly _injector: Injector + ) { this._initialize(); } @@ -30,12 +36,11 @@ export class CanvasView { private _initialize() { const engine = this._engine; - const scene = new Scene(CANVAS_VIEW_KEY.MAIN_SCENE, engine, { - width: 1200, + const scene = (this._scene = new Scene(CANVAS_VIEW_KEY.MAIN_SCENE, engine, { + width: 1024, height: 2000, - }); + })); - this._scene = scene; const viewMain = new Viewport(CANVAS_VIEW_KEY.DOCS_VIEW, scene, { left: 0, top: 0, @@ -76,13 +81,15 @@ export class CanvasView { this._viewLoader(scene); - engine.runRenderLoop(() => { - scene.render(); - const app = document.getElementById('app'); - if (app) { - app.innerText = `fps:${Math.round(engine.getFps()).toString()}`; - } - }); + if (this.standalone) { + engine.runRenderLoop(() => { + scene.render(); + const app = document.getElementById('app'); + if (app) { + app.innerText = `fps:${Math.round(engine.getFps()).toString()}`; + } + }); + } } private _viewLoader(scene: Scene) { diff --git a/packages/base-docs/src/View/Render/Views/DocsView.ts b/packages/base-docs/src/View/Render/Views/DocsView.ts index 20eeb19943..0dec7c3c34 100644 --- a/packages/base-docs/src/View/Render/Views/DocsView.ts +++ b/packages/base-docs/src/View/Render/Views/DocsView.ts @@ -2,6 +2,7 @@ import { Documents, DocumentSkeleton, IDocumentSkeletonDrawing, Picture, Scene } import { DocumentModel, ICurrentUniverService, LocaleService } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; +import { DocsViewManagerService } from '../../../services/docs-view-manager/docs-view-manager.service'; import { BaseView, CANVAS_VIEW_KEY, CanvasViewRegistry } from '../BaseView'; export enum DOCS_VIEW_KEY { @@ -17,8 +18,30 @@ export class DocsView extends BaseView { private _documents: Documents; - constructor(@ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, @Inject(LocaleService) private readonly _localeService: LocaleService) { + private readonly _useExternalModel: boolean; + + private _model: DocumentModel | null = null; + + // TODO: wzhudev this is strange that DocsView depends on current univer service + constructor( + externalModel: DocumentModel | undefined, + @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, + @Inject(DocsViewManagerService) private _docsViewManagerService: DocsViewManagerService, + @Inject(LocaleService) private readonly _localeService: LocaleService + ) { super(); + + if (externalModel) { + this._useExternalModel = true; + this._model = externalModel; + } else { + this._useExternalModel = false; + } + + const docsModel = this._useExternalModel ? externalModel : this._currentUniverService.getCurrentUniverDocInstance()?.getDocument(); + if (docsModel) { + this._docsViewManagerService.registerCanvasViewForUniverInstance(docsModel!.getUnitId(), this); + } } getDocumentSkeleton() { @@ -51,12 +74,16 @@ export class DocsView extends BaseView { protected override _initialize() { const scene = this.getScene(); - - const docsModel = this._currentUniverService.getCurrentUniverDocInstance().getDocument(); + const docsModel = this._useExternalModel ? this._model! : this._currentUniverService.getCurrentUniverDocInstance()?.getDocument(); + if (!docsModel) { + return; + } const documentSkeleton = this._buildSkeleton(docsModel); - - const documents = new Documents(DOCS_VIEW_KEY.MAIN, documentSkeleton); + const documents = new Documents(DOCS_VIEW_KEY.MAIN, documentSkeleton, { + pageMarginLeft: docsModel.documentStyle.marginLeft, + pageMarginTop: docsModel.documentStyle.marginTop, + }); documents.zIndex = 1000; this._documentSkeleton = documentSkeleton; this._documents = documents; @@ -110,8 +137,6 @@ export class DocsView extends BaseView { obj?.translate(obj.left + docsLeft - pageMarginLeft, obj.top + docsTop - pageMarginTop); } } - - documents.enableEditor(); } private _buildSkeleton(model: DocumentModel) { diff --git a/packages/base-docs/src/View/Render/Views/PageView.ts b/packages/base-docs/src/View/Render/Views/PageView.ts index 4bab2810d9..2eab3389b2 100644 --- a/packages/base-docs/src/View/Render/Views/PageView.ts +++ b/packages/base-docs/src/View/Render/Views/PageView.ts @@ -1,5 +1,6 @@ -import { Rect, Documents, IPageRenderConfig, Scene } from '@univerjs/base-render'; +import { Documents, IPageRenderConfig, Rect, Scene } from '@univerjs/base-render'; import { Injector } from '@wendellhu/redi'; + import { BaseView, CanvasViewRegistry } from '../BaseView'; import { DOCS_VIEW_KEY } from './DocsView'; diff --git a/packages/base-docs/src/commands/commands/core-editing.command.ts b/packages/base-docs/src/commands/commands/core-editing.command.ts index b8eba20ba2..86f49d7e67 100644 --- a/packages/base-docs/src/commands/commands/core-editing.command.ts +++ b/packages/base-docs/src/commands/commands/core-editing.command.ts @@ -1,10 +1,13 @@ import { CommandType, + createEmptyDocSnapshot, getTextIndexByCursor, ICommand, ICommandInfo, ICommandService, + ICurrentUniverService, IDocumentBody, + IDocumentData, ITextSelectionRange, IUndoRedoService, UpdateDocsAttributeType, @@ -91,7 +94,7 @@ export const InsertCommand: ICommand = { const result = await commandService.executeCommand(doMutation.id, doMutation.params); if (result) { undoRedoService.pushUndoRedo({ - URI: 'doc', + URI: unitId, undo() { commandService.executeCommand(RichTextEditingMutation.id, result); return true; @@ -143,7 +146,7 @@ export const DeleteCommand: ICommand = { const result = await commandService.executeCommand(doMutation.id, doMutation.params); if (result) { undoRedoService.pushUndoRedo({ - URI: 'doc', + URI: unitId, undo() { commandService.executeCommand(RichTextEditingMutation.id, result); return true; @@ -212,7 +215,7 @@ export const UpdateCommand: ICommand = { const result = await commandService.executeCommand(doMutation.id, doMutation.params); if (result) { undoRedoService.pushUndoRedo({ - URI: 'doc', + URI: unitId, undo() { commandService.executeCommand(RichTextEditingMutation.id, result); return true; @@ -243,7 +246,6 @@ export const IMEInputCommand: ICommand = { type: CommandType.COMMAND, handler: async (accessor, params: IIMEInputCommandParams) => { const commandService = accessor.get(ICommandService); - const undoRedoService = accessor.get(IUndoRedoService); const { unitId, newText, oldTextLen, start, segmentId } = params; const doMutation: ICommandInfo = { @@ -281,7 +283,6 @@ export const IMEInputCommand: ICommand = { const result = await commandService.executeCommand(doMutation.id, doMutation.params); if (!result) { - // NOTE: maybe we should throw an error here? return false; } @@ -289,6 +290,35 @@ export const IMEInputCommand: ICommand = { }, }; +export interface ICoverCommandParams { + unitId: string; + + snapshot?: IDocumentData; + clearUndoRedoStack?: boolean; +} + +export const CoverCommand: ICommand = { + id: 'doc.command-cover-content', + type: CommandType.COMMAND, + handler: async (accessor, params: ICoverCommandParams) => { + const currentUniverService = accessor.get(ICurrentUniverService); + const { unitId, snapshot, clearUndoRedoStack } = params; + const doc = currentUniverService.getUniverDocInstance(unitId); + if (!doc) { + return false; + } + + doc.getDocument().reset(snapshot || createEmptyDocSnapshot()); + + if (clearUndoRedoStack) { + const undoRedoService = accessor.get(IUndoRedoService); + undoRedoService.clearUndoRedo(unitId); + } + + return true; + }, +}; + function getRetainAndDeleteFromReplace(range: ITextSelectionRange, segmentId?: string): Array { const { cursorStart, cursorEnd, isEndBack, isStartBack, isCollapse } = range; const dos: Array = []; diff --git a/packages/base-docs/src/commands/mutations/functions/insert.ts b/packages/base-docs/src/commands/mutations/functions/insert.ts index a23f0e041e..d50ebb9d3d 100644 --- a/packages/base-docs/src/commands/mutations/functions/insert.ts +++ b/packages/base-docs/src/commands/mutations/functions/insert.ts @@ -21,7 +21,7 @@ export function InsertApply(document: DocumentModel, insertBody: IDocumentBody, updateAttributeByInsert(body, insertBody, textLength, currentIndex); - console.log('插入的model打印', bodyModel, body, textLength); + // console.log('插入的model打印', bodyModel, body, textLength); } function updateAttributeByInsert(body: IDocumentBody, insertBody: IDocumentBody, textLength: number, currentIndex: number) { diff --git a/packages/base-docs/src/index.ts b/packages/base-docs/src/index.ts index 7ace7013bc..3f774571ea 100644 --- a/packages/base-docs/src/index.ts +++ b/packages/base-docs/src/index.ts @@ -1 +1,15 @@ +export { + CoverCommand, + DeleteCommand, + ICoverCommandParams, + IDeleteCommandParams, + IIMEInputCommandParams, + IInsertCommandParams, + IMEInputCommand, + InsertCommand, + IUpdateCommandParams, + UpdateCommand, +} from './commands/commands/core-editing.command'; export * from './DocPlugin'; +export { DocsViewManagerService } from './services/docs-view-manager/docs-view-manager.service'; +export { DocsView, DocsViewFactory } from './View/Render/Views/DocsView'; diff --git a/packages/base-docs/src/services/doc-editing/doc-editing-service.ts b/packages/base-docs/src/services/doc-editing/doc-editing-service.ts deleted file mode 100644 index 7e3c0512d0..0000000000 --- a/packages/base-docs/src/services/doc-editing/doc-editing-service.ts +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This services provides methods to edit the content of the document. - */ -export class DocEditingService {} diff --git a/packages/base-docs/src/services/docs-view-manager/docs-view-manager.service.ts b/packages/base-docs/src/services/docs-view-manager/docs-view-manager.service.ts new file mode 100644 index 0000000000..52d4d91e50 --- /dev/null +++ b/packages/base-docs/src/services/docs-view-manager/docs-view-manager.service.ts @@ -0,0 +1,59 @@ +import { Disposable, fromObservable, ICurrentUniverService, toDisposable } from '@univerjs/core'; +import { IDisposable } from '@wendellhu/redi'; +import { Observable, Subject } from 'rxjs'; + +import type { DocsView } from '../../View/Render/Views/DocsView'; + +/** + * This services manages instances of doc CanvasView and determines which one + * the user is currently interacting with. + */ +export class DocsViewManagerService extends Disposable { + readonly current$: Observable; + + private readonly _current$: Subject = new Subject(); + + private readonly _viewsMap = new Map(); + + constructor(@ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService) { + super(); + + this.disposeWithMe( + fromObservable( + this._currentUniverService.focused$.subscribe((id) => { + if (!id) { + this._current$.next(null); + return; + } + + // FIXME: if the focused id is a sheet ID, we should focus on the cell editor instead + const view = this._viewsMap.get(id); + if (view) { + this._current$.next(view ?? null); + } + }) + ) + ); + + this.current$ = this._current$.asObservable(); + } + + override dispose(): void { + super.dispose(); + + this._viewsMap.clear(); + this._current$.complete(); + } + + registerCanvasViewForUniverInstance(unitId: string, docsView: DocsView): IDisposable { + this._viewsMap.set(unitId, docsView); + + return toDisposable(() => { + this._viewsMap.delete(unitId); + }); + } + + getDocsView(unitId: string): DocsView | undefined { + return this._viewsMap.get(unitId); + } +} diff --git a/packages/base-docs/src/services/selection-manager/selection-manager.service.ts b/packages/base-docs/src/services/selection-manager/selection-manager.service.ts deleted file mode 100644 index 71da739f61..0000000000 --- a/packages/base-docs/src/services/selection-manager/selection-manager.service.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { createIdentifier } from '@wendellhu/redi'; - -export interface ISelectionManagerService {} - -export const ISelectionManagerService = createIdentifier('doc.selection-manager-service'); - -export class SelectionManagerService implements ISelectionManagerService {} diff --git a/packages/base-docs/src/shortcuts/core-editing.shortcut.ts b/packages/base-docs/src/shortcuts/core-editing.shortcut.ts index 3b6c24852f..77a65f8371 100644 --- a/packages/base-docs/src/shortcuts/core-editing.shortcut.ts +++ b/packages/base-docs/src/shortcuts/core-editing.shortcut.ts @@ -1,13 +1,15 @@ -import { IShortcutItem, KeyCode } from '@univerjs/base-ui'; +import { FOCUSING_DOC, IShortcutItem, KeyCode } from '@univerjs/base-ui'; import { BreakLineCommand, DeleteLeftCommand } from '../commands/commands/core-editing.command'; export const BreakLineShortcut: IShortcutItem = { id: BreakLineCommand.id, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), binding: KeyCode.ENTER, }; export const DeleteLeftShortcut: IShortcutItem = { id: DeleteLeftCommand.id, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), binding: KeyCode.BACKSPACE, }; diff --git a/packages/base-docs/src/shortcuts/cursor.shortcut.ts b/packages/base-docs/src/shortcuts/cursor.shortcut.ts index e891ffd198..0d56988797 100644 --- a/packages/base-docs/src/shortcuts/cursor.shortcut.ts +++ b/packages/base-docs/src/shortcuts/cursor.shortcut.ts @@ -1,4 +1,4 @@ -import { IShortcutItem, KeyCode } from '@univerjs/base-ui'; +import { FOCUSING_DOC, IShortcutItem, KeyCode } from '@univerjs/base-ui'; import { Direction } from '@univerjs/core'; import { MoveCursorOperation } from '../commands/operations/cursor.operation'; @@ -6,6 +6,7 @@ import { MoveCursorOperation } from '../commands/operations/cursor.operation'; export const MoveCursorUpShortcut: IShortcutItem = { id: MoveCursorOperation.id, binding: KeyCode.ARROW_UP, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), staticParameters: { direction: Direction.UP, }, @@ -14,6 +15,7 @@ export const MoveCursorUpShortcut: IShortcutItem = { export const MoveCursorDownShortcut: IShortcutItem = { id: MoveCursorOperation.id, binding: KeyCode.ARROW_DOWN, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), staticParameters: { direction: Direction.DOWN, }, @@ -22,6 +24,7 @@ export const MoveCursorDownShortcut: IShortcutItem = { export const MoveCursorLeftShortcut: IShortcutItem = { id: MoveCursorOperation.id, binding: KeyCode.ARROW_LEFT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), staticParameters: { direction: Direction.LEFT, }, @@ -30,6 +33,7 @@ export const MoveCursorLeftShortcut: IShortcutItem = { export const MoveCursorRightShortcut: IShortcutItem = { id: MoveCursorOperation.id, binding: KeyCode.ARROW_RIGHT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_DOC), staticParameters: { direction: Direction.RIGHT, }, diff --git a/packages/base-render/src/Basics/Interfaces.ts b/packages/base-render/src/Basics/Interfaces.ts index 1d569f35a5..16c791f34b 100644 --- a/packages/base-render/src/Basics/Interfaces.ts +++ b/packages/base-render/src/Basics/Interfaces.ts @@ -1,5 +1,6 @@ import { BooleanNumber, + DocumentBodyModel, GridType, IDocStyleBase, IDocumentLayout, @@ -11,12 +12,12 @@ import { ISize, ITextStyle, ITransformState, - DocumentBodyModel, } from '@univerjs/core'; + import { - IDocumentSkeletonDrawingAnchor, IDocumentSkeletonBullet, IDocumentSkeletonDrawing, + IDocumentSkeletonDrawingAnchor, IDocumentSkeletonFontStyle, IDocumentSkeletonFooter, IDocumentSkeletonHeader, diff --git a/packages/base-render/src/Component/Docs/DocSkeleton.ts b/packages/base-render/src/Component/Docs/DocSkeleton.ts index 7669faff97..9b03a0621e 100644 --- a/packages/base-render/src/Component/Docs/DocSkeleton.ts +++ b/packages/base-render/src/Component/Docs/DocSkeleton.ts @@ -1,6 +1,7 @@ import { ColumnSeparatorType, DocumentModelOrSimple, + fromObservable, GridType, HorizontalAlign, ISectionBreak, @@ -47,7 +48,15 @@ export class DocumentSkeleton extends Skeleton { constructor(docModel: DocumentModelOrSimple, localeService: LocaleService) { super(localeService); + this._docModel = docModel; + this.disposeWithMe( + fromObservable( + this._docModel.bodyModel.modelChange$.subscribe(() => { + this.calculate(); + }) + ) + ); } static create(docModel: DocumentModelOrSimple, localeService: LocaleService) { diff --git a/packages/base-render/src/Component/Docs/Document.Editor.ts b/packages/base-render/src/Component/Docs/Document.Editor.ts index 47e4610af4..bde66e6eb6 100644 --- a/packages/base-render/src/Component/Docs/Document.Editor.ts +++ b/packages/base-render/src/Component/Docs/Document.Editor.ts @@ -126,9 +126,51 @@ export class DocsEditor { this._scrollToSelection(); } + activate(toLastPosition = false): void { + // const documents = this._documents; + // if (!documents) { + // return; + // } + // setTimeout(() => { + // documents.makeDirty(); + // }); + // FIXME: I don't know if this is correct. Just trying it. + // const lastCharIndex = documents.getSkeleton()!.getModel().bodyModel.body.dataStream.length; + // const span = documents.findNodeByCharIndex(lastCharIndex - 1); + // const position = documents.findPositionBySpan(span!); + // const remain = this.remain(); + // if (position && remain) { + // remain.startNodePosition = { ...position, isBack: true }; + // remain.endNodePosition = undefined; + // } + // if (!this._documents) { + // return; + // } + // TODO + // this.activeViewport = this._documents.getActiveViewportByCoord(evtOffsetX, evtOffsetY); + // const startNode = this._documents.findNodeByCoord(evtOffsetX, evtOffsetY); + // console.log('startNode', startNode, position, evtOffsetX, evtOffsetY); + // if (position == null) { + // this._deleteAllTextSelection(); + // return; + // } + // if (startNode?.node.streamType === DataStreamTreeTokenType.PARAGRAPH) { + // position.isBack = true; + // } + // if (evt.ctrlKey || this._isEmptyTextSelection()) { + // const newTextSelection = new TextSelection(this._documents.getScene(), position); + // this._addTextSelection(newTextSelection); + // } else { + // this._updateTextSelection(position); + // } + // this._activeSelectionRefresh(); + // this._syncDomToSelection(); + } + active(x: number, y: number) { this._container.style.left = `${x}px`; this._container.style.top = `${y}px`; + this._container.style.zIndex = `1000`; this._cursor.style.animation = 'univer_cursor_blinkStyle 1s steps(1) infinite'; this._cursor.style.display = 'revert'; @@ -138,14 +180,21 @@ export class DocsEditor { }, 0); } - deactivate() { + focus(): void { + this._input.focus(); + } + + // FIXME: for editor cell editor we don't need to blur the input element + deactivate(withoutBlur = true) { this._container.style.left = `0px`; this._container.style.top = `0px`; this._cursor.style.animation = ''; this._cursor.style.display = 'none'; - this._input.blur(); + if (!withoutBlur) { + this._input.blur(); + } } changeDocuments(documents: DocComponent) { @@ -169,23 +218,16 @@ export class DocsEditor { private _initialDom() { const container = document.createElement('div'); - container.style.position = 'absolute'; - - container.style.position = 'absolute'; - container.style.left = `0px`; container.style.top = `0px`; const inputParent = document.createElement('div'); const inputDom = document.createElement('div'); - const cursorDom = document.createElement('div'); inputParent.appendChild(inputDom); - container.appendChild(inputParent); - container.appendChild(cursorDom); this._container = container; @@ -675,12 +717,11 @@ export class DocsEditor { const scene = documents.getScene(); this._downObserver = documents.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { - const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; - if (!this._documents) { return; } + const { offsetX: evtOffsetX, offsetY: evtOffsetY } = evt; this.activeViewport = this._documents.getActiveViewportByCoord(evtOffsetX, evtOffsetY); const startNode = this._documents.findNodeByCoord(evtOffsetX, evtOffsetY); diff --git a/packages/base-render/src/Component/Docs/Document.ts b/packages/base-render/src/Component/Docs/Document.ts index e8969f3959..74c69b5a1c 100644 --- a/packages/base-render/src/Component/Docs/Document.ts +++ b/packages/base-render/src/Component/Docs/Document.ts @@ -90,6 +90,10 @@ export class Documents extends DocComponent { return new Documents(oKey, documentSkeleton, config); } + getEditor(): DocsEditor { + return this._editor; + } + getActiveSelection() { return this._editor.getActiveTextSelection(); } @@ -157,6 +161,7 @@ export class Documents extends DocComponent { if (this._hasEditor) { return; } + this._editor = DocsEditor.create(this); this._hasEditor = true; } @@ -212,6 +217,11 @@ export class Documents extends DocComponent { return; } + const isDrawingCellEditor = documentSkeleton.getModel().snapshot.id === 'sheet.model.cell-editor'; + if (isDrawingCellEditor) { + console.log('drawing', documentSkeleton, documentSkeleton.getModel().snapshot); + } + // if (this.isCalculateSkeleton) { // documentSkeleton.calculate(bounds); // } diff --git a/packages/base-render/src/Component/Skeleton.ts b/packages/base-render/src/Component/Skeleton.ts index 5af637bdbb..a6a6360b3a 100644 --- a/packages/base-render/src/Component/Skeleton.ts +++ b/packages/base-render/src/Component/Skeleton.ts @@ -1,15 +1,17 @@ -import { LocaleService } from '@univerjs/core'; +import { Disposable, LocaleService } from '@univerjs/core'; import { Inject } from '@wendellhu/redi'; import { IFontLocale } from '../Basics/Interfaces'; import { en, zh } from '../Locale'; -export class Skeleton { +export class Skeleton extends Disposable { private _fontLocale: IFontLocale; private _dirty = true; constructor(@Inject(LocaleService) protected readonly _localService: LocaleService) { + super(); + this._localeInitial(); } diff --git a/packages/base-render/src/Engine.ts b/packages/base-render/src/Engine.ts index cfce011dd8..193839b276 100644 --- a/packages/base-render/src/Engine.ts +++ b/packages/base-render/src/Engine.ts @@ -6,6 +6,7 @@ import { TRANSFORM_CHANGE_OBSERVABLE_TYPE } from './Basics/Interfaces'; import { PerformanceMonitor } from './Basics/PerformanceMonitor'; import { getPointerPrefix, getSizeForDom, IsSafari, requestNewFrame } from './Basics/Tools'; import { Canvas } from './Canvas'; +import { fromWindowEvent } from './common/lifecycle'; import { Scene } from './Scene'; import { ThinEngine } from './ThinEngine'; @@ -111,7 +112,9 @@ export class Engine extends ThinEngine { setContainer(elem: HTMLElement) { this._container = elem; this._container.appendChild(this._canvasEle); + this.resize(); + this.disposeWithMe(fromWindowEvent('resize', () => this.resize())); } resize() { @@ -119,7 +122,6 @@ export class Engine extends ThinEngine { return; } - // const { offsetWidth, offsetHeight } = this._container; const { width, height } = getSizeForDom(this._container); this.resizeBySize(width, height); } diff --git a/packages/base-render/src/RenderEngine.ts b/packages/base-render/src/RenderEngine.ts index c89c250e08..55cb529ca5 100644 --- a/packages/base-render/src/RenderEngine.ts +++ b/packages/base-render/src/RenderEngine.ts @@ -1,24 +1,18 @@ import { Plugin, PLUGIN_NAMES, PluginType } from '@univerjs/core'; -import { createIdentifier, Injector, Optional } from '@wendellhu/redi'; +import { createIdentifier, Inject, Injector } from '@wendellhu/redi'; import { Engine } from './Engine'; +/** + * The global rendering engine. + */ export const IRenderingEngine = createIdentifier('univer.render-engine'); export class RenderEngine extends Plugin { static override type = PluginType.Univer; - private _engine: Engine; - - constructor(_config: undefined, @Optional(Injector) override readonly _injector: Injector) { + constructor(_config: undefined, @Inject(Injector) override readonly _injector: Injector) { super(PLUGIN_NAMES.BASE_RENDER); - - this._engine = new Engine(); - - this._injector?.add([IRenderingEngine, { useFactory: () => this._engine }]); - } - - getEngine(): Engine { - return this._engine; + this._injector.add([IRenderingEngine, { useFactory: () => new Engine() }]); } } diff --git a/packages/base-render/src/Scene.inputManager.ts b/packages/base-render/src/Scene.inputManager.ts index 9ed7c78bab..1ed0e204f2 100644 --- a/packages/base-render/src/Scene.inputManager.ts +++ b/packages/base-render/src/Scene.inputManager.ts @@ -1,9 +1,10 @@ -import { Observer, Nullable } from '@univerjs/core'; -import { IKeyboardEvent, IMouseEvent, IPointerEvent, IEvent, DeviceType, PointerInput, IWheelEvent } from './Basics/IEvents'; -import { Vector2 } from './Basics/Vector2'; +import { Nullable, Observer } from '@univerjs/core'; + import { BaseObject } from './BaseObject'; -import { ThinScene } from './ThinScene'; import { RENDER_CLASS_TYPE } from './Basics/Const'; +import { DeviceType, IEvent, IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent, PointerInput } from './Basics/IEvents'; +import { Vector2 } from './Basics/Vector2'; +import { ThinScene } from './ThinScene'; import { Viewport } from './Viewport'; export class InputManager { diff --git a/packages/base-render/src/SceneViewer.ts b/packages/base-render/src/SceneViewer.ts index 34a2c6edfd..2c032f5a0a 100644 --- a/packages/base-render/src/SceneViewer.ts +++ b/packages/base-render/src/SceneViewer.ts @@ -1,10 +1,11 @@ import { IKeyValue, Nullable } from '@univerjs/core'; -import { IBoundRect, Vector2 } from './Basics/Vector2'; + import { BaseObject } from './BaseObject'; -import { ThinScene } from './ThinScene'; -import { IObjectFullState } from './Basics/Interfaces'; import { RENDER_CLASS_TYPE } from './Basics/Const'; +import { IObjectFullState } from './Basics/Interfaces'; import { transformBoundingCoord } from './Basics/Position'; +import { IBoundRect, Vector2 } from './Basics/Vector2'; +import { ThinScene } from './ThinScene'; export class SceneViewer extends BaseObject { private _subScenes = new Map(); @@ -14,6 +15,7 @@ export class SceneViewer extends BaseObject { private _allowSelectedClipElement = false; constructor(key?: string, props?: IObjectFullState) { + // WTF: the name of `props`'s interface ends with State? super(key); this._initialProps(props); } @@ -56,7 +58,7 @@ export class SceneViewer extends BaseObject { } getSubScene(sceneKey: string) { - for (let [key, scene] of this._subScenes) { + for (const [key, scene] of this._subScenes) { if (key === sceneKey) { return scene; } diff --git a/packages/base-render/src/ThinEngine.ts b/packages/base-render/src/ThinEngine.ts index df1e84a33a..ed5b9a0985 100644 --- a/packages/base-render/src/ThinEngine.ts +++ b/packages/base-render/src/ThinEngine.ts @@ -1,9 +1,13 @@ -import { Observable } from '@univerjs/core'; -import { CURSOR_TYPE, RENDER_CLASS_TYPE } from './Basics/Const'; -import { ITransformChangeState } from './Basics/Interfaces'; -import { IEvent } from './Basics/IEvents'; +import { Disposable, Observable } from '@univerjs/core'; +import { IDisposable } from '@wendellhu/redi'; -export abstract class ThinEngine { +import { CURSOR_TYPE, RENDER_CLASS_TYPE } from './Basics/Const'; +import { IEvent } from './Basics/IEvents'; +import { ITransformChangeState } from './Basics/Interfaces'; +import { Canvas } from './Canvas'; + +// FIXME: T should extends something that is disposable +export abstract class ThinEngine extends Disposable { onInputChangedObservable = new Observable(); onTransformChangeObservable = new Observable(); @@ -62,7 +66,9 @@ export abstract class ThinEngine { return this._activeScene != null; } - dispose() { + override dispose() { + super.dispose(); + const scenes = this.getScenes(); const sceneKeys = Object.keys(scenes); sceneKeys.forEach((key) => { @@ -79,11 +85,11 @@ export abstract class ThinEngine { } } - clearCanvas() {} + abstract clearCanvas(): void; - getCanvas() {} + abstract getCanvas(): Canvas; - getCanvasElement() {} + abstract getCanvasElement(): HTMLCanvasElement; - setCanvasCursor(val: CURSOR_TYPE) {} + abstract setCanvasCursor(val: CURSOR_TYPE): void; } diff --git a/packages/base-render/src/ThinScene.ts b/packages/base-render/src/ThinScene.ts index c8ed894693..bc3b2d44df 100644 --- a/packages/base-render/src/ThinScene.ts +++ b/packages/base-render/src/ThinScene.ts @@ -1,14 +1,11 @@ import { EventState, IKeyValue, Nullable, Observable, Observer } from '@univerjs/core'; -import { EVENT_TYPE, RENDER_CLASS_TYPE } from './Basics/Const'; - -import { ITransformChangeState } from './Basics/Interfaces'; - -import { Transform } from './Basics/Transform'; - -import { IBoundRect, Vector2 } from './Basics/Vector2'; import { BaseObject } from './BaseObject'; +import { EVENT_TYPE, RENDER_CLASS_TYPE } from './Basics/Const'; import { IKeyboardEvent, IMouseEvent, IPointerEvent, IWheelEvent } from './Basics/IEvents'; +import { ITransformChangeState } from './Basics/Interfaces'; +import { Transform } from './Basics/Transform'; +import { IBoundRect, Vector2 } from './Basics/Vector2'; export abstract class ThinScene { onTransformChangeObservable = new Observable(); diff --git a/packages/base-render/src/common/lifecycle.ts b/packages/base-render/src/common/lifecycle.ts new file mode 100644 index 0000000000..a81334a5bc --- /dev/null +++ b/packages/base-render/src/common/lifecycle.ts @@ -0,0 +1,7 @@ +import { toDisposable } from '@univerjs/core'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function fromWindowEvent(type: K, listener: (this: Window, ev: WindowEventMap[K]) => any, options?: boolean | AddEventListenerOptions) { + window.addEventListener(type, listener, options); + return toDisposable(() => window.removeEventListener(type, listener, options)); +} diff --git a/packages/base-render/src/index.ts b/packages/base-render/src/index.ts index b14af3152c..72874d55cf 100644 --- a/packages/base-render/src/index.ts +++ b/packages/base-render/src/index.ts @@ -1,15 +1,15 @@ -export * from './Locale'; +export * from './BaseObject'; export * from './Basics'; +export * from './Canvas'; +export * from './Component'; export * from './Custom'; export * from './Engine'; -export * from './BaseObject'; +export * from './Group'; export * from './Layer'; +export * from './Locale'; +export { IRenderingEngine, RenderEngine } from './RenderEngine'; export * from './Scene'; export * from './SceneViewer'; -export * from './Viewport'; -export * from './Canvas'; -export * from './Group'; -export * from './Shape'; -export * from './Component'; -export { RenderEngine, IRenderingEngine } from './RenderEngine'; export * from './ScrollTimer'; +export * from './Shape'; +export * from './Viewport'; diff --git a/packages/base-sheets/src/Commands/Commands/add-worksheet-merge.command.ts b/packages/base-sheets/src/Commands/Commands/add-worksheet-merge.command.ts index c088857c8a..540af220f6 100644 --- a/packages/base-sheets/src/Commands/Commands/add-worksheet-merge.command.ts +++ b/packages/base-sheets/src/Commands/Commands/add-worksheet-merge.command.ts @@ -76,7 +76,7 @@ export const AddWorksheetMergeCommand: ICommand = { if (result && removeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(RemoveWorksheetMergeMutation.id, undoMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(AddWorksheetMergeMutation.id, undoRemoveMergeMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/clear-selection-content.command.ts b/packages/base-sheets/src/Commands/Commands/clear-selection-content.command.ts index a30d0f6d54..3e5d4657af 100644 --- a/packages/base-sheets/src/Commands/Commands/clear-selection-content.command.ts +++ b/packages/base-sheets/src/Commands/Commands/clear-selection-content.command.ts @@ -45,7 +45,7 @@ export const ClearSelectionContentCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return (commandService.executeCommand(SetRangeValuesMutation.id, undoClearMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(SetRangeStyleMutation.id, setStyleMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/copy-format-to-range.command.ts b/packages/base-sheets/src/Commands/Commands/copy-format-to-range.command.ts index ad284533ae..86545f8055 100644 --- a/packages/base-sheets/src/Commands/Commands/copy-format-to-range.command.ts +++ b/packages/base-sheets/src/Commands/Commands/copy-format-to-range.command.ts @@ -62,7 +62,7 @@ export const CopyFormatToRangeCommand: ICommand = { const result = commandService.executeCommand(SetRangeStyleMutation.id, setRangeStyleMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetRangeStyleMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/copy-range-to.command.ts b/packages/base-sheets/src/Commands/Commands/copy-range-to.command.ts index de677e28a2..d7328be485 100644 --- a/packages/base-sheets/src/Commands/Commands/copy-range-to.command.ts +++ b/packages/base-sheets/src/Commands/Commands/copy-range-to.command.ts @@ -62,7 +62,7 @@ export const CopyRangeToCommand: ICommand = { const result = commandService.executeCommand(SetRangeValuesMutation.id, setRangeValuesMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetRangeValuesMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/copy-to-worksheet.command.ts b/packages/base-sheets/src/Commands/Commands/copy-to-worksheet.command.ts index 1ebbb756b0..86be78dcb6 100644 --- a/packages/base-sheets/src/Commands/Commands/copy-to-worksheet.command.ts +++ b/packages/base-sheets/src/Commands/Commands/copy-to-worksheet.command.ts @@ -43,7 +43,7 @@ export const CopySheetToCommand: ICommand = { const result = commandService.executeCommand(SetWorksheetConfigMutation.id, setWorksheetConfigMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetConfigMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/copy-values-to-range.command.ts b/packages/base-sheets/src/Commands/Commands/copy-values-to-range.command.ts index 1e76f2647c..70c08a89fd 100644 --- a/packages/base-sheets/src/Commands/Commands/copy-values-to-range.command.ts +++ b/packages/base-sheets/src/Commands/Commands/copy-values-to-range.command.ts @@ -59,7 +59,7 @@ export const CopyValuesToRangeCommand: ICommand = { const result = commandService.executeCommand(SetRangeFormattedValueMutation.id, setRangeFormattedValueMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetRangeFormattedValueMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/copy-worksheet.command.ts b/packages/base-sheets/src/Commands/Commands/copy-worksheet.command.ts index 9758bf5d3e..5648756d16 100644 --- a/packages/base-sheets/src/Commands/Commands/copy-worksheet.command.ts +++ b/packages/base-sheets/src/Commands/Commands/copy-worksheet.command.ts @@ -54,7 +54,7 @@ export const CopySheetCommand: ICommand = { if (insertResult && result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(SetWorksheetActivateMutation.id, undoMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(RemoveSheetMutation.id, removeSheetMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/delete-range-move-left.command.ts b/packages/base-sheets/src/Commands/Commands/delete-range-move-left.command.ts index 3e487817b1..ab78e54f97 100644 --- a/packages/base-sheets/src/Commands/Commands/delete-range-move-left.command.ts +++ b/packages/base-sheets/src/Commands/Commands/delete-range-move-left.command.ts @@ -45,7 +45,7 @@ export const DeleteRangeMoveLeftCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(InsertRangeMutation.id, insertRangeMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/delete-range-move-up.command.ts b/packages/base-sheets/src/Commands/Commands/delete-range-move-up.command.ts index 42be253e97..b5b5a3e092 100644 --- a/packages/base-sheets/src/Commands/Commands/delete-range-move-up.command.ts +++ b/packages/base-sheets/src/Commands/Commands/delete-range-move-up.command.ts @@ -45,7 +45,7 @@ export const DeleteRangeMoveUpCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(InsertRangeMutation.id, insertRangeMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/insert-range-move-down.command.ts b/packages/base-sheets/src/Commands/Commands/insert-range-move-down.command.ts index c7b6713715..f6c4f0be9c 100644 --- a/packages/base-sheets/src/Commands/Commands/insert-range-move-down.command.ts +++ b/packages/base-sheets/src/Commands/Commands/insert-range-move-down.command.ts @@ -56,7 +56,7 @@ export const InsertRangeMoveDownCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(DeleteRangeMutation.id, deleteRangeMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/insert-range-move-right.command.ts b/packages/base-sheets/src/Commands/Commands/insert-range-move-right.command.ts index dbd9207d7c..5af8de2eec 100644 --- a/packages/base-sheets/src/Commands/Commands/insert-range-move-right.command.ts +++ b/packages/base-sheets/src/Commands/Commands/insert-range-move-right.command.ts @@ -56,7 +56,7 @@ export const InsertRangeMoveRightCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(DeleteRangeMutation.id, deleteRangeMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/insert-row-col.command.ts b/packages/base-sheets/src/Commands/Commands/insert-row-col.command.ts index af5f85d3cd..9640e38157 100644 --- a/packages/base-sheets/src/Commands/Commands/insert-row-col.command.ts +++ b/packages/base-sheets/src/Commands/Commands/insert-row-col.command.ts @@ -103,7 +103,7 @@ export const InsertRowCommand: ICommand = { if (result && deleteResult && removeResult && mergeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(DeleteRangeMutation.id, deleteRangeMutationParams) as Promise) .then((res) => { @@ -312,7 +312,7 @@ export const InsertColCommand: ICommand = { if (result && deleteResult && removeResult && mergeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(DeleteRangeMutation.id, deleteRangeMutationParams) as Promise) .then((res) => { diff --git a/packages/base-sheets/src/Commands/Commands/insert-sheet.command.ts b/packages/base-sheets/src/Commands/Commands/insert-sheet.command.ts index 8990bf9b89..3f6c950ceb 100644 --- a/packages/base-sheets/src/Commands/Commands/insert-sheet.command.ts +++ b/packages/base-sheets/src/Commands/Commands/insert-sheet.command.ts @@ -66,7 +66,7 @@ export const InsertSheetCommand: ICommand = { if (result && activateResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(SetWorksheetActivateMutation.id, undoMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(RemoveSheetMutation.id, removeSheetMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/move-columns-to.command.ts b/packages/base-sheets/src/Commands/Commands/move-columns-to.command.ts index 6d89683d3a..c7b2397708 100644 --- a/packages/base-sheets/src/Commands/Commands/move-columns-to.command.ts +++ b/packages/base-sheets/src/Commands/Commands/move-columns-to.command.ts @@ -69,7 +69,7 @@ export const MoveColumnsToCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return (commandService.executeCommand(InsertColMutation.id, undoRemoveColumnMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(RemoveColMutation.id, undoMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/move-range-to.command.ts b/packages/base-sheets/src/Commands/Commands/move-range-to.command.ts index b1c3910cd8..598306c7df 100644 --- a/packages/base-sheets/src/Commands/Commands/move-range-to.command.ts +++ b/packages/base-sheets/src/Commands/Commands/move-range-to.command.ts @@ -64,7 +64,7 @@ export const MoveRangeToCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return (commandService.executeCommand(SetRangeValuesMutation.id, undoClearMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(SetRangeValuesMutation.id, undoMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/move-rows-to.command.ts b/packages/base-sheets/src/Commands/Commands/move-rows-to.command.ts index 53a850679b..94199b2e96 100644 --- a/packages/base-sheets/src/Commands/Commands/move-rows-to.command.ts +++ b/packages/base-sheets/src/Commands/Commands/move-rows-to.command.ts @@ -69,7 +69,7 @@ export const MoveRowsToCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return (commandService.executeCommand(InsertRowMutation.id, undoRemoveRowMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(RemoveRowMutation.id, undoMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/remove-row-col.command.ts b/packages/base-sheets/src/Commands/Commands/remove-row-col.command.ts index c5c3473594..975762b9bd 100644 --- a/packages/base-sheets/src/Commands/Commands/remove-row-col.command.ts +++ b/packages/base-sheets/src/Commands/Commands/remove-row-col.command.ts @@ -107,7 +107,7 @@ export const RemoveRowCommand: ICommand = { if (result && deleteResult && removeResult && mergeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(InsertRowMutation.id, undoMutationParams) as Promise) .then((res) => { @@ -233,7 +233,7 @@ export const RemoveColCommand: ICommand = { if (result && deleteResult && removeResult && mergeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(InsertColMutation.id, undoMutationParams) as Promise) .then((res) => { diff --git a/packages/base-sheets/src/Commands/Commands/remove-sheet.command.ts b/packages/base-sheets/src/Commands/Commands/remove-sheet.command.ts index 5f8c6a1302..12487aec7e 100644 --- a/packages/base-sheets/src/Commands/Commands/remove-sheet.command.ts +++ b/packages/base-sheets/src/Commands/Commands/remove-sheet.command.ts @@ -56,7 +56,7 @@ export const RemoveSheetCommand: ICommand = { if (result && activeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(InsertSheetMutation.id, InsertSheetMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(SetWorksheetActivateMutation.id, activeMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/remove-worksheet-merge.command.ts b/packages/base-sheets/src/Commands/Commands/remove-worksheet-merge.command.ts index 398cd17502..42a7aadc19 100644 --- a/packages/base-sheets/src/Commands/Commands/remove-worksheet-merge.command.ts +++ b/packages/base-sheets/src/Commands/Commands/remove-worksheet-merge.command.ts @@ -36,7 +36,7 @@ export const RemoveWorksheetMergeCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(AddWorksheetMergeMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-border-command.ts b/packages/base-sheets/src/Commands/Commands/set-border-command.ts index 1be3921a2d..c8f1c0e3aa 100644 --- a/packages/base-sheets/src/Commands/Commands/set-border-command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-border-command.ts @@ -330,7 +330,7 @@ export const SetBorderCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(SetBorderStylesMutation.id, undoSetBorderStylesMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-border-styles-command.ts b/packages/base-sheets/src/Commands/Commands/set-border-styles-command.ts index cc5d0604fe..07c2435de8 100644 --- a/packages/base-sheets/src/Commands/Commands/set-border-styles-command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-border-styles-command.ts @@ -317,7 +317,7 @@ export const SetBorderCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(SetBorderStylesMutation.id, undoSetBorderStylesMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-frozen-cancel.command.ts b/packages/base-sheets/src/Commands/Commands/set-frozen-cancel.command.ts index 6db786a701..b3f0cb0207 100644 --- a/packages/base-sheets/src/Commands/Commands/set-frozen-cancel.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-frozen-cancel.command.ts @@ -40,7 +40,7 @@ export const SetFrozenCancelCommand: ICommand = { if (result && frozenResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(SetFrozenColumnsMutation.id, frozenColumnMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(SetFrozenRowsMutation.id, undoMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/set-frozen-columns.command.ts b/packages/base-sheets/src/Commands/Commands/set-frozen-columns.command.ts index 45046cdf54..f1ae525025 100644 --- a/packages/base-sheets/src/Commands/Commands/set-frozen-columns.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-frozen-columns.command.ts @@ -34,7 +34,7 @@ export const SetFrozenColumnsCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetFrozenColumnsMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-frozen-rows.command.ts b/packages/base-sheets/src/Commands/Commands/set-frozen-rows.command.ts index 6554a225d2..a5cc8b1e19 100644 --- a/packages/base-sheets/src/Commands/Commands/set-frozen-rows.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-frozen-rows.command.ts @@ -34,7 +34,7 @@ export const SetFrozenRowsCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetFrozenRowsMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-hide-gridlines.command.ts b/packages/base-sheets/src/Commands/Commands/set-hide-gridlines.command.ts index 63dac7f8fa..efcf8e953d 100644 --- a/packages/base-sheets/src/Commands/Commands/set-hide-gridlines.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-hide-gridlines.command.ts @@ -43,7 +43,7 @@ export const SetHideGridlinesCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetHideGridlinesMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-range-formatted-value.command.ts b/packages/base-sheets/src/Commands/Commands/set-range-formatted-value.command.ts index 5b9c43ec6d..fa73dc35ad 100644 --- a/packages/base-sheets/src/Commands/Commands/set-range-formatted-value.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-range-formatted-value.command.ts @@ -74,7 +74,7 @@ export const SetRangeFormattedValueCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(SetRangeFormattedValueMutation.id, undoSetRangeFormattedValueMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-range-values.command.ts b/packages/base-sheets/src/Commands/Commands/set-range-values.command.ts index 46960c48d5..87d7eac321 100644 --- a/packages/base-sheets/src/Commands/Commands/set-range-values.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-range-values.command.ts @@ -1,4 +1,16 @@ -import { CommandType, ICellData, ICommand, ICommandService, ICurrentUniverService, IUndoRedoService, ObjectMatrix, ObjectMatrixPrimitiveType, Tools } from '@univerjs/core'; +import { + CommandType, + ICellData, + ICommand, + ICommandService, + ICurrentUniverService, + IRangeData, + isICellData, + IUndoRedoService, + ObjectMatrix, + ObjectMatrixPrimitiveType, + Tools, +} from '@univerjs/core'; import { IAccessor } from '@wendellhu/redi'; import { SelectionManagerService } from '../../Services/selection-manager.service'; @@ -7,6 +19,7 @@ import { ISetRangeValuesMutationParams, SetRangeValuesMutation, SetRangeValuesUn export interface ISetRangeValuesCommandParams { worksheetId?: string; workbookId?: string; + range?: IRangeData; value: ICellData | ICellData[][] | ObjectMatrixPrimitiveType; } @@ -22,47 +35,49 @@ export const SetRangeValuesCommand: ICommand = { const currentUniverService = accessor.get(ICurrentUniverService); const selectionManagerService = accessor.get(SelectionManagerService); - const range = selectionManagerService.getRangeDatas(); - if (!range?.length) { + const { + value, + range, + workbookId = currentUniverService.getCurrentUniverSheetInstance().getUnitId(), + worksheetId = currentUniverService.getCurrentUniverSheetInstance().getWorkBook().getActiveSheet().getSheetId(), + } = params; + + const currentSelections = range ? [range] : selectionManagerService.getRangeDatas(); + if (!currentSelections || !currentSelections.length) { return false; } - const workbookId = params.workbookId || currentUniverService.getCurrentUniverSheetInstance().getUnitId(); - const worksheetId = params.worksheetId || currentUniverService.getCurrentUniverSheetInstance().getWorkBook().getActiveSheet().getSheetId(); + const cellValue = new ObjectMatrix(); + let realCellValue: ObjectMatrixPrimitiveType | undefined; - const { value } = params; - let cellValue = new ObjectMatrix(); - - for (let i = 0; i < range.length; i++) { - const { startRow, startColumn, endRow, endColumn } = range[i]; + // FIXME: this is actually not ideal + for (let i = 0; i < currentSelections.length; i++) { + const { startRow, startColumn, endRow, endColumn } = currentSelections[i]; if (Tools.isArray(value)) { for (let r = 0; r <= endRow - startRow; r++) { for (let c = 0; c <= endColumn - startColumn; c++) { cellValue.setValue(r + startRow, c + startColumn, value[r][c]); } } - } else if (Tools.isObject(value)) { - cellValue = value as ObjectMatrix; - } else { + } else if (isICellData(value)) { cellValue.setValue(startRow, startColumn, value); + } else { + realCellValue = value as ObjectMatrixPrimitiveType; } } const setRangeValuesMutationParams: ISetRangeValuesMutationParams = { - rangeData: range, + rangeData: currentSelections, worksheetId, workbookId, - cellValue: cellValue as any, + cellValue: realCellValue ?? cellValue.getMatrix(), }; const undoSetRangeValuesMutationParams: ISetRangeValuesMutationParams = SetRangeValuesUndoMutationFactory(accessor, setRangeValuesMutationParams); - // execute do mutations and add undo mutations to undo stack if completed const result = commandService.executeCommand(SetRangeValuesMutation.id, setRangeValuesMutationParams); if (result) { undoRedoService.pushUndoRedo({ - // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 - // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(SetRangeValuesMutation.id, undoSetRangeValuesMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-style.command.ts b/packages/base-sheets/src/Commands/Commands/set-style.command.ts index 3e55ea43c6..6a3e2d6523 100644 --- a/packages/base-sheets/src/Commands/Commands/set-style.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-style.command.ts @@ -94,7 +94,7 @@ export const SetStyleCommand: ICommand> = { const result = commandService.executeCommand(SetRangeStyleMutation.id, setRangeStyleMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', // TODO: this URI is fake: would be replace with Univer business instance id + URI: workbookId, undo() { return commandService.executeCommand(SetRangeStyleMutation.id, undoSetRangeStyleMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-tab-color.command.ts b/packages/base-sheets/src/Commands/Commands/set-tab-color.command.ts index 59c0d524bc..a64bf19f55 100644 --- a/packages/base-sheets/src/Commands/Commands/set-tab-color.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-tab-color.command.ts @@ -35,7 +35,7 @@ export const SetTabColorCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetTabColorMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-activate.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-activate.command.ts index 244981fc39..9f2943d2eb 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-activate.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-activate.command.ts @@ -35,7 +35,7 @@ export const SetWorksheetActivateCommand: ICommand = { // no need // if (result) { // undoRedoService.pushUndoRedo({ - // URI: 'sheet', + // URI: workbookId, // undo() { // return commandService.executeCommand(SetWorksheetActivateMutation.id, undoMutationParams); // }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-col-width.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-col-width.command.ts index 52a4e967bc..f2bb48ce70 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-col-width.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-col-width.command.ts @@ -36,7 +36,7 @@ export const SetWorksheetColWidthCommand: ICommand = { const result = commandService.executeCommand(SetWorksheetColWidthMutation.id, redoMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetColWidthMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-column-hide.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-column-hide.command.ts index 37610f33b3..676e54ea52 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-column-hide.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-column-hide.command.ts @@ -36,7 +36,7 @@ export const SetWorksheetColumnHideCommand: ICommand = { const result = commandService.executeCommand(SetWorksheetColumnHideMutation.id, redoMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetColumnShowMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-column-show.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-column-show.command.ts index ed8fa02f34..2729d1faf8 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-column-show.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-column-show.command.ts @@ -37,7 +37,7 @@ export const SetWorksheetColumnShowCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetColumnHideMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-hide.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-hide.command.ts index c250fdb299..0fda9f707c 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-hide.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-hide.command.ts @@ -54,7 +54,7 @@ export const SetWorksheetHideCommand: ICommand = { if (result && activeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(SetWorksheetActivateMutation.id, unActiveMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(SetWorksheetHideMutation.id, undoMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-name.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-name.command.ts index 638242e450..c4f55d7f4b 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-name.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-name.command.ts @@ -34,7 +34,7 @@ export const SetWorksheetNameCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetNameMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-order.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-order.command.ts index 0c4fbf9065..a9c46a3bc0 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-order.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-order.command.ts @@ -36,7 +36,7 @@ export const SetWorksheetOrderCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetOrderMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-right-to-left.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-right-to-left.command.ts index ae54dd7b1e..1bbc17055d 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-right-to-left.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-right-to-left.command.ts @@ -47,7 +47,7 @@ export const SetWorksheetRightToLeftCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetRightToLeftMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-row-height.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-row-height.command.ts index c73220cbca..1a2bb9a586 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-row-height.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-row-height.command.ts @@ -37,7 +37,7 @@ export const SetWorksheetRowHeightCommand: ICommand = { const result = commandService.executeCommand(SetWorksheetRowHeightMutation.id, redoMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetRowHeightMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-row-hide.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-row-hide.command.ts index 7d5015bde9..03f9f38abf 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-row-hide.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-row-hide.command.ts @@ -36,7 +36,7 @@ export const SetWorksheetRowHideCommand: ICommand = { const result = commandService.executeCommand(SetWorksheetRowHideMutation.id, redoMutationParams); if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetRowShowMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-row-show.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-row-show.command.ts index bfa81dc2a7..5bd2bbd2dc 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-row-show.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-row-show.command.ts @@ -37,7 +37,7 @@ export const SetWorksheetRowShowCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetWorksheetRowHideMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/set-worksheet-show.command.ts b/packages/base-sheets/src/Commands/Commands/set-worksheet-show.command.ts index 5a2836ded2..06d692b969 100644 --- a/packages/base-sheets/src/Commands/Commands/set-worksheet-show.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-worksheet-show.command.ts @@ -50,7 +50,7 @@ export const SetWorksheetShowCommand: ICommand = { if (result && activeResult) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return (commandService.executeCommand(SetWorksheetActivateMutation.id, unActiveMutationParams) as Promise).then((res) => { if (res) return commandService.executeCommand(SetWorksheetHideMutation.id, undoMutationParams); diff --git a/packages/base-sheets/src/Commands/Commands/set-zoom-ratio.command.ts b/packages/base-sheets/src/Commands/Commands/set-zoom-ratio.command.ts index b662f6ca1d..d236504164 100644 --- a/packages/base-sheets/src/Commands/Commands/set-zoom-ratio.command.ts +++ b/packages/base-sheets/src/Commands/Commands/set-zoom-ratio.command.ts @@ -43,7 +43,7 @@ export const SetZoomRatioCommand: ICommand = { if (result) { undoRedoService.pushUndoRedo({ - URI: 'sheet', + URI: workbookId, undo() { return commandService.executeCommand(SetZoomRatioMutation.id, undoMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Commands/trim-whitespace.command.ts b/packages/base-sheets/src/Commands/Commands/trim-whitespace.command.ts index 4e6c6987e1..cc024fe7eb 100644 --- a/packages/base-sheets/src/Commands/Commands/trim-whitespace.command.ts +++ b/packages/base-sheets/src/Commands/Commands/trim-whitespace.command.ts @@ -57,7 +57,7 @@ export const TrimWhitespaceCommand: ICommand = { undoRedoService.pushUndoRedo({ // 如果有多个 mutation 构成一个封装项目,那么要封装在同一个 undo redo element 里面 // 通过勾子可以 hook 外部 controller 的代码来增加新的 action - URI: 'sheet', // TODO: this URI is fake + URI: workbookId, undo() { return commandService.executeCommand(SetRangeFormattedValueMutation.id, undoSetRangeFormattedValueMutationParams); }, diff --git a/packages/base-sheets/src/Commands/Mutations/set-range-values.mutation.ts b/packages/base-sheets/src/Commands/Mutations/set-range-values.mutation.ts index 15428c5620..67770ae94f 100644 --- a/packages/base-sheets/src/Commands/Mutations/set-range-values.mutation.ts +++ b/packages/base-sheets/src/Commands/Mutations/set-range-values.mutation.ts @@ -1,15 +1,4 @@ -import { - CommandType, - createRowColIter, - ICellData, - ICopyToOptionsData, - ICurrentUniverService, - IMutation, - IRangeData, - ObjectMatrix, - ObjectMatrixPrimitiveType, - Tools, -} from '@univerjs/core'; +import { CommandType, ICellData, ICopyToOptionsData, ICurrentUniverService, IMutation, IRangeData, ObjectMatrix, ObjectMatrixPrimitiveType, Tools } from '@univerjs/core'; import { IAccessor } from '@wendellhu/redi'; /** Params of `SetRangeValuesMutation` */ @@ -57,6 +46,7 @@ export const SetRangeValuesUndoMutationFactory = (accessor: IAccessor, params: I } as ISetRangeValuesMutationParams; }; +// TODO@Dushusir: this would cover style as well. Which is not expected. export const SetRangeValuesMutation: IMutation = { id: 'sheet.mutation.set-range-values', type: CommandType.MUTATION, @@ -76,11 +66,11 @@ export const SetRangeValuesMutation: IMutation { - if (cellMatrix.getValue(r, c)) { - cellMatrix.setValue(r, c, { v: null }); - } - }); + // createRowColIter(startRow, endRow, startColumn, endColumn).forEach((r, c) => { + // if (cellMatrix.getValue(r, c)) { + // cellMatrix.setValue(r, c, { v: null }); + // } + // }); newValues.forValue((row, col, newVal) => { const oldVal = cellMatrix.getValue(row, col) || {}; @@ -91,23 +81,29 @@ export const SetRangeValuesMutation: IMutation { - return this.getActiveRange()?.getTextStyle(); - } - - setSelectionValue(value: IDocumentData | string) { - const range = this.getActiveRange(); - if (!range) return; - - if (typeof value === 'string') { - // range.setValue(value); - } - if (typeof value === 'object') { - // range.setRangeData({ p: value }); - } - } - - getActiveRange() { - const cellRange = this._selectionManagerService.getLast()?.cellRange; - if (!cellRange) return; - let { row, column } = cellRange; - - if (cellRange.isMerged) { - row = cellRange.startRow; - column = cellRange.startColumn; - } - - return this._currentUniverService.getCurrentUniverSheetInstance().getWorkBook().getActiveSheet().getRange(row, column); - } - - handleBackSpace() {} - - handleDirection(direction: Direction) { - // todo - } -} diff --git a/packages/base-sheets/src/Controller/SheetContainerController.ts b/packages/base-sheets/src/Controller/SheetContainerController.ts index 25aa3c37af..361702337f 100644 --- a/packages/base-sheets/src/Controller/SheetContainerController.ts +++ b/packages/base-sheets/src/Controller/SheetContainerController.ts @@ -24,13 +24,11 @@ export class SheetContainerController extends Disposable { private _initialize() { this.disposeWithMe( this._commandService.onCommandExecuted((command: ICommandInfo) => { - // refresh selection and worksheet canvas view if (updateCommandList.includes(command.id)) { const workbook = this._currentUniverService.getCurrentUniverSheetInstance().getWorkBook(); const unitId = workbook.getUnitId(); - const worksheet = this._currentUniverService.getCurrentUniverSheetInstance().getWorkBook().getActiveSheet(); + const worksheet = workbook.getActiveSheet(); const sheetId = worksheet.getSheetId(); - // this._selectionManager.updateToSheet(worksheet); this._selectionManagerService.setCurrentSelection({ pluginName: NORMAL_SELECTION_PLUGIN_NAME, diff --git a/packages/base-sheets/src/Controller/index.ts b/packages/base-sheets/src/Controller/index.ts index 5205d75bf4..bd374877e4 100644 --- a/packages/base-sheets/src/Controller/index.ts +++ b/packages/base-sheets/src/Controller/index.ts @@ -1,9 +1,5 @@ -// @index(['./*.ts','./**/index.ts'], f => `export * from '${f.path}'`) export * from './BasicWorkbook.controller'; export * from './BasicWorksheet.controller'; -export * from './CellEditorController'; export * from './CountBarController'; -// export * from './EditTooltipsController'; export * from './SheetBarController'; export * from './SheetContainerController'; -// @endindex diff --git a/packages/base-sheets/src/SheetPlugin.ts b/packages/base-sheets/src/SheetPlugin.ts index 87ecf87e0f..36f5a70e07 100644 --- a/packages/base-sheets/src/SheetPlugin.ts +++ b/packages/base-sheets/src/SheetPlugin.ts @@ -1,10 +1,11 @@ -import { Engine, IRenderingEngine, ISelectionTransformerShapeManager, SelectionTransformerShapeManager } from '@univerjs/base-render'; -import { DEFAULT_SELECTION, ICurrentUniverService, LocaleService, Plugin, PLUGIN_NAMES, PluginType } from '@univerjs/core'; +import { ISelectionTransformerShapeManager, SelectionTransformerShapeManager } from '@univerjs/base-render'; +import { DEFAULT_SELECTION, ICommandService, ICurrentUniverService, LocaleService, Plugin, PLUGIN_NAMES, PluginType } from '@univerjs/core'; import { Dependency, Inject, Injector } from '@wendellhu/redi'; import { DEFAULT_SPREADSHEET_PLUGIN_DATA, install, ISheetPluginConfig } from './Basics'; import { SheetPluginObserve, uninstall } from './Basics/Observer'; -import { BasicWorkbookController, CellEditorController, CountBarController, SheetBarController, SheetContainerController } from './Controller'; +import { SetSelectionsOperation } from './Commands/Operations/selection.operation'; +import { BasicWorkbookController, CountBarController, SheetBarController, SheetContainerController } from './Controller'; import { BasicWorksheetController } from './Controller/BasicWorksheet.controller'; import { FormulaBarController } from './Controller/FormulaBarController'; import { en, zh } from './Locale'; @@ -20,25 +21,18 @@ export class SheetPlugin extends Plugin { private _config: ISheetPluginConfig; - private _canvasEngine: Engine; - - // private _editTooltipsController: EditTooltipsController; - private _formulaBarController: FormulaBarController; private _sheetBarController: SheetBarController; - // private _cellEditorController: CellEditorController; - private _countBarController: CountBarController; - // private _hideColumnController: HideColumnController; - private _sheetContainerController: SheetContainerController; constructor( config: Partial, @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, + @ICommandService private readonly _commandService: ICommandService, @Inject(LocaleService) private readonly _localeService: LocaleService, @Inject(Injector) override readonly _injector: Injector ) { @@ -47,6 +41,7 @@ export class SheetPlugin extends Plugin { this._config = Object.assign(DEFAULT_SPREADSHEET_PLUGIN_DATA, config); this._initializeDependencies(_injector); + this._initializeCommands(); } initialize(): void { @@ -59,7 +54,6 @@ export class SheetPlugin extends Plugin { this.initConfig(); this.initController(); - this.initCanvasView(); this.listenEventManager(); } @@ -81,23 +75,16 @@ export class SheetPlugin extends Plugin { } } - // TODO@huwenzhao: We don't need to init controllers manually initController() { this._sheetContainerController = this._injector.get(SheetContainerController); this._formulaBarController = this._injector.get(FormulaBarController); - // this._editTooltipsController = this._injector.get(EditTooltipsController); this._sheetBarController = this._injector.get(SheetBarController); this._countBarController = this._injector.get(CountBarController); - // this._hideColumnController = this._injector.get(HideColumnController); this._injector.get(BasicWorksheetController); this._injector.get(BasicWorkbookController); } - initCanvasView() { - this._canvasEngine = this._injector.get(IRenderingEngine); - } - override onMounted(): void { this.initialize(); } @@ -114,52 +101,35 @@ export class SheetPlugin extends Plugin { this._sheetBarController.listenEventManager(); } - /** @deprecated move to DI system */ - // getSelectionManager() { - // return this._injector.get(ISelectionManager); - // } - private _initializeDependencies(sheetInjector: Injector) { const dependencies: Dependency[] = [ [CanvasView], - [CellEditorController], - [SheetContainerController], - [FormulaBarController], - // [EditTooltipsController], - [SheetBarController], - [CountBarController], - - // TODO@huwenzhao: this is a temporary solution - // [ - // ISelectionManager, - // { - // useFactory: (injector: Injector, canvasView: CanvasView) => injector.createInstance(SelectionManager, canvasView.getSheetView(), this._config), - // deps: [Injector, CanvasView], - // }, - // ], - // [RowTitleController], - // [ColumnTitleController], - // [DragLineController], - // [HideColumnController], - - [BasicWorksheetController], - [BasicWorkbookController], - + // services [BorderStyleManagerService], - [SelectionManagerService], - [ ISelectionTransformerShapeManager, { useClass: SelectionTransformerShapeManager, }, ], + + // controllers + [SheetContainerController], + [FormulaBarController], + [SheetBarController], + [CountBarController], + [BasicWorksheetController], + [BasicWorkbookController], ]; dependencies.forEach((d) => { sheetInjector.add(d); }); } + + private _initializeCommands(): void { + this._commandService.registerCommand(SetSelectionsOperation); + } } diff --git a/packages/base-sheets/src/View/BaseView.ts b/packages/base-sheets/src/View/BaseView.ts index f7987463cb..6ef0c73ba6 100644 --- a/packages/base-sheets/src/View/BaseView.ts +++ b/packages/base-sheets/src/View/BaseView.ts @@ -36,6 +36,7 @@ export enum CANVAS_VIEW_KEY { VIEW_LEFT_TOP = 'viewLeftTop', SHEET_VIEW = 'sheetView', DRAG_LINE_VIEW = 'dragLineView', + CELL_EDITOR = 'cellEditor', SELECTION_VIEW = 'selectionView', } diff --git a/packages/base-sheets/src/View/CanvasView.ts b/packages/base-sheets/src/View/CanvasView.ts index 04f6cbd77e..be46ab533c 100644 --- a/packages/base-sheets/src/View/CanvasView.ts +++ b/packages/base-sheets/src/View/CanvasView.ts @@ -53,11 +53,14 @@ export class CanvasView { const rowTitle = config.rowTitle; const columnTitle = config.columnTitle; + // How do we know if a business should claim itself as main scene? const scene = new Scene(CANVAS_VIEW_KEY.MAIN_SCENE, engine, { width: 1500, height: 1000, }); + scene.openTransformer(); + this._scene = scene; const viewMain = new Viewport(CANVAS_VIEW_KEY.VIEW_MAIN, scene, { left: rowTitle.width, @@ -89,6 +92,7 @@ export class CanvasView { }); // viewMain.linkToViewport(viewLeft, LINK_VIEW_PORT_TYPE.Y); // viewMain.linkToViewport(viewTop, LINK_VIEW_PORT_TYPE.X); + // syncing scroll on the main area to headerbars viewMain.onScrollAfterObserver.add((param: IScrollObserverParam) => { const { scrollX, scrollY, actualScrollX, actualScrollY } = param; @@ -129,6 +133,7 @@ export class CanvasView { const currentRatio = sheet.getZoomRatio(); let nextRatio = +parseFloat(`${currentRatio + ratioDelta}`).toFixed(1); nextRatio = nextRatio >= 4 ? 4 : nextRatio <= 0.1 ? 0.1 : nextRatio; + // sheet.setZoomRatio(nextRatio); e.preventDefault(); @@ -145,11 +150,6 @@ export class CanvasView { engine.runRenderLoop(() => { scene.render(); - - const app = document.getElementById('app'); - if (app) { - app.innerText = `fps:${Math.round(engine.getFps()).toString()}`; - } }); } diff --git a/packages/base-sheets/src/View/Views/SelectionView.ts b/packages/base-sheets/src/View/Views/SelectionView.ts index 5993b61a68..f28d87f3c4 100644 --- a/packages/base-sheets/src/View/Views/SelectionView.ts +++ b/packages/base-sheets/src/View/Views/SelectionView.ts @@ -9,9 +9,10 @@ import { SpreadsheetColumnTitle, SpreadsheetRowTitle, } from '@univerjs/base-render'; -import { ICurrentUniverService, ISelectionRange, LocaleService, ObserverManager, Worksheet } from '@univerjs/core'; +import { ICommandService, ICurrentUniverService, ISelectionRange, LocaleService, ObserverManager, Worksheet } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; +import { SetSelectionsOperation } from '../../Commands/Operations/selection.operation'; import { NORMAL_SELECTION_PLUGIN_NAME, SelectionManagerService } from '../../Services/selection-manager.service'; import { BaseView, CANVAS_VIEW_KEY, CanvasViewRegistry, SHEET_VIEW_KEY } from '../BaseView'; @@ -21,6 +22,7 @@ export class SelectionView extends BaseView { constructor( @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, @ISelectionTransformerShapeManager private readonly _selectionTransformerShapeManager: ISelectionTransformerShapeManager, + @ICommandService private readonly _commandService: ICommandService, @Inject(SelectionManagerService) private readonly _selectionManagerService: SelectionManagerService, @Inject(ObserverManager) private readonly _observerManager: ObserverManager, @Inject(LocaleService) private readonly _localeService: LocaleService @@ -69,7 +71,16 @@ export class SelectionView extends BaseView { spreadsheetLeftTopPlaceholder?.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent) => {}); this._selectionTransformerShapeManager.selectionRangeWithStyle$.subscribe((selectionDataWithStyleList) => { - this._selectionManagerService.replaceWithNoRefresh(selectionDataWithStyleList.map((selectionDataWithStyle) => convertSelectionDataToRange(selectionDataWithStyle))); + const unitId = workbook.getUnitId(); + const sheetId = worksheet.getSheetId(); + + this._commandService.executeCommand(SetSelectionsOperation.id, { + unitId, + sheetId, + pluginName: NORMAL_SELECTION_PLUGIN_NAME, + selections: selectionDataWithStyleList.map((selectionDataWithStyle) => convertSelectionDataToRange(selectionDataWithStyle)), + }); + const current = selectionDataWithStyleList[selectionDataWithStyleList.length - 1]; if (current == null) { return; diff --git a/packages/base-sheets/src/index.ts b/packages/base-sheets/src/index.ts index a6eae71919..c8e55148cf 100644 --- a/packages/base-sheets/src/index.ts +++ b/packages/base-sheets/src/index.ts @@ -81,6 +81,7 @@ export { export { CopySheetCommand } from './Commands/Commands/copy-worksheet.command'; export { RemoveSheetCommand } from './Commands/Commands/remove-sheet.command'; export { RemoveWorksheetMergeCommand } from './Commands/Commands/remove-worksheet-merge.command'; +export { ISetRangeValuesCommandParams, SetRangeValuesCommand } from './Commands/Commands/set-range-values.command'; export { SetTabColorCommand } from './Commands/Commands/set-tab-color.command'; export { SetWorksheetHideCommand } from './Commands/Commands/set-worksheet-hide.command'; export { SetWorksheetNameCommand } from './Commands/Commands/set-worksheet-name.command'; diff --git a/packages/base-slides/src/View/Render/Adaptors/DocsAdaptor.ts b/packages/base-slides/src/View/Render/Adaptors/DocsAdaptor.ts index bdf50645b3..dda35fb3fa 100644 --- a/packages/base-slides/src/View/Render/Adaptors/DocsAdaptor.ts +++ b/packages/base-slides/src/View/Render/Adaptors/DocsAdaptor.ts @@ -15,7 +15,8 @@ import { } from '@univerjs/base-render'; import { DocumentModel, EventState, IPageElement, LocaleService, PageElementType } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; -import { ObjectAdaptor, CanvasObjectProviderRegistry } from '../Adaptor'; + +import { CanvasObjectProviderRegistry, ObjectAdaptor } from '../Adaptor'; export enum DOCS_VIEW_KEY { MAIN = '__DocsRender__', @@ -42,6 +43,7 @@ export class DocsAdaptor extends ObjectAdaptor { return this; } + // eslint-disable-next-line max-lines-per-function override convert(pageElement: IPageElement, mainScene: Scene) { const { id, zIndex, left = 0, top = 0, width, height, angle, scaleX, scaleY, skewX, skewY, flipX, flipY, title, description, document: documentData } = pageElement; if (documentData == null) { diff --git a/packages/base-slides/src/View/Render/Adaptors/RichTextAdaptor.ts b/packages/base-slides/src/View/Render/Adaptors/RichTextAdaptor.ts index ca629fbe1f..dd96fabbef 100644 --- a/packages/base-slides/src/View/Render/Adaptors/RichTextAdaptor.ts +++ b/packages/base-slides/src/View/Render/Adaptors/RichTextAdaptor.ts @@ -1,8 +1,8 @@ import { IRichTextProps, RichText, Scene } from '@univerjs/base-render'; import { IPageElement, LocaleService, PageElementType } from '@univerjs/core'; - import { Inject, Injector } from '@wendellhu/redi'; -import { ObjectAdaptor, CanvasObjectProviderRegistry } from '../Adaptor'; + +import { CanvasObjectProviderRegistry, ObjectAdaptor } from '../Adaptor'; export class RichTextAdaptor extends ObjectAdaptor { override zIndex = 2; diff --git a/packages/base-slides/src/View/Render/Adaptors/SlideAdaptor.ts b/packages/base-slides/src/View/Render/Adaptors/SlideAdaptor.ts index 8acba63f03..b95234e764 100644 --- a/packages/base-slides/src/View/Render/Adaptors/SlideAdaptor.ts +++ b/packages/base-slides/src/View/Render/Adaptors/SlideAdaptor.ts @@ -1,8 +1,8 @@ import { Engine, Rect, Scene, Slide, Viewport } from '@univerjs/base-render'; -import { SlideModel, getColorStyle, IColorStyle, IPageElement, ISlidePage, PageElementType } from '@univerjs/core'; - +import { getColorStyle, IColorStyle, IPageElement, ISlidePage, PageElementType, SlideModel } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; -import { ObjectAdaptor, CanvasObjectProviderRegistry } from '../Adaptor'; + +import { CanvasObjectProviderRegistry, ObjectAdaptor } from '../Adaptor'; import { ObjectProvider } from '../ObjectProvider'; // import { DocsAdaptor, ImageAdaptor, RichTextAdaptor, ShapeAdaptor } from './'; diff --git a/packages/base-slides/src/View/Render/Adaptors/SpreadsheetAdaptor.ts b/packages/base-slides/src/View/Render/Adaptors/SpreadsheetAdaptor.ts index 20793f0b18..c508429e48 100644 --- a/packages/base-slides/src/View/Render/Adaptors/SpreadsheetAdaptor.ts +++ b/packages/base-slides/src/View/Render/Adaptors/SpreadsheetAdaptor.ts @@ -13,9 +13,10 @@ import { SpreadsheetSkeleton, Viewport, } from '@univerjs/base-render'; -import { EventState, ICellData, IPageElement, ObjectMatrix, PageElementType, Styles, LocaleService } from '@univerjs/core'; +import { EventState, ICellData, IPageElement, LocaleService, ObjectMatrix, PageElementType, Styles } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; -import { ObjectAdaptor, CanvasObjectProviderRegistry } from '../Adaptor'; + +import { CanvasObjectProviderRegistry, ObjectAdaptor } from '../Adaptor'; enum SHEET_VIEW_KEY { MAIN = 'spreadInSlide', diff --git a/packages/base-slides/src/View/Render/CanvasView.ts b/packages/base-slides/src/View/Render/CanvasView.ts index 5d35631f92..a044267634 100644 --- a/packages/base-slides/src/View/Render/CanvasView.ts +++ b/packages/base-slides/src/View/Render/CanvasView.ts @@ -289,12 +289,11 @@ export class CanvasView { const { pageElements, pageBackgroundFill } = page; + // SceneViewers const objects = this._ObjectProvider.convertToRenderObjects(pageElements, this._scene); - scene.openTransformer(); - this._addBackgroundRect(scene, pageBackgroundFill); - + // So finally SceneViewers are added to the scene as objects. How can we do optimizations on this? scene.addObjects(objects); const transformer = scene.getTransformer(); diff --git a/packages/base-ui/package.json b/packages/base-ui/package.json index 409047e9a6..a76c55f8b4 100644 --- a/packages/base-ui/package.json +++ b/packages/base-ui/package.json @@ -45,17 +45,21 @@ "@storybook/addon-interactions": "^7.4.1", "@storybook/addon-links": "^7.4.1", "@storybook/blocks": "^7.4.1", - "@storybook/react": "^7.4.1", "@storybook/react-vite": "^7.4.1", + "@storybook/react": "^7.4.1", "@storybook/testing-library": "^0.2.0", "@testing-library/react": "^14.0.0", "@types/jest": "^29.5.4", "@types/node": "^18.14.0", - "@types/react": "^18.2.21", "@types/react-dom": "^18.2.7", - "jest": "29.6.4", + "@types/react": "^18.2.21", + "debug": "^4.3.3", + "enzyme": "^3.11.0", + "identity-obj-proxy": "^3.0.0", "jest-css-modules-transform": "^4.4.2", + "jest-dev-server": "^6.1.1", "jest-environment-jsdom": "^29.6.4", + "jest": "29.6.4", "less": "^4.1.1", "storybook": "^7.4.1", "ts-jest": "29.1.1", diff --git a/packages/base-ui/src/BaseComponent.ts b/packages/base-ui/src/BaseComponent.ts index bb5979749a..da73392bf1 100644 --- a/packages/base-ui/src/BaseComponent.ts +++ b/packages/base-ui/src/BaseComponent.ts @@ -1,11 +1,4 @@ -import { Context } from '@univerjs/core/src/Basics/Context'; - -// TODO Button const enum; BaseComponentSheet => BaseComponentXXX - export interface BaseComponentProps { getComponent?: (ref: any) => void; //获取自身组件 id?: string; // 组件id - - /** @deprecated */ - context?: Context; } diff --git a/packages/base-ui/src/Basics/Register/CellEditRegister/CellEditExtensionManager.ts b/packages/base-ui/src/Basics/Register/CellEditRegister/CellEditExtensionManager.ts index 33a4983ee3..fda45cab77 100644 --- a/packages/base-ui/src/Basics/Register/CellEditRegister/CellEditExtensionManager.ts +++ b/packages/base-ui/src/Basics/Register/CellEditRegister/CellEditExtensionManager.ts @@ -1,57 +1,58 @@ -import { Nullable } from '@univerjs/core'; -import { ICell } from '../../Interfaces/Cell'; -import { BaseCellEditExtension, BaseCellEditExtensionFactory } from './CellEditExtensionFactory'; -import { CellEditExtensionRegister } from './CellEditExtensionRegister'; - -export class CellEditExtensionManager { - static register: CellEditExtensionRegister; - - private _cellEditExtensionFactoryList: BaseCellEditExtensionFactory[]; - - static create(): CellEditExtensionRegister { - if (!this.register) { - this.register = new CellEditExtensionRegister(); - this.register.initialize(); - } - - return this.register; - } - - /** - * inject cell position - * @param row - * @param column - * @param value - */ - handle(cell: ICell): Nullable { - const cellEditExtensionFactoryList = CellEditExtensionManager?.register?.cellEditExtensionFactoryList; - if (!cellEditExtensionFactoryList) return; - // get the sorted list - // get the dynamically added list - this._cellEditExtensionFactoryList = cellEditExtensionFactoryList; - const extension = this._checkExtension(cell); - if (extension) { - extension.execute(); - return extension.getCell(); - } - } - - /** - * Execute when the action is matched - * @param command - * @returns - */ - private _checkExtension(cell: ICell): BaseCellEditExtension | false { - let extension: BaseCellEditExtension | false = false; - if (!this._cellEditExtensionFactoryList) return false; - - for (let index = 0; index < this._cellEditExtensionFactoryList.length; index++) { - const extensionFactory = this._cellEditExtensionFactoryList[index]; - extension = extensionFactory.check(cell); - if (extension !== false) { - break; - } - } - return extension; - } -} +import { Nullable } from '@univerjs/core'; + +import { ICell } from '../../Interfaces/Cell'; +import { BaseCellEditExtension, BaseCellEditExtensionFactory } from './CellEditExtensionFactory'; +import { CellEditExtensionRegister } from './CellEditExtensionRegister'; + +export class CellEditExtensionManager { + static register: CellEditExtensionRegister; + + private _cellEditExtensionFactoryList: BaseCellEditExtensionFactory[]; + + static create(): CellEditExtensionRegister { + if (!this.register) { + this.register = new CellEditExtensionRegister(); + this.register.initialize(); + } + + return this.register; + } + + /** + * inject cell position + * @param row + * @param column + * @param value + */ + handle(cell: ICell): Nullable { + const cellEditExtensionFactoryList = CellEditExtensionManager?.register?.cellEditExtensionFactoryList; + if (!cellEditExtensionFactoryList) return; + // get the sorted list + // get the dynamically added list + this._cellEditExtensionFactoryList = cellEditExtensionFactoryList; + const extension = this._checkExtension(cell); + if (extension) { + extension.execute(); + return extension.getCell(); + } + } + + /** + * Execute when the action is matched + * @param command + * @returns + */ + private _checkExtension(cell: ICell): BaseCellEditExtension | false { + let extension: BaseCellEditExtension | false = false; + if (!this._cellEditExtensionFactoryList) return false; + + for (let index = 0; index < this._cellEditExtensionFactoryList.length; index++) { + const extensionFactory = this._cellEditExtensionFactoryList[index]; + extension = extensionFactory.check(cell); + if (extension !== false) { + break; + } + } + return extension; + } +} diff --git a/packages/base-ui/src/Common/AppContext.tsx b/packages/base-ui/src/Common/AppContext.tsx index 317bab62ae..2b6b59fd21 100644 --- a/packages/base-ui/src/Common/AppContext.tsx +++ b/packages/base-ui/src/Common/AppContext.tsx @@ -2,8 +2,8 @@ * Use createContext to provide global language and skin settings */ import { LocaleService, ObserverManager } from '@univerjs/core'; -import { createContext } from 'react'; import { Injector } from '@wendellhu/redi'; +import { createContext } from 'react'; import { LocaleType } from '../Enum'; import { ComponentManager } from './ComponentManager'; @@ -18,6 +18,4 @@ export type AppContextValues = { observerManager: ObserverManager; }; -const AppContext = createContext>({}); -export { AppContext }; -export default AppContext; +export const AppContext = createContext>({}); diff --git a/packages/base-ui/src/Common/ComponentManager.ts b/packages/base-ui/src/Common/ComponentManager.ts index 2521762ab5..068d82914b 100644 --- a/packages/base-ui/src/Common/ComponentManager.ts +++ b/packages/base-ui/src/Common/ComponentManager.ts @@ -26,6 +26,7 @@ import { FillColorIcon, ForwardIcon, FullBorderIcon, + FxIcon, InnerBorderIcon, ItalicIcon, LeftAlignIcon, @@ -48,7 +49,6 @@ import { TopVerticalIcon, UnderLineIcon, VerticalBorderIcon, - FxIcon, } from '../Components/Icon'; import { Item } from '../Components/Item/Item'; diff --git a/packages/base-ui/src/Common/KeyboardManager.ts b/packages/base-ui/src/Common/KeyboardManager.ts deleted file mode 100644 index 9c347340df..0000000000 --- a/packages/base-ui/src/Common/KeyboardManager.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { Inject } from '@wendellhu/redi'; -import { Observable, ObserverManager } from '@univerjs/core'; - -export type KeyboardObserver = { - onKeyDownObservable: Observable; - onKeyUpObservable: Observable; - onKeyCopyObservable: Observable; - onKeyPasteObservable: Observable; - onKeyCutObservable: Observable; - onKeyCompositionStartObservable: Observable; - onKeyCompositionUpdateObservable: Observable; - onKeyCompositionEndObservable: Observable; -}; - -export class KeyboardManager { - constructor(@Inject(ObserverManager) private readonly _observerManager: ObserverManager) { - this._installObserver(); - } - - /** - * init keyboard listener - * - * add to docs/slides/ - */ - handleKeyboardAction(element: HTMLElement) { - const keyboardDownEvent = (evt: KeyboardEvent) => { - this._observerManager.requiredObserver('onKeyDownObservable', 'core')?.notifyObservers(evt); - }; - - const keyboardUpEvent = (evt: KeyboardEvent) => { - this._observerManager.requiredObserver('onKeyUpObservable', 'core')?.notifyObservers(evt); - }; - - // Maybe move to Paste.ts - const keyboardCopyEvent = (evt: ClipboardEvent) => { - this._observerManager.requiredObserver('onKeyCopyObservable', 'core')?.notifyObservers(evt); - }; - - const keyboardPasteEvent = (evt: ClipboardEvent) => { - this._observerManager.requiredObserver('onKeyPasteObservable', 'core')?.notifyObservers(evt); - }; - const keyboardCutEvent = (evt: ClipboardEvent) => { - this._observerManager.requiredObserver('onKeyCutObservable', 'core')?.notifyObservers(evt); - }; - - const keyboardCompositionStartEvent = (evt: CompositionEvent) => { - this._observerManager.requiredObserver('onKeyCompositionStartObservable', 'core')?.notifyObservers(evt); - }; - const keyboardCompositionUpdateEvent = (evt: CompositionEvent) => { - this._observerManager.requiredObserver('onKeyCompositionUpdateObservable', 'core')?.notifyObservers(evt); - }; - const keyboardCompositionEndEvent = (evt: CompositionEvent) => { - this._observerManager.requiredObserver('onKeyCompositionEndObservable', 'core')?.notifyObservers(evt); - }; - - element.addEventListener('keydown', keyboardDownEvent); - element.addEventListener('keyup', keyboardUpEvent); - element.addEventListener('copy', keyboardCopyEvent); - element.addEventListener('paste', keyboardPasteEvent); - element.addEventListener('cut', keyboardCutEvent); - element.addEventListener('compositionstart', keyboardCompositionStartEvent); - element.addEventListener('compositionupdate', keyboardCompositionUpdateEvent); - element.addEventListener('compositionend', keyboardCompositionEndEvent); - } - - private _installObserver() { - // keyboard - this._observerManager.addObserver('onKeyDownObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyUpObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyCopyObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyPasteObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyCutObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyCompositionStartObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyCompositionUpdateObservable', 'core', new Observable()); - this._observerManager.addObserver('onKeyCompositionEndObservable', 'core', new Observable()); - } -} diff --git a/packages/base-ui/src/Common/index.ts b/packages/base-ui/src/Common/index.ts index 76d92a6cd5..e0e39572cd 100644 --- a/packages/base-ui/src/Common/index.ts +++ b/packages/base-ui/src/Common/index.ts @@ -1,6 +1,5 @@ export * from './AppContext'; export * from './ComponentManager'; export * from './DragManager'; -export * from './KeyboardManager'; export * from './SlotManager'; export * from './ZIndexManager'; diff --git a/packages/base-ui/src/Components/README.md b/packages/base-ui/src/Components/README.md new file mode 100644 index 0000000000..d64340bfcd --- /dev/null +++ b/packages/base-ui/src/Components/README.md @@ -0,0 +1,3 @@ +TODO + +- [ ] This whole folder should be moved to views/components diff --git a/packages/base-ui/src/UIPlugin.ts b/packages/base-ui/src/UIPlugin.ts new file mode 100644 index 0000000000..84edba4b11 --- /dev/null +++ b/packages/base-ui/src/UIPlugin.ts @@ -0,0 +1,63 @@ +import { Plugin, PLUGIN_NAMES, PluginType } from '@univerjs/core'; +import { Dependency, Inject, Injector } from '@wendellhu/redi'; + +import { ComponentManager } from './Common/ComponentManager'; +import { ZIndexManager } from './Common/ZIndexManager'; +import { SharedController } from './controllers/shared-shortcut.controller'; +import { IUIController, IWorkbenchOptions } from './controllers/ui/ui.controller'; +import { DesktopUIController } from './controllers/ui/ui-desktop.controller'; +import { ContextService, IContextService } from './services/context/context.service'; +import { DesktopContextMenuService, IContextMenuService } from './services/contextmenu/contextmenu.service'; +import { DesktopMenuService, IMenuService } from './services/menu/menu.service'; +import { DesktopPlatformService, IPlatformService } from './services/platform/platform.service'; +import { DesktopShortcutService, IShortcutService } from './services/shortcut/shortcut.service'; + +export interface IUIPluginConfig extends IWorkbenchOptions {} + +const DEFAULT_SLIDE_PLUGIN_DATA = {}; + +/** + * UI plugin provides basic interaction with users. Including workbench (menus, UI parts, notifications etc.), copy paste, shortcut. + */ +export class UIPlugin extends Plugin { + static override type = PluginType.Univer; + + private _config: IUIPluginConfig; + + constructor(config: Partial = {}, @Inject(Injector) injector: Injector) { + super(PLUGIN_NAMES.BASE_UI); + + this._config = Object.assign(DEFAULT_SLIDE_PLUGIN_DATA, config); + this._injector = injector; + } + + override onMounted(): void { + this.initDependencies(); + this.initUI(); + } + + private initDependencies(): void { + const dependencies: Dependency[] = [ + // legacy managers - deprecated + [ComponentManager], + [ZIndexManager], + + // services + [IShortcutService, { useClass: DesktopShortcutService }], + [IPlatformService, { useClass: DesktopPlatformService }], + [IMenuService, { useClass: DesktopMenuService }], + [IContextService, { useClass: ContextService }], + [IContextMenuService, { useClass: DesktopContextMenuService }], + + // controllers + [SharedController], + [IUIController, { useClass: DesktopUIController }], + ]; + + dependencies.forEach((dependency) => this._injector.add(dependency)); + } + + private initUI(): void { + this._injector.get(IUIController).bootstrapWorkbench(this._config); + } +} diff --git a/packages/base-ui/src/controllers/contextmenu/contextmenu.controller.ts b/packages/base-ui/src/controllers/contextmenu/contextmenu.controller.ts new file mode 100644 index 0000000000..988bfc3019 --- /dev/null +++ b/packages/base-ui/src/controllers/contextmenu/contextmenu.controller.ts @@ -0,0 +1 @@ +export class \ No newline at end of file diff --git a/packages/base-ui/src/controllers/shared-shortcut.controller.ts b/packages/base-ui/src/controllers/shared-shortcut.controller.ts index b3fc46400b..0e6ade8e09 100644 --- a/packages/base-ui/src/controllers/shared-shortcut.controller.ts +++ b/packages/base-ui/src/controllers/shared-shortcut.controller.ts @@ -1,7 +1,7 @@ import { Disposable, ICommandService, RedoCommand, UndoCommand } from '@univerjs/core'; -import { IShortcutItem, IShortcutService } from '../services/shortcut/shortcut.service'; import { KeyCode, MetaKeys } from '../services/shortcut/keycode'; +import { IShortcutItem, IShortcutService } from '../services/shortcut/shortcut.service'; export const UndoShortcutItem: IShortcutItem = { id: UndoCommand.id, @@ -23,12 +23,6 @@ export class SharedController extends Disposable { this.registerShortcuts(); } - // private registerToolbarItem(): void {} - - // private registerContextMenuItem(): void {} - - // private registerFooterItem(): void {} - private registerShortcuts(): void { [UndoShortcutItem, RedoShortcutItem].forEach((shortcut) => { this.disposeWithMe(this._shortcutService.registerShortcut(shortcut)); diff --git a/packages/base-ui/src/controllers/ui/ui-desktop.controller.tsx b/packages/base-ui/src/controllers/ui/ui-desktop.controller.tsx new file mode 100644 index 0000000000..741de02f50 --- /dev/null +++ b/packages/base-ui/src/controllers/ui/ui-desktop.controller.tsx @@ -0,0 +1,58 @@ +import { Engine, IRenderingEngine } from '@univerjs/base-render'; +import { Disposable, toDisposable } from '@univerjs/core'; +import { Inject, Injector } from '@wendellhu/redi'; +import { connectInjector } from '@wendellhu/redi/react-bindings'; +import { createRoot } from 'react-dom/client'; + +import { App } from '../../views/app'; +import { IUIController, IWorkbenchOptions } from './ui.controller'; + +export class DesktopUIController extends Disposable implements IUIController { + constructor(@Inject(Injector) private readonly _injector: Injector, @IRenderingEngine private readonly _renderingEngine: Engine) { + super(); + } + + bootstrapWorkbench(options: IWorkbenchOptions): void { + this.disposeWithMe( + toDisposable( + bootStrap(this._injector, options, (element) => { + // set render container after UI is rendered + this._renderingEngine.setContainer(element); + }) + ) + ); + } +} + +function bootStrap(injector: Injector, options: IWorkbenchOptions, callback: (containerEl: HTMLElement) => void) { + let mountContainer: HTMLElement; + + const container = options.container; + if (typeof container === 'string') { + const containerElement = document.getElementById(container); + if (!containerElement) { + mountContainer = createContainer(container); + } else { + mountContainer = containerElement; + } + } else if (container instanceof HTMLElement) { + mountContainer = container; + } else { + mountContainer = createContainer('univer'); + } + + const root = createRoot(mountContainer); + const ConnectedApp = connectInjector(App, injector); + root.render(); + + return () => { + root.unmount(); + }; +} + +function createContainer(id: string): HTMLElement { + const element = document.createElement('div'); + element.id = id; + // FIXME: the element is not append to the DOM tree. So it won't be rendered. + return element; +} diff --git a/packages/base-ui/src/controllers/ui/ui.controller.ts b/packages/base-ui/src/controllers/ui/ui.controller.ts new file mode 100644 index 0000000000..51a438051e --- /dev/null +++ b/packages/base-ui/src/controllers/ui/ui.controller.ts @@ -0,0 +1,18 @@ +import { createIdentifier } from '@wendellhu/redi'; + +export interface IWorkbenchOptions { + container?: string | HTMLElement; + + outerLeft?: boolean; + innerLeft?: boolean; + header?: boolean; + infoBar?: boolean; + toolbar?: boolean; + footer?: boolean; +} + +export interface IUIController { + bootstrapWorkbench(options: IWorkbenchOptions): void; +} + +export const IUIController = createIdentifier('univer.ui-controller'); diff --git a/packages/base-ui/src/index.ts b/packages/base-ui/src/index.ts index 8a4a732793..8a6748efe0 100644 --- a/packages/base-ui/src/index.ts +++ b/packages/base-ui/src/index.ts @@ -5,6 +5,8 @@ export * from './Components'; export { SharedController } from './controllers/shared-shortcut.controller'; export * from './Enum'; export * from './Helpers'; +export { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE } from './services/context/context'; +export { ContextService, IContextService } from './services/context/context.service'; export { ICustomComponentOption, ICustomComponentProps, @@ -23,4 +25,5 @@ export { DesktopMenuService, IMenuService } from './services/menu/menu.service'; export { DesktopPlatformService, IPlatformService } from './services/platform/platform.service'; export { KeyCode, MetaKeys } from './services/shortcut/keycode'; export { DesktopShortcutService, IShortcutItem, IShortcutService } from './services/shortcut/shortcut.service'; +export { UIPlugin } from './UIPlugin'; export * from './Utils'; diff --git a/packages/base-ui/src/services/context/context.service.ts b/packages/base-ui/src/services/context/context.service.ts new file mode 100644 index 0000000000..873d92efbe --- /dev/null +++ b/packages/base-ui/src/services/context/context.service.ts @@ -0,0 +1,50 @@ +import { Disposable, fromObservable, ICurrentUniverService, UniverDoc, UniverSheet, UniverSlide } from '@univerjs/core'; +import { createIdentifier } from '@wendellhu/redi'; + +import { FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE } from './context'; + +export interface IContextService { + getContextValue(key: string): boolean; + setContextValue(key: string, value: boolean): void; + + // TODO: actually it should provide an `evaluate` method to evaluate the context value. An callback in shortcut service works but not looks nice. +} + +export const IContextService = createIdentifier('univer.context-service'); + +export class ContextService extends Disposable implements IContextService { + private readonly _contextMap = new Map(); + + constructor(@ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService) { + super(); + + this.disposeWithMe( + fromObservable( + this._currentUniverService.focused$.subscribe(() => { + this.handleFocusedUniverChange(); + }) + ) + ); + } + + getContextValue(key: string): boolean { + return this._contextMap.get(key) ?? false; + } + + setContextValue(key: string, value: boolean): void { + this._contextMap.set(key, value); + } + + private handleFocusedUniverChange() { + [FOCUSING_DOC, FOCUSING_SHEET, FOCUSING_SLIDE].forEach((k) => this.setContextValue(k, false)); + + const current = this._currentUniverService.getFocusedUniverInstance(); + if (current instanceof UniverSheet) { + this.setContextValue(FOCUSING_SHEET, true); + } else if (current instanceof UniverDoc) { + this.setContextValue(FOCUSING_DOC, true); + } else if (current instanceof UniverSlide) { + this.setContextValue(FOCUSING_SLIDE, true); + } + } +} diff --git a/packages/base-ui/src/services/context/context.ts b/packages/base-ui/src/services/context/context.ts new file mode 100644 index 0000000000..135aaafcce --- /dev/null +++ b/packages/base-ui/src/services/context/context.ts @@ -0,0 +1,3 @@ +export const FOCUSING_SHEET = 'FOCUSING_SHEET'; +export const FOCUSING_DOC = 'FOCUSING_DOC'; +export const FOCUSING_SLIDE = 'FOCUSING_SLIDE'; diff --git a/packages/base-ui/src/services/contextmenu/contextmenu.service.ts b/packages/base-ui/src/services/contextmenu/contextmenu.service.ts new file mode 100644 index 0000000000..299a4e313f --- /dev/null +++ b/packages/base-ui/src/services/contextmenu/contextmenu.service.ts @@ -0,0 +1,42 @@ +import { Engine, IMouseEvent, IPointerEvent, IRenderingEngine } from '@univerjs/base-render'; +import { Disposable, toDisposable } from '@univerjs/core'; +import { createIdentifier, IDisposable } from '@wendellhu/redi'; + +export interface IContextMenuHandler { + handleContextMenu(event: IPointerEvent | IMouseEvent): void; +} + +export interface IContextMenuService { + registerContextMenuHandler(handler: IContextMenuHandler): IDisposable; +} + +export const IContextMenuService = createIdentifier('univer.context-menu-service'); + +export class DesktopContextMenuService extends Disposable implements IContextMenuService { + private _currentHandler: IContextMenuHandler | null = null; + + constructor(@IRenderingEngine private readonly _renderingEngine: Engine) { + super(); + } + + registerContextMenuHandler(handler: IContextMenuHandler): IDisposable { + if (this._currentHandler) { + throw new Error('Cannot add context menu handler twice.'); + } + + this._currentHandler = handler; + const pointerDownOnMain = this._renderingEngine.getScene('mainScene')?.onPointerDownObserver; + const observer = pointerDownOnMain?.add((event: IPointerEvent | IMouseEvent) => { + // right click + if (event.button === 2) { + event.preventDefault(); + this._currentHandler?.handleContextMenu(event); + } + }); + + return toDisposable(() => { + this._currentHandler = null; + pointerDownOnMain?.remove(observer); + }); + } +} diff --git a/packages/base-ui/src/services/menu/menu.service.ts b/packages/base-ui/src/services/menu/menu.service.ts index 2712b8dbb9..d4c901cd5b 100644 --- a/packages/base-ui/src/services/menu/menu.service.ts +++ b/packages/base-ui/src/services/menu/menu.service.ts @@ -1,8 +1,9 @@ -import { IDisposable, createIdentifier } from '@wendellhu/redi'; import { Disposable, toDisposable } from '@univerjs/core'; -import { IShortcutService } from '../shortcut/shortcut.service'; -import { BaseSelectChildrenProps } from '../../Components/Select/Select'; +import { createIdentifier, IDisposable } from '@wendellhu/redi'; +// TODO@wzhudev: this props should be moved to menu.service.ts to break cycle import +import { BaseSelectChildrenProps } from '../../Components/Select/Select'; +import { IShortcutService } from '../shortcut/shortcut.service'; import { IDisplayMenuItem, IMenuItem, MenuPosition } from './menu'; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/base-ui/src/services/platform/platform.service.ts b/packages/base-ui/src/services/platform/platform.service.ts index afe860b307..d95eda9aeb 100644 --- a/packages/base-ui/src/services/platform/platform.service.ts +++ b/packages/base-ui/src/services/platform/platform.service.ts @@ -23,4 +23,4 @@ export class DesktopPlatformService implements IPlatformService { get isLinux(): boolean { return /Linux/.test(navigator.appVersion); } -} \ No newline at end of file +} diff --git a/packages/base-ui/src/services/shortcut/keycode.ts b/packages/base-ui/src/services/shortcut/keycode.ts index 5909b9a004..9f9139db85 100644 --- a/packages/base-ui/src/services/shortcut/keycode.ts +++ b/packages/base-ui/src/services/shortcut/keycode.ts @@ -5,9 +5,9 @@ export const enum KeyCode { UNKNOWN = 0, BACKSPACE = 8, + TAB = 9, DELETE = 12, ENTER = 13, - TAB = 17, ESC = 27, SPACE = 32, @@ -81,4 +81,4 @@ export const enum MetaKeys { /** Command key on MacOS. Ctrl key on other systems. */ CTRL_COMMAND = 1 << 12, MAC_CTRL = 1 << 13, -} \ No newline at end of file +} diff --git a/packages/base-ui/src/services/shortcut/shortcut.service.ts b/packages/base-ui/src/services/shortcut/shortcut.service.ts index 6ddc363ef6..1cec80b187 100644 --- a/packages/base-ui/src/services/shortcut/shortcut.service.ts +++ b/packages/base-ui/src/services/shortcut/shortcut.service.ts @@ -2,6 +2,7 @@ import { Disposable, ICommandService, toDisposable } from '@univerjs/core'; import { createIdentifier, IDisposable } from '@wendellhu/redi'; import { fromDocumentEvent } from '../../Common/lifecycle'; +import { IContextService } from '../context/context.service'; import { IPlatformService } from '../platform/platform.service'; import { MetaKeys } from './keycode'; @@ -10,7 +11,9 @@ export interface IShortcutItem

{ id: string; description?: string; - // TODO@wzhudev: add a precondition to check if the command should trigger + priority?: number; + /** A callback that will be triggered to examine if the shortcut should be invoked. */ + preconditions?: (contextService: Pick) => boolean; /** A command can be bound to several bindings, with different static parameters perhaps. */ binding: number; @@ -32,12 +35,15 @@ export const IShortcutService = createIdentifier('univer.short export class DesktopShortcutService extends Disposable implements IShortcutService { // TODO: @wzhudev: this should be a linked list to resolve different shortcut mapping to the same keybinding - - private readonly _shortCutMapping = new Map(); + private readonly _shortCutMapping = new Map>(); private readonly _idToShortcut = new Map(); - constructor(@ICommandService private readonly _commandService: ICommandService, @IPlatformService private readonly _platformService: IPlatformService) { + constructor( + @ICommandService private readonly _commandService: ICommandService, + @IPlatformService private readonly _platformService: IPlatformService, + @IContextService private readonly _contextService: IContextService + ) { super(); this.disposeWithMe( @@ -50,7 +56,13 @@ export class DesktopShortcutService extends Disposable implements IShortcutServi registerShortcut(shortcut: IShortcutItem): IDisposable { // first map shortcut to a number, so it could be converted and fetched quickly const binding = this.getBindingFromItem(shortcut); - this._shortCutMapping.set(binding, shortcut); + const existing = this._shortCutMapping.get(binding); + if (existing) { + existing.add(shortcut); + } else { + this._shortCutMapping.set(binding, new Set([shortcut])); + } + this._idToShortcut.set(shortcut.id, binding); return toDisposable(() => { @@ -89,14 +101,20 @@ export class DesktopShortcutService extends Disposable implements IShortcutServi return false; } - const shortcut = this._shortCutMapping.get(binding); - if (shortcut === undefined) { + const shortcuts = this._shortCutMapping.get(binding); + if (shortcuts === undefined) { return false; } - // shortcut could support static parameters - this._commandService.executeCommand(shortcut.id, shortcut.staticParameters); - return true; + const shouldTrigger = Array.from(shortcuts) + .sort((s1, s2) => (s1.priority ?? 0) - (s2.priority ?? 0)) + .find((s) => s.preconditions?.({ getContextValue: this._contextService.getContextValue.bind(this._contextService) }) ?? true); + if (shouldTrigger) { + this._commandService.executeCommand(shouldTrigger.id, shouldTrigger.staticParameters); + return true; + } + + return false; } private getBindingFromItem(item: IShortcutItem): number { diff --git a/packages/base-ui/src/views/app.module.less b/packages/base-ui/src/views/app.module.less new file mode 100644 index 0000000000..100caac0a5 --- /dev/null +++ b/packages/base-ui/src/views/app.module.less @@ -0,0 +1,77 @@ +.layout-container { + // theme + background-color: var(--bg); + color: var(--text-color); + height: 100%; + + .content-container-horizontal { + display: flex; + min-height: 14em; + position: relative; + + .content-inner-left-container { + // position: relative; + width: 50%; + padding-right: 10px; + flex: none; + + .hover-cursor { + position: absolute; + right: 0; + top: 0; + height: 100%; + width: 10px; + background: gray; + cursor: e-resize; + } + } + + .content-inner-right-container { + width: 50%; + flex: 1; + // position: relative; + } + } + + .content-container-vertical { + display: flex; + flex-direction: column; + min-height: 14em; + position: relative; + + .content-inner-left-container { + // position: relative; + height: 50%; + padding-bottom: 10px; + flex: none; + + .hover-cursor { + position: absolute; + left: 0; + bottom: 0; + height: 10px; + width: 100%; + background: gray; + cursor: n-resize; + } + } + + .content-inner-right-container { + height: 50%; + flex: 1; + } + } + + .main-content { + background: var(--bg); + } + + .outer-right-container { + // position: relative; + flex: 0 0 280px; + > div { + position: absolute; + width: 100%; + } + } +} diff --git a/packages/base-ui/src/views/app.tsx b/packages/base-ui/src/views/app.tsx new file mode 100644 index 0000000000..82a7e6a5f5 --- /dev/null +++ b/packages/base-ui/src/views/app.tsx @@ -0,0 +1,150 @@ +import '../Basics/CSS/Skin/default.module.less'; + +import { LocaleService, ObserverManager } from '@univerjs/core'; +import { useDependency, useInjector } from '@wendellhu/redi/react-bindings'; +import { useEffect, useRef } from 'react'; + +import { defaultSkin } from '../Basics/CSS'; +import { ComponentManager, ZIndexManager } from '../Common'; +import { AppContext } from '../Common/AppContext'; +import { Content, Footer, Header, Layout, Sider } from '../Components'; +import { Container } from '../Components/Container/Container'; +import { IWorkbenchOptions } from '../controllers/ui/ui.controller'; +import { LocaleType } from '../Enum'; +import { IContextMenuService } from '../services/contextmenu/contextmenu.service'; +import style from './app.module.less'; +import { ContextMenu } from './components/contextmenu/contextmenu'; +import { Toolbar } from './components/toolbar/toolbar'; + +export interface IUniverAppProps extends IWorkbenchOptions { + onRendered?: (container: HTMLElement) => void; +} + +// eslint-disable-next-line max-lines-per-function +export function App(props: IUniverAppProps) { + const injector = useInjector(); + const localeService = useDependency(LocaleService); + const observerManager = useDependency(ObserverManager); + const zIndexManager = useDependency(ZIndexManager); + const componentManager = useDependency(ComponentManager); + const contextMenuService = useDependency(IContextMenuService); + + const containerRef = useRef(null); + + useEffect(() => { + /** + * delete style rule in universheet-skin-style + * @param skinStyleSheet + * @param id + */ + function deleteStyleRuleIndexBySelector(skinStyleSheet: CSSStyleSheet, id: string) { + let index = 0; + for (let i = 0; i < skinStyleSheet.cssRules.length; i++) { + const rule = skinStyleSheet.cssRules[i]; + + if (rule instanceof CSSStyleRule && rule.selectorText === `#${id}`) { + index = i; + skinStyleSheet.deleteRule(index); + break; + } + } + } + + function getSkinStyleSheet(id: string) { + const title = 'universheet-skin-style'; + // avoid duplicates + for (let i = 0; i < document.styleSheets.length; i++) { + if (document.styleSheets[i].title === title) { + deleteStyleRuleIndexBySelector(document.styleSheets[i], id); + + return document.styleSheets[i]; + } + } + const head = document.head || document.getElementsByTagName('head')[0]; + const styleEle = document.createElement('style'); + styleEle.title = title; + head.appendChild(styleEle); + + return document.styleSheets[document.styleSheets.length - 1]; + } + + // set css variable + const id = containerRef.current!.id; + const sheet = getSkinStyleSheet(id); + + /** + * covert object to style, remove " and replace , to ; + * Example: + * before: {--primary-color:"#0188fb",--primary-color-hover:"#5391ff"} + * after: {--primary-color:#0188fb;--primary-color-hover:#5391ff;} + */ + let currentSkin = defaultSkin; + currentSkin = Object.fromEntries(Object.keys(defaultSkin).map((item) => [`--${item.replace(/([A-Z0-9])/g, '-$1').toLowerCase()}`, currentSkin[item]])); + sheet.insertRule( + `#${'univer-container'} ${JSON.stringify(currentSkin) + .replace(/"/g, '') + .replace(/,(?=--)/g, ';')}` + ); + }, []); + + useEffect(() => { + if (containerRef.current) { + props.onRendered?.(containerRef.current); + } + }, [props.onRendered]); + + return ( + + {/* TODO: UI here is not fine tuned */} +

+ {/* language selector */} + + Language + + +
+ + + {/* outer sidebar */} + + + {/* header */} +
{props.toolbar && }
+ {/* content */} + + {/* inner left */} + + {/* FIXME: context menu component shouldn't have to mount on this position */} + e.preventDefault()} className={style.contentInnerRightContainer} ref={containerRef}> + + {/* {config.rightMenu && } */} + {/* {} */} + + + + {/* footer */} +
+
+
+
+ + ); +} diff --git a/packages/base-docs/src/services/index.ts b/packages/base-ui/src/views/components/contextmenu/contextmenu-item.tsx similarity index 100% rename from packages/base-docs/src/services/index.ts rename to packages/base-ui/src/views/components/contextmenu/contextmenu-item.tsx diff --git a/packages/base-ui/src/views/components/contextmenu/contextmenu.module.less b/packages/base-ui/src/views/components/contextmenu/contextmenu.module.less new file mode 100644 index 0000000000..b3ec63c15c --- /dev/null +++ b/packages/base-ui/src/views/components/contextmenu/contextmenu.module.less @@ -0,0 +1,50 @@ +.context-menu { + position: fixed; + + left: 0; + top: 26px; + background-color: var(--bg); + box-shadow: var(--box-shadow-base); + z-index: 2; + + .cols-menu { + .cols-menuitem { + text-align: left; + padding: 6px 25px 6px 25px; + + input { + width: 40px; + height: 20px; + text-align: center; + margin: 0 3px; + padding: 0; + } + + button { + margin-left: 5px; + padding: 2px 3px; + line-height: 12px; + font-size: 12px; + } + + .btn { + height: auto; + } + + .right-menu-center { + min-width: auto; + } + } + } + + .right-menu-item { + position: relative; + + span { + position: absolute; + right: -15px; + top: 50%; + transform: translateY(-50%); + } + } +} diff --git a/packages/base-ui/src/views/components/contextmenu/contextmenu.tsx b/packages/base-ui/src/views/components/contextmenu/contextmenu.tsx new file mode 100644 index 0000000000..5056a1f007 --- /dev/null +++ b/packages/base-ui/src/views/components/contextmenu/contextmenu.tsx @@ -0,0 +1,144 @@ +import { IMouseEvent } from '@univerjs/base-render'; +import { RediContext, WithDependency } from '@wendellhu/redi/react-bindings'; +import { Component, createRef } from 'react'; + +import { Menu } from '../../../Components'; +import { BaseMenuItem } from '../../../Interfaces/Menu'; +import { IContextMenuService } from '../../../services/contextmenu/contextmenu.service'; +import { IDisplayMenuItem, IMenuItem, MenuPosition } from '../../../services/menu/menu'; +import styles from './contextmenu.module.less'; + +// WTF: props here is not correct +export interface IContextMenuProps extends BaseMenuItem {} + +export interface IContextMenuState { + visible: boolean; + srcElement?: HTMLElement; // FIXME: any? + eventType: string | null; + children: IContextMenuProps[]; + menuItems: Array>; +} + +export class ContextMenu extends Component { + static override contextType = RediContext; + + @WithDependency(IContextMenuService) + private declare contextMenuService: IContextMenuService; + + declare context: typeof RediContext; + + private menuRef = createRef(); + + private rootRef = createRef(); + + constructor(props: IContextMenuProps) { + super(props); + + this.state = { + visible: false, + srcElement: null, // WTF: this property is weird + eventType: null, // WTF: this property is weird + children: [] as IContextMenuProps[], + menuItems: [] as Array>, + }; + } + + override componentDidMount() { + document.addEventListener('click', this.handleClick); + + // TODO: dispose + this.contextMenuService.registerContextMenuHandler({ + handleContextMenu: this.handleContextMenu, + }); + } + + override componentWillUnmount(): void { + document.removeEventListener('click', this.handleClick); + } + + showContextMenu(show: boolean) { + this.menuRef.current?.showMenu(show); + } + + override render() { + const { visible } = this.state; + + return ( + visible && ( +
e.preventDefault()}> + +
+ ) + ); + } + + handleContextMenu = async (event: IMouseEvent, rect?: any, down?: boolean) => { + event.preventDefault(); + + // FIXME: srcElement is deprecated + // FIXME: contextmenu position algorithm is not correct + this.setState({ visible: true, srcElement: event.srcElement, eventType: event.type }, () => { + new Promise((resolve, reject) => { + this.menuRef.current?.showMenu(true); + resolve(); + }).then(() => { + // clientX/Y 获取到的是触发点相对于浏览器可视区域左上角距离 + const clickX = !down ? event.clientX : rect.x; + const clickY = !down ? event.clientY : rect.y; + + // window.innerWidth/innerHeight 获取的是当前浏览器窗口的视口宽度/高度 + const screenW = window.innerWidth; + const screenH = window.innerHeight; + // 获取自定义菜单的宽度/高度 + const currentUl = this.menuRef.current?.getMenuRef().current; + const rootW = currentUl?.offsetWidth || 0; + const rootH = currentUl?.offsetHeight || 0; + + // right为true,说明鼠标点击的位置到浏览器的右边界的宽度可以放下菜单。否则,菜单放到左边。 + // bottom为true,说明鼠标点击位置到浏览器的下边界的高度可以放下菜单。否则,菜单放到上边。 + const right = screenW - clickX > rootW; + const left = !right; + const bottom = screenH - clickY > rootH; + const top = !bottom; + + const current = this.rootRef.current; + if (!current) { + return; + } + + if (right) { + current.style.left = `${clickX}px`; + } + if (left) { + current.style.left = `${clickX - rootW}px`; + } + + if (bottom) { + current.style.top = `${clickY}px`; + } + if (top) { + current.style.top = `${clickY - rootH}px`; + } + }); + }); + }; + + private handleClick = (e: MouseEvent) => { + const { visible, eventType, srcElement } = this.state; + if (!this.rootRef.current) { + return; + } + + if (eventType === 'click' && srcElement && srcElement.contains(e.target)) { + return; + } + + if (this.rootRef.current.contains(e.target)) { + return; + } + + if (visible) { + this.setState({ visible: false }); + } + }; +} diff --git a/packages/base-ui/src/views/components/toolbar/data-bar.tsx b/packages/base-ui/src/views/components/toolbar/data-bar.tsx new file mode 100644 index 0000000000..4f000c29ce --- /dev/null +++ b/packages/base-ui/src/views/components/toolbar/data-bar.tsx @@ -0,0 +1,22 @@ +type BarProps = { + list: Array>; + img: string; + onClick?: (item: {}, index: number) => void; +}; + +export const DataBar = (props: BarProps) => { + const { list, img, onClick } = props; + const background = `url(${img}) no-repeat`; + + const handleClick = (item: {}, index: number) => { + onClick?.call(this, item, index); + }; + + return ( +
+ {list.map((item, index) => ( +
handleClick(item, index)} style={{ width: '28px', height: '26px', background, backgroundPosition: `${item.x} ${item.y}` }}>
+ ))} +
+ ); +}; diff --git a/packages/base-ui/src/views/components/toolbar/index.less b/packages/base-ui/src/views/components/toolbar/index.less new file mode 100644 index 0000000000..04f1f4201e --- /dev/null +++ b/packages/base-ui/src/views/components/toolbar/index.less @@ -0,0 +1,310 @@ +.universheet-wa-editor.toolbar { + height: 32px; + background: var(--bg); + position: relative; + padding: 5px 0 3px 15px; + border-bottom: 1px solid var(--bg); + white-space: nowrap; + transition: all 0.2s; + box-sizing: initial; + + span[role='img'] { + font-size: 24px; + display: inline-block; + } + + span[role='img'].univericon-nextIcon { + font-size: 13px; + } + + span[role='img'].univericon-rightIcon { + font-size: 13px; + } + + .universheet-toolbar-left-skin { + width: 15px; + position: absolute; + left: 0; + top: 1px; + bottom: 1px; + } + + .universheet-inline-block { + display: inline-block; + } + + .universheet-flex { + display: flex; + } + + .universheet-toolbar-button, + .universheet-toolbar-menu-button { + vertical-align: middle; + + &:hover { + background: rgba(0, 0, 0, 0.06); + } + + button { + min-width: 26px; + height: 26px; + padding: 0; + border: none; + .toolbar-button(); + + span { + height: 100%; + vertical-align: text-bottom; + } + } + } + + .universheet-toolbar-button-outer-box, + .universheet-toolbar-menu-button-outer-box { + .outer-inner-box(); + } + + .universheet-toolbar-select { + position: relative; + } + + .universheet-toolbar-button-inner-box, + .universheet-toolbar-menu-button-inner-box { + padding: 0 2px; + padding: 0; + text-align: center; + height: 26px; + min-width: 26px; + font-size: 12px; + } + + .universheet-toolbar-menu-button-caption { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-left: 1px; + text-align: center; + height: 100%; + } + + .universheet-toolbar-menu-button-text { + width: 55px !important; + } + + .universheet-toolbar-separator { + line-height: normal; + list-style: none; + outline: 0; + overflow: hidden; + padding: 0; + text-decoration: none; + width: 0; + height: 20px; + vertical-align: top; + border-left: 1px solid #e0e0e0; + margin: 5px 1px; + } + + .universheet-toolbar-combo-button { + font-weight: 700; + vertical-align: inherit; + margin: 0 1px; + .toolbar-button(); + } + + .universheet-toolbar-combo-button-inner-box, + .universheet-toolbar-combo-button-outer-box { + .outer-inner-box(); + } + + .universheet-toolbar-zoom-combobox .universheet-toolbar-combo-button-caption { + width: 36px !important; + } + + .universheet-toolbar-combo-button-caption { + padding: 0; + margin: 0 0 0 -3px; + } + + .universheet-toolbar-textinput { + -webkit-border-radius: 1px; + -moz-border-radius: 1px; + border-radius: 1px; + border: 1px solid #d9d9d9; + border-top: 1px solid silver; + font-size: 13px; + height: 25px; + padding: 1px 0 1px 8px; + } + + .universheet-toolbar-combo-button-input { + background: 0 0; + border: 1px solid transparent !important; + color: var(--text-color); + font-family: Arial, sans-serif !important; + font-size: 11px !important; + font-weight: 700 !important; + height: 20px !important; + overflow: hidden !important; + color: rgba(0, 0, 0, 0.7); + height: 22px !important; + width: 22px; + } + + .universheet-cols-menu { + .menu-button(); + width: 252px; + left: 0; + top: 26px; + display: none; + } + + .universheet-cols-menu .universheet-cols-menuitem-hover, + .universheet-cols-menu .universheet-cols-menuitem:hover { + background-color: #efefef; + } + + .universheet-cols-menu .universheet-cols-menuitem { + position: relative; + color: var(--text-color); + cursor: pointer; + list-style: none; + margin: 0; + padding: 1px 6em 1px 20px; + white-space: nowrap; + padding-left: 8px; + vertical-align: middle; + padding-right: 24px; + user-select: none; + line-height: 16px; + } + + .universheet-submenu-arrow { + -webkit-transition: all 218ms; + -moz-transition: all 218ms; + -o-transition: all 218ms; + transition: all 218ms; + font-size: 12px; + left: auto; + right: 15px; + top: 50%; + transform: translateY(-50%); + padding-right: 0; + position: absolute; + text-align: right; + opacity: 0.5; + color: #000; + user-select: none; + font-family: Arial; + } + + .universheet-toolbar-dblbutton { + .universheet-toolbar-next-button { + border-left: 1px solid transparent; + position: relative; + + .universheet-next-menu { + position: absolute; + z-index: 2; + background-color: #fff; + box-shadow: var(--box-shadow-base); + font-size: 12px; + width: fit-content; + + .univer-menu-item > div { + width: 100%; + display: flex; + justify-content: space-between; + box-sizing: border-box; + padding: 0 5px 0 0; + + .universheet-next-menu-icon { + width: 25px; + + & ~ div { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + } + } + } + + svg { + font-size: 24px; + vertical-align: top; + } + } + + svg { + font-size: 12px; + vertical-align: middle; + } + } + + .universheet-color-bottom { + width: 10px; + margin: 0 auto; + outline: 2px solid; + position: relative; + bottom: 7px; + } + + button.univer-btn { + min-width: 15px; + + &:active { + background: transparent !important; + } + } + + :hover .universheet-toolbar-next-button { + border-left: 1px solid #e0e0e0; + } + } +} + +// 重复样式 +.outer-inner-box() { + border: 0; + vertical-align: top; + margin: 0; + padding: 0; +} + +.toolbar-button() { + -webkit-border-radius: 2px; + -moz-border-radius: 2px; + border-radius: 2px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + background: 0; + border: 1px solid transparent; + outline: 0; + padding: 0; + list-style: none; + font-size: 11px; + text-decoration: none; + cursor: default; + height: 26px; + line-height: 26px; + color: var(--text-color); +} + +.menu-button() { + border-radius: 0; + box-shadow: 0 2px 4px rgb(0 0 0 / 20%); + transition: opacity 218ms; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.2); + cursor: default; + font-size: 13px; + margin: 0; + outline: 0; + padding: 6px 0; + position: absolute; + z-index: 2; + box-sizing: border-box; + user-select: none; +} diff --git a/packages/base-ui/src/views/components/toolbar/index.module.less b/packages/base-ui/src/views/components/toolbar/index.module.less new file mode 100644 index 0000000000..e99f17bd85 --- /dev/null +++ b/packages/base-ui/src/views/components/toolbar/index.module.less @@ -0,0 +1,176 @@ +.toolbar-warp { + max-height: fit-content; + width: 100%; + overflow: unset; + box-sizing: border-box; + font-size: var(--font-size); + background-color: var(--bg); + position: relative; + display: flex; + align-items: center; + border-bottom: 1px solid var(--border-color-base); + + .single-button { + &.more-hide { + display: none; + } + + &.more-show { + display: block; + } + } +} + +.toolbar { + max-width: 100%; + color: var(--text-color); + padding: 0 15px; + height: 40px; + position: relative; + display: flex; + align-items: center; + + >div { + height: 26px; + display: inline-block; + vertical-align: middle; + margin: 0 3px; + cursor: pointer; + + &.select-button:hover { + background-color: #efefef; + } + } + + .cols-menu { + overflow-y: auto; + } + + .select-label-string { + .drop-content { + >div { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + width: 90px; + } + } + } + + .select-double-string { + .drop-content { + >div { + width: 24px; + } + } + } + + .select-color-picker-parent { + .select-item-content { + width: 100%; + } + + .select-color-picker { + cursor: default; + + &:hover { + background: var(--bg); + } + + .select-item { + padding: 0; + } + + .color-picker-outer { + box-shadow: none; + } + } + } + + .select-line-bold-parent { + .select-item-content { + width: 100%; + } + } + + .select-single { + height: 26px; + line-height: 26px; + } + + .select-input { + height: 26px; + line-height: 26px; + } + + .select-double { + height: 26px; + line-height: 26px; + } + + button { + padding: 0; + height: 26px; + } + + span[role='img'].univericon-nextIcon { + font-size: 12px; + } + + .text-button { + span[role="img"] { + font-size: 24px; + } + + button { + padding: 0 5px; + } + } + +} + +.more-button { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); + .button(); + + &.more-show { + display: block; + } +} + +.more-tool { + // height: 40px; + // display: none; + // align-items: center; + padding: 0 10px; + background-color: var(--bg); + box-shadow: var(--box-shadow-base); + position: absolute; + right: 2em; + top: 40px; + z-index: 2; + + &.more-show { + display: flex; + } + + button { + .button(); + } +} + +.button() { + padding: 0px; + line-height: 1; +} + +.tooltip-title { + opacity: 1; +} + +.bottom { + display: none; +} \ No newline at end of file diff --git a/packages/base-ui/src/views/components/toolbar/toolbar-item.tsx b/packages/base-ui/src/views/components/toolbar/toolbar-item.tsx new file mode 100644 index 0000000000..30510bf54d --- /dev/null +++ b/packages/base-ui/src/views/components/toolbar/toolbar-item.tsx @@ -0,0 +1,121 @@ +import { ICommandService } from '@univerjs/core'; +import { Component } from 'react'; +import { Subscription } from 'rxjs'; + +import { AppContext } from '../../../Common/AppContext'; +import { Button } from '../../../Components/Button/Button'; +import { CustomLabel } from '../../../Components/CustomLabel/CustomLabel'; +import { Select } from '../../../Components/Select/Select'; +import { Tooltip } from '../../../Components/Tooltip/Tooltip'; +import { IDisplayMenuItem, IMenuButtonItem, IMenuItem, IMenuSelectorItem, IValueOption, MenuItemType } from '../../../services/menu/menu'; +import styles from './index.module.less'; + +export interface IToolbarItemStatus { + disabled: boolean; + activated: boolean; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any; +} + +/** The component to render toolbar item. */ +export class ToolbarItem extends Component, IToolbarItemStatus> { + static override contextType = AppContext; + + private disabledSubscription: Subscription | undefined; + + private activatedSubscription: Subscription | undefined; + + private currentValueSubscription: Subscription | undefined; + + constructor(props: IDisplayMenuItem) { + super(props); + + this.state = { + disabled: false, + activated: false, + value: undefined, + }; + } + + override componentDidMount() { + this.disabledSubscription = this.props.disabled$?.subscribe((disabled) => { + this.setState({ disabled }); + }); + + if (this.props.type === MenuItemType.BUTTON) { + const props = this.props as IDisplayMenuItem; + this.activatedSubscription = props.activated$?.subscribe((activated) => { + this.setState({ activated }); + }); + } + + if (this.props.type === MenuItemType.SELECTOR) { + const props = this.props as IDisplayMenuItem>; + this.currentValueSubscription = props.value$?.subscribe((value) => { + this.setState({ value }); + }); + } + } + + override UNSAFE_componentWillMount() { + this.disabledSubscription?.unsubscribe(); + this.activatedSubscription?.unsubscribe(); + this.currentValueSubscription?.unsubscribe(); + } + + override render() { + switch (this.props.type) { + case MenuItemType.SUBITEMS: + case MenuItemType.SELECTOR: + return this.renderSelectorType(); + default: + return this.renderButtonType(); + } + } + + private renderSelectorType() { + const { context, state } = this; + const commandService: ICommandService = context.injector.get(ICommandService); + const { disabled, value } = state; + + const props = this.props as IDisplayMenuItem>; + const { icon, title, label, display, selectType, selections, id } = props; + + return ( + + + + ); + } + + private renderButtonType() { + const { props, context, state } = this; + const { disabled, activated } = state; + const { icon, title } = props; + const commandService: ICommandService = context.injector.get(ICommandService); + + return ( + + + + ); + } + + private getTooltip(): string { + return this.context.localeService?.t(this.props.tooltip) + (this.props.shortcut ? ` (${this.props.shortcut})` : ''); + } +} diff --git a/packages/base-ui/src/views/components/toolbar/toolbar.tsx b/packages/base-ui/src/views/components/toolbar/toolbar.tsx new file mode 100644 index 0000000000..cf52c8f072 --- /dev/null +++ b/packages/base-ui/src/views/components/toolbar/toolbar.tsx @@ -0,0 +1,65 @@ +import { Component, createRef } from 'react'; + +import { BaseComponentProps } from '../../../BaseComponent'; +import { AppContext } from '../../../Common/AppContext'; +import { Container } from '../../../Components/Container/Container'; +import { IDisplayMenuItem, IMenuItem, MenuPosition } from '../../../services/menu/menu'; +import { IMenuService } from '../../../services/menu/menu.service'; +import styles from './index.module.less'; +import { ToolbarItem } from './toolbar-item'; + +interface IToolbarState { + menuItems: Array>; +} + +interface IToolbarProps extends BaseComponentProps { + style?: React.CSSProperties; +} + +export class Toolbar extends Component { + static override contextType = AppContext; + + declare context: React.ContextType; + + toolbarRef = createRef(); + + moreBtnRef = createRef(); + + moreToolRef = createRef(); + + SelectRef = createRef(); + + clientWidth = 0; + + constructor(props: IToolbarProps) { + super(props); + this.initialize(); + } + + initialize() { + this.state = { + menuItems: [], + }; + } + + override componentDidMount() { + this.props.getComponent?.(this); // pass the UI to the controller, which is not good... + const menuService = this.context.injector!.get(IMenuService); + this.setState({ + menuItems: menuService.getMenuItems(MenuPosition.TOOLBAR), + }); + } + + override render() { + const { menuItems } = this.state; + return ( + +
+ {menuItems.map((item) => ( + + ))} +
+
+ ); + } +} diff --git a/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar.md b/packages/base-ui/src/views/hooks/di.ts similarity index 100% rename from packages/ui-plugin-sheets/src/View/Toolbar/Toolbar.md rename to packages/base-ui/src/views/hooks/di.ts diff --git a/packages/base-ui/src/views/parts.tsx b/packages/base-ui/src/views/parts.tsx new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/src/Basics/Univer.ts b/packages/core/src/Basics/Univer.ts index 7651f2273c..d8af794dc3 100644 --- a/packages/core/src/Basics/Univer.ts +++ b/packages/core/src/Basics/Univer.ts @@ -64,8 +64,8 @@ export class Univer { // TODO@wzhudev: clean this sheet.getWorkBook().onUniver(); - this.initializePluginsForSheet(sheet); this._currentUniverService.addSheet(sheet); + this.initializePluginsForSheet(sheet); return sheet; } @@ -73,8 +73,8 @@ export class Univer { createUniverDoc(config: Partial): UniverDoc { const doc = this._univerInjector.createInstance(UniverDoc, config); - this.initializePluginsForDoc(doc); this._currentUniverService.addDoc(doc); + this.initializePluginsForDoc(doc); return doc; } @@ -82,8 +82,8 @@ export class Univer { createUniverSlide(config: Partial): UniverSlide { const slide = this._univerInjector.createInstance(UniverSlide, config); - this.initializePluginsForSlide(slide); this._currentUniverService.addSlide(slide); + this.initializePluginsForSlide(slide); return slide; } @@ -135,7 +135,15 @@ export class Univer { private initializeDependencies(): Injector { return new Injector([ [ObserverManager], - [ICurrentUniverService, { useClass: CurrentUniverService }], + [ + ICurrentUniverService, + { + useFactory: () => + new CurrentUniverService({ + createUniverDoc: (data) => this.createUniverDoc(data), + }), + }, + ], [LocaleService], [ILogService, { useClass: DesktopLogService, lazy: true }], [ICommandService, { useClass: CommandService, lazy: true }], @@ -148,8 +156,7 @@ export class Univer { // For plugins at Univer level. Plugins would be initialized immediately so they can register dependencies. const pluginInstance: Plugin = this._univerInjector.createInstance(plugin as unknown as Ctor, options); - // TODO: remove these two lines later - pluginInstance.onCreate(); + pluginInstance.onMounted(); this._univerPluginStore.addPlugin(pluginInstance); } @@ -197,7 +204,6 @@ export class Univer { plugins.forEach((p) => { doc.addPlugin(p.plugin as unknown as PluginCtor, p.options); }); - doc.mount(); } private initializePluginsForSlide(slide: UniverSlide): void { diff --git a/packages/core/src/Basics/UniverDoc.ts b/packages/core/src/Basics/UniverDoc.ts index 025ea122d9..3f97c31714 100644 --- a/packages/core/src/Basics/UniverDoc.ts +++ b/packages/core/src/Basics/UniverDoc.ts @@ -37,17 +37,14 @@ export class UniverDoc { addPlugin(pluginCtor: PluginCtor, options: any): void { const pluginInstance: Plugin = this._injector.createInstance(pluginCtor as unknown as Ctor, options); - // FIXME: onCreate onMounted should be executed after all plugins have been added - pluginInstance.onCreate(); pluginInstance.onMounted(); this._pluginStore.addPlugin(pluginInstance); } mount(): void { - this._pluginStore.forEachPlugin((plugin) => { - plugin.onCreate(); - plugin.onMounted(); - }); + // this._pluginStore.forEachPlugin((plugin) => { + // plugin.onMounted(); + // }); } private _initializeDependencies(parentInjector?: Injector): Injector { diff --git a/packages/core/src/Basics/UniverSheet.ts b/packages/core/src/Basics/UniverSheet.ts index c166dff42e..d176a5df47 100644 --- a/packages/core/src/Basics/UniverSheet.ts +++ b/packages/core/src/Basics/UniverSheet.ts @@ -17,6 +17,7 @@ export class UniverSheet implements IDisposable { private readonly _sheetInjector: Injector; + // TODO@wzhudev: maybe we should support multiple workbooks in the future? private readonly _workbook: Workbook; private readonly _pluginStore = new PluginStore(); @@ -58,7 +59,6 @@ export class UniverSheet implements IDisposable { addPlugin(plugin: PluginCtor, options: any): void { const pluginInstance: Plugin = this._sheetInjector.createInstance(plugin as unknown as Ctor, options); - pluginInstance.onCreate(); pluginInstance.onMounted(); this._pluginStore.addPlugin(pluginInstance); } diff --git a/packages/core/src/Basics/UniverSlide.ts b/packages/core/src/Basics/UniverSlide.ts index e519b39204..efe7ef054a 100644 --- a/packages/core/src/Basics/UniverSlide.ts +++ b/packages/core/src/Basics/UniverSlide.ts @@ -1,11 +1,12 @@ import { Ctor, Dependency, Injector, Optional } from '@wendellhu/redi'; -import { ISlideData } from '../Types/Interfaces'; -import { IOHttp, IOHttpConfig, Logger, GenName } from '../Shared'; -import { VersionCode, VersionEnv } from './Version'; + +import { ObserverManager } from '../Observer'; +import { Plugin, PluginCtor, PluginStore } from '../Plugin'; +import { GenName, IOHttp, IOHttpConfig, Logger } from '../Shared'; import { ColorBuilder } from '../Sheets/Domain/ColorBuilder'; import { SlideModel } from '../Slides/Domain'; -import { ObserverManager } from '../Observer'; -import { PluginCtor, PluginStore, Plugin } from '../Plugin'; +import { ISlideData } from '../Types/Interfaces'; +import { VersionCode, VersionEnv } from './Version'; /** * Externally provided UniverSlide root instance @@ -57,6 +58,10 @@ export class UniverSlide { return new ColorBuilder(); } + getUnitId(): string { + return this._slideModel.getUnitId(); + } + /** * Add a plugin into UniverSlide. UniverSlide should add dependencies exposed from this plugin to its DI system. * @@ -68,7 +73,6 @@ export class UniverSlide { addPlugin(plugin: PluginCtor, options: any): void { const pluginInstance: Plugin = this._slideInjector.createInstance(plugin as unknown as Ctor, options); - pluginInstance.onCreate(); pluginInstance.onMounted(); this._pluginStore.addPlugin(pluginInstance); } diff --git a/packages/core/src/Docs/Domain/DataStreamTreeNode.ts b/packages/core/src/Docs/Domain/DataStreamTreeNode.ts index b817881784..8a2efd0b9f 100644 --- a/packages/core/src/Docs/Domain/DataStreamTreeNode.ts +++ b/packages/core/src/Docs/Domain/DataStreamTreeNode.ts @@ -1,4 +1,4 @@ -import { horizontalLineSegmentsSubtraction, deleteContent, insertTextToContent } from '../../Shared/DocTool'; +import { deleteContent, horizontalLineSegmentsSubtraction, insertTextToContent } from '../../Shared/DocTool'; import { Nullable } from '../../Shared/Types'; import { DataStreamTreeNodeType, DataStreamTreeTokenType } from './Types'; diff --git a/packages/core/src/Docs/Domain/DocumentBodyModel.ts b/packages/core/src/Docs/Domain/DocumentBodyModel.ts index c2fcda599a..2405c4c762 100644 --- a/packages/core/src/Docs/Domain/DocumentBodyModel.ts +++ b/packages/core/src/Docs/Domain/DocumentBodyModel.ts @@ -1,3 +1,6 @@ +import { IDisposable } from '@wendellhu/redi'; +import { Observable, Subject } from 'rxjs'; + import { Nullable } from '../../Shared/Types'; import { IDocumentBody, ITextRun } from '../../Types/Interfaces/IDocumentData'; import { DataStreamTreeNode } from './DataStreamTreeNode'; @@ -5,16 +8,31 @@ import { DataStreamTreeNodeType, DataStreamTreeTokenType } from './Types'; export type DocumentBodyModelOrSimple = DocumentBodyModelSimple | DocumentBodyModel; -export class DocumentBodyModelSimple { +export class DocumentBodyModelSimple implements IDisposable { children: DataStreamTreeNode[] = []; - constructor(public body: IDocumentBody) { + readonly modelChange$: Observable; + + protected readonly _modelChange$: Subject; + + constructor( + /** @deprecated this does not hold true fact about the text model, do not use this directly */ + public body: IDocumentBody + ) { if (this.body == null) { return; } + + this._modelChange$ = new Subject(); + this.modelChange$ = this._modelChange$.asObservable(); + this.children = this._transformToTree(this.body.dataStream); } + dispose(): void { + this._modelChange$.complete(); + } + resetCache() {} getSectionBreak(index: number) { @@ -46,7 +64,7 @@ export class DocumentBodyModelSimple { return 0; } - private _transformToTree(dataStream: string) { + protected _transformToTree(dataStream: string) { const dataStreamLen = dataStream.length; let content = ''; @@ -191,10 +209,21 @@ export class DocumentBodyModel extends DocumentBodyModelSimple { private customRangeCurrentIndex = 0; + /** + * @deprecated use constructor directly + * @param body + * @returns + */ static create(body: IDocumentBody) { return new DocumentBodyModel(body); } + reset(body: IDocumentBody) { + this.children = this._transformToTree(body.dataStream); + this.body = body; + this._modelChange$.next(); + } + insert(insertBody: IDocumentBody, insertIndex = 0) { const dataStream = insertBody.dataStream; let dataStreamLen = dataStream.length; @@ -272,82 +301,14 @@ export class DocumentBodyModel extends DocumentBodyModelSimple { } }); } + + this._modelChange$.next(); } delete(currentIndex: number, textLength: number) { const nodes = this.children; this.deleteTree(nodes, currentIndex, textLength); - } - - deleteTree(nodes: DataStreamTreeNode[], currentIndex: number, textLength: number) { - const startIndex = currentIndex; - const endIndex = currentIndex + textLength - 1; - let mergeNode: Nullable = null; - let nodeCount = nodes.length; - let i = 0; - while (i < nodeCount) { - const node = nodes[i]; - const { startIndex: st, endIndex: ed, children } = node; - - this.deleteTree(children, currentIndex, textLength); - - if (startIndex === endIndex && endIndex === ed) { - // The cursor is at the dividing point between two paragraphs, - // and it is necessary to determine whether to delete elements - // such as paragraphs, chapters, and tables - if (node.nodeType === DataStreamTreeNodeType.PARAGRAPH) { - const nextNode = this._getNextNode(node); - if (nextNode == null) { - i++; - continue; - } - - // if (nextNode.isBullet || nextNode.isIndent) { - // i++; - // continue; - // } else { - node.minus(startIndex, endIndex); - node.merge(nextNode); - nodeCount--; - // } - } - // else if (node.nodeType === DataStreamTreeNodeType.SECTION_BREAK) { - // } else if (node.nodeType === DataStreamTreeNodeType.TABLE) { - // } else if (node.nodeType === DataStreamTreeNodeType.TABLE_ROW) { - // } else if (node.nodeType === DataStreamTreeNodeType.TABLE_CELL) { - // } - } else if (startIndex <= st && endIndex >= ed) { - // The first case. The selection range of the text box - // is larger than the current node - node.remove(); - nodeCount--; - continue; - } else if (st <= startIndex && ed >= endIndex) { - // The second case. The selection range of - // the text box is smaller than the current node - node.minus(startIndex, endIndex); - } else if (endIndex > st && endIndex < ed) { - // The third case. - // The text selection left contains the current node - node.minus(st, endIndex); - if (mergeNode != null) { - mergeNode.merge(node); - mergeNode = null; - nodeCount--; - continue; - } - } else if (startIndex > st && startIndex < ed) { - // The fourth case. - // The text selection right contains the current node - node.minus(startIndex, ed); - mergeNode = node; - } else if (st > endIndex) { - // The current node is not on the right side of - // the selection area and needs to be moved as a whole - node.plus(-textLength); - } - i++; - } + this._modelChange$.next(); } getParagraphByTree(nodes: DataStreamTreeNode[], insertIndex: number): Nullable { @@ -501,6 +462,24 @@ export class DocumentBodyModel extends DocumentBodyModelSimple { } } + /** Get pure text content in the given range. */ + getText(): string { + // Basically this is a DFS traversal of the tree to get the `content` and append it to the result. + // TODO: implement + const pieces: string[] = []; + + function traverseTreeNode(node: DataStreamTreeNode) { + if (node.content) { + pieces.push(node.content); + } + + node.children.forEach(traverseTreeNode); + } + + this.children.forEach((n) => traverseTreeNode(n)); + return pieces.join(''); + } + override getCustomBlock(index: number) { const customBlocks = this.body.customBlocks; if (customBlocks == null) { @@ -544,6 +523,77 @@ export class DocumentBodyModel extends DocumentBodyModelSimple { } } + private deleteTree(nodes: DataStreamTreeNode[], currentIndex: number, textLength: number) { + const startIndex = currentIndex; + const endIndex = currentIndex + textLength - 1; + let mergeNode: Nullable = null; + let nodeCount = nodes.length; + let i = 0; + while (i < nodeCount) { + const node = nodes[i]; + const { startIndex: st, endIndex: ed, children } = node; + + this.deleteTree(children, currentIndex, textLength); + + if (startIndex === endIndex && endIndex === ed) { + // The cursor is at the dividing point between two paragraphs, + // and it is necessary to determine whether to delete elements + // such as paragraphs, chapters, and tables + if (node.nodeType === DataStreamTreeNodeType.PARAGRAPH) { + const nextNode = this._getNextNode(node); + if (nextNode == null) { + i++; + continue; + } + + // if (nextNode.isBullet || nextNode.isIndent) { + // i++; + // continue; + // } else { + node.minus(startIndex, endIndex); + node.merge(nextNode); + nodeCount--; + // } + } + // else if (node.nodeType === DataStreamTreeNodeType.SECTION_BREAK) { + // } else if (node.nodeType === DataStreamTreeNodeType.TABLE) { + // } else if (node.nodeType === DataStreamTreeNodeType.TABLE_ROW) { + // } else if (node.nodeType === DataStreamTreeNodeType.TABLE_CELL) { + // } + } else if (startIndex <= st && endIndex >= ed) { + // The first case. The selection range of the text box + // is larger than the current node + node.remove(); + nodeCount--; + continue; + } else if (st <= startIndex && ed >= endIndex) { + // The second case. The selection range of + // the text box is smaller than the current node + node.minus(startIndex, endIndex); + } else if (endIndex > st && endIndex < ed) { + // The third case. + // The text selection left contains the current node + node.minus(st, endIndex); + if (mergeNode != null) { + mergeNode.merge(node); + mergeNode = null; + nodeCount--; + continue; + } + } else if (startIndex > st && startIndex < ed) { + // The fourth case. + // The text selection right contains the current node + node.minus(startIndex, ed); + mergeNode = node; + } else if (st > endIndex) { + // The current node is not on the right side of + // the selection area and needs to be moved as a whole + node.plus(-textLength); + } + i++; + } + } + private _getNextNode(node: DataStreamTreeNode): DataStreamTreeNode { const currentIndex = node.getPositionInParent(); const children = node.parent?.children; diff --git a/packages/core/src/Docs/Domain/DocumentModel.ts b/packages/core/src/Docs/Domain/DocumentModel.ts index 74e744289e..5a0344ffd6 100644 --- a/packages/core/src/Docs/Domain/DocumentModel.ts +++ b/packages/core/src/Docs/Domain/DocumentModel.ts @@ -115,11 +115,21 @@ export class DocumentModel extends DocumentModelSimple { constructor(snapshot: Partial) { super(snapshot); - this._unitId = this.snapshot.id ?? Tools.generateRandomId(UNIT_ID_LENGTH); + this._unitId = this.snapshot.id ?? Tools.generateRandomId(UNIT_ID_LENGTH); this.initializeRowColTree(); } + reset(snapshot: Partial) { + if (snapshot.id && snapshot.id !== this._unitId) { + throw new Error('Cannot reset a document model with a different unit id!'); + } + + this.snapshot = { ...DEFAULT_DOC, ...snapshot }; + this.initializeRowColTree(); + this.bodyModel.reset(snapshot.body ?? { dataStream: '\r\n\0' }); + } + getUnitId(): string { return this._unitId; } @@ -145,3 +155,11 @@ export class DocumentModel extends DocumentModelSimple { } } } + +export function createEmptyDocSnapshot(): Partial { + return { + body: { + dataStream: '\r\n\0', + }, + }; +} diff --git a/packages/core/src/Plugin/Plugin.ts b/packages/core/src/Plugin/Plugin.ts index 698bf81f54..6728d9a61f 100644 --- a/packages/core/src/Plugin/Plugin.ts +++ b/packages/core/src/Plugin/Plugin.ts @@ -1,7 +1,7 @@ import { Ctor, Injector } from '@wendellhu/redi'; +import { Nullable } from '../common/type-utils'; import { Observable, ObserverManager } from '../Observer'; -import { Nullable } from '../Shared'; export type PluginCtor = Ctor & { type: PluginType }; @@ -13,17 +13,9 @@ export enum PluginType { Slide, } -/** - * Basics function of plugin - */ -export interface BasePlugin { +export interface IPlugin { getPluginName(): string; - /** - * Could register dependencies at this lifecycle stage. - */ - onCreate(): void; - /** * Could setup initialization works at this lifecycle stage. */ @@ -34,13 +26,13 @@ export interface BasePlugin { */ onDestroy(): void; - getPluginByName(name: string): Nullable; + getPluginByName(name: string): Nullable; } /** * Plug-in base class, all plug-ins must inherit from this base class. Provide basic methods. */ -export abstract class Plugin implements BasePlugin { +export abstract class Plugin implements IPlugin { static type: PluginType; _injector: Injector; @@ -54,13 +46,11 @@ export abstract class Plugin implements BasePlugin { this._observeNames = []; } - /** @deprecated this plugin will be removed */ - getPluginByName(name: string): Nullable { + /** @deprecated this method will be removed */ + getPluginByName(name: string): Nullable { throw new Error('Method not implemented.'); } - onCreate(): void {} - onMounted(): void {} onDestroy(): void { @@ -71,8 +61,7 @@ export abstract class Plugin implements BasePlugin { return this._name; } - // TODO@huwenzhao: remove this in the future - /** @deprecated */ + /** @deprecated this method will be removed */ pushToObserve(...names: K[]): void { const manager = (this._injector as Injector).get(ObserverManager); names.forEach((name) => { diff --git a/packages/core/src/Shared/Lifecycle.ts b/packages/core/src/Shared/Lifecycle.ts index 7481e3117d..7afacc92ab 100644 --- a/packages/core/src/Shared/Lifecycle.ts +++ b/packages/core/src/Shared/Lifecycle.ts @@ -1,11 +1,30 @@ import { IDisposable } from '@wendellhu/redi'; +import { Subscription, SubscriptionLike } from 'rxjs'; +import { isSubscription } from 'rxjs/internal/Subscription'; + +export function toDisposable(subscription: SubscriptionLike): IDisposable; +export function toDisposable(callback: () => void): IDisposable; +export function toDisposable(v: SubscriptionLike | (() => void)): IDisposable { + if (isSubscription(v)) { + return { + dispose: () => v.unsubscribe(), + }; + } -export function toDisposable(callback: () => void): IDisposable { return { - dispose: callback, + dispose: v as () => void, }; } +/** + * @deprecated use toDisposable instead + */ +export function fromObservable(subscription: Subscription) { + return toDisposable(() => { + subscription.unsubscribe(); + }); +} + export class DisposableCollection implements IDisposable { private readonly _disposables = new Set(); diff --git a/packages/core/src/Shared/Tools.ts b/packages/core/src/Shared/Tools.ts index 211e7bd615..976de260f5 100644 --- a/packages/core/src/Shared/Tools.ts +++ b/packages/core/src/Shared/Tools.ts @@ -1,8 +1,9 @@ import { nanoid } from 'nanoid'; + import { Environment } from '../Basics/Environment'; +import { ObjectArray, ObjectArrayPrimitiveType } from './ObjectArray'; import { ObjectMatrix, ObjectMatrixPrimitiveType } from './ObjectMatrix'; import { Class, IKeyValue } from './Types'; -import { ObjectArray, ObjectArrayPrimitiveType } from './ObjectArray'; const rmsPrefix = /^-ms-/; const rDashAlpha = /-([a-z])/g; diff --git a/packages/core/src/Sheets/Domain/Workbook.ts b/packages/core/src/Sheets/Domain/Workbook.ts index faac511794..1be971104e 100644 --- a/packages/core/src/Sheets/Domain/Workbook.ts +++ b/packages/core/src/Sheets/Domain/Workbook.ts @@ -71,6 +71,7 @@ export class Workbook { return typeof range === 'string' || 'startRow' in range || 'row' in range; } + /** @deprecated */ onUniver() { this._getDefaultWorkSheet(); } diff --git a/packages/core/src/Slides/Domain/SlideModel.ts b/packages/core/src/Slides/Domain/SlideModel.ts index e7dd15c8f2..dc4e384c5a 100644 --- a/packages/core/src/Slides/Domain/SlideModel.ts +++ b/packages/core/src/Slides/Domain/SlideModel.ts @@ -1,6 +1,6 @@ +import { Tools } from '../../Shared'; import { DEFAULT_SLIDE } from '../../Types/Const'; import { ISlideData, ISlidePage, PageType } from '../../Types/Interfaces'; -import { Tools } from '../../Shared'; export class SlideModel { private _snapshot: ISlideData; diff --git a/packages/core/src/Types/Const/PLUGIN_NAMES.ts b/packages/core/src/Types/Const/PLUGIN_NAMES.ts index 3fc2ca2c66..8aa39ddf0a 100644 --- a/packages/core/src/Types/Const/PLUGIN_NAMES.ts +++ b/packages/core/src/Types/Const/PLUGIN_NAMES.ts @@ -1,5 +1,7 @@ /** * Names of Univer Plugin + * + * @deprecated how can core possibly know all plugin names? */ export enum PLUGIN_NAMES { // base- diff --git a/packages/core/src/Types/Interfaces/ICellData.ts b/packages/core/src/Types/Interfaces/ICellData.ts index 00a8c4c89e..983cd00788 100644 --- a/packages/core/src/Types/Interfaces/ICellData.ts +++ b/packages/core/src/Types/Interfaces/ICellData.ts @@ -48,6 +48,12 @@ export interface ICellData { */ // n?: string; // note comment postil } + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function isICellData(value: any): value is ICellData { + return value && ((value as ICellData).s !== undefined || (value as ICellData).p !== undefined || (value as ICellData).v !== undefined); +} + /** * Format of cell data */ diff --git a/packages/core/src/Types/Interfaces/ISlideData.ts b/packages/core/src/Types/Interfaces/ISlideData.ts index c04588b073..6ae15dcff4 100644 --- a/packages/core/src/Types/Interfaces/ISlideData.ts +++ b/packages/core/src/Types/Interfaces/ISlideData.ts @@ -1,6 +1,6 @@ +import { IKeyType, Nullable } from '../../Shared/Types'; import { LocaleType, ThemeColorType } from '../Enum'; import { ShapeType } from '../Enum/ShapeType'; -import { IKeyType, Nullable } from '../../Shared/Types'; import { ICustomBlock, IDocumentData, ILists, ISizeData } from './IDocumentData'; import { IImageProperties } from './IImageProperties'; import { IPlaceholder } from './IPlaceholder'; diff --git a/packages/core/src/common/type-utils.ts b/packages/core/src/common/type-utils.ts new file mode 100644 index 0000000000..ddec5b5657 --- /dev/null +++ b/packages/core/src/common/type-utils.ts @@ -0,0 +1,4 @@ +/** + * Alias type for value that can be null + */ +export type Nullable = T | null | undefined | void; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index a0b1821cd2..3c3c9d8279 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -24,7 +24,7 @@ export { DesktopLogService, ILogService } from './services/log/log.service'; export { DesktopPermissionService, IPermissionService } from './services/permission/permission.service'; export { IUndoRedoService, LocalUndoRedoService, RedoCommand, UndoCommand } from './services/undoredo/undoredo.service'; export * from './Shared'; -export { Disposable, DisposableCollection, toDisposable } from './Shared/Lifecycle'; +export { Disposable, DisposableCollection, fromObservable, toDisposable } from './Shared/Lifecycle'; export { createRowColIter, IRowColIter } from './Shared/RowColIter'; export * from './Sheets'; export * from './Slides/Domain'; diff --git a/packages/core/src/services/command/command.service.ts b/packages/core/src/services/command/command.service.ts index e19abd0348..aaef88fc61 100644 --- a/packages/core/src/services/command/command.service.ts +++ b/packages/core/src/services/command/command.service.ts @@ -230,7 +230,6 @@ export class CommandService implements ICommandService { return this._commandRegistry.registerCommand(command, injector); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any private async _execute

(command: ICommand, injector: Injector, params?: P): Promise { this._log.log(`${'|-'.repeat(this._commandExecutingLevel)}[ICommandService]: executing command "${command.id}".`); diff --git a/packages/core/src/services/current.service.ts b/packages/core/src/services/current.service.ts index b8e3b2a033..bed996788b 100644 --- a/packages/core/src/services/current.service.ts +++ b/packages/core/src/services/current.service.ts @@ -1,37 +1,76 @@ import { createIdentifier } from '@wendellhu/redi'; +import { BehaviorSubject, Observable } from 'rxjs'; import { UniverDoc } from '../Basics/UniverDoc'; import { UniverSheet } from '../Basics/UniverSheet'; import { UniverSlide } from '../Basics/UniverSlide'; import { Nullable } from '../Shared'; +import { Disposable } from '../Shared/Lifecycle'; +import { IDocumentData } from '../Types/Interfaces'; +export interface IUniverHandler { + createUniverDoc(data: Partial): UniverDoc; +} + +/** + * ICurrentUniverService holds all the current univer instances. And it also manages + * the focused univer instance. + */ export interface ICurrentUniverService { + focused$: Observable; + + createDoc(data: Partial): UniverDoc; + addSheet(sheet: UniverSheet): void; addSlide(slide: UniverSlide): void; addDoc(doc: UniverDoc): void; + getUniverSheetInstance(id: string): Nullable; getUniverDocInstance(id: string): Nullable; - getUniverSlideInstance(id: string): Nullable; + getUniverSlideInstance(id: string): Nullable; getCurrentUniverSheetInstance(): UniverSheet; getCurrentUniverDocInstance(): UniverDoc; getCurrentUniverSlideInstance(): UniverSlide; getAllUniverSheetsInstance(): UniverSheet[]; getAllUniverDocsInstance(): UniverDoc[]; getAllUniverSlidesInstance(): UniverSlide[]; + + getFocusedUniverInstance(): UniverSheet | UniverDoc | UniverSlide | null; + focusUniverInstance(id: string | null): void; } export const ICurrentUniverService = createIdentifier('univer.current'); -/** - * Manager instances inside Univer. Also it manages focus on univer instances. - */ -export class CurrentUniverService implements ICurrentUniverService { +export class CurrentUniverService extends Disposable implements ICurrentUniverService { + readonly focused$: Observable; + + private readonly _focused$: BehaviorSubject; + + private _focused: UniverDoc | UniverSheet | UniverSlide | null = null; + private readonly _sheets: UniverSheet[] = []; private readonly _docs: UniverDoc[] = []; private readonly _slides: UniverSlide[] = []; + constructor(private readonly _handler: IUniverHandler) { + super(); + + this._focused$ = new BehaviorSubject(null); + this.focused$ = this._focused$.asObservable(); + } + + override dispose(): void { + super.dispose(); + + this._focused$.complete(); + } + + createDoc(data: Partial): UniverDoc { + return this._handler.createUniverDoc(data); + } + addSheet(sheet: UniverSheet): void { this._sheets.push(sheet); } @@ -52,8 +91,8 @@ export class CurrentUniverService implements ICurrentUniverService { return this._docs.find((doc) => doc.getUnitId() === id); } - getUniverSlideInstance(id: string): Nullable { - return null; + getUniverSlideInstance(id: string): Nullable { + return this._slides.find((slide) => slide.getUnitId() === id); } getAllUniverSheetsInstance() { @@ -83,4 +122,15 @@ export class CurrentUniverService implements ICurrentUniverService { getCurrentUniverSlideInstance() { return this._slides[0]; } + + focusUniverInstance(id: string | null): void { + if (id) { + this._focused = this.getUniverSheetInstance(id) || this.getUniverDocInstance(id) || this.getUniverSlideInstance(id) || null; + } + this._focused$.next(id); + } + + getFocusedUniverInstance(): UniverSheet | UniverDoc | UniverSlide | null { + return this._focused; + } } diff --git a/packages/core/src/services/lifecycle/lifecycle.service.ts b/packages/core/src/services/lifecycle/lifecycle.service.ts new file mode 100644 index 0000000000..fcd31921e3 --- /dev/null +++ b/packages/core/src/services/lifecycle/lifecycle.service.ts @@ -0,0 +1,5 @@ +import { LifecycleStages } from './lifecycle'; + +export class LifecycleService { + stage: LifecycleStages = LifecycleStages.Staring; +} \ No newline at end of file diff --git a/packages/core/src/services/lifecycle/lifecycle.ts b/packages/core/src/services/lifecycle/lifecycle.ts new file mode 100644 index 0000000000..605b877815 --- /dev/null +++ b/packages/core/src/services/lifecycle/lifecycle.ts @@ -0,0 +1,25 @@ +/** + * This enum defines multiple lifecycle stages in Univer SDK. + */ +export const enum LifecycleStages { + /** + * Register plugins to Univer. + */ + Staring, + + /** + * Univer business instances (UniverDoc / UniverSheet / UniverSlide) are created and services or controllers provided by + * plugins get intialized. The application is ready to do the first-time rendering. + */ + Ready, + + /** + * First-time rendering is completed. + */ + Rendered, + + /** + * All lazy tasks are completed. The application is fully ready to provide features to users. + */ + Steady, +} diff --git a/packages/core/src/services/undoredo/undoredo.service.ts b/packages/core/src/services/undoredo/undoredo.service.ts index da4990a56c..0c3a165ae6 100644 --- a/packages/core/src/services/undoredo/undoredo.service.ts +++ b/packages/core/src/services/undoredo/undoredo.service.ts @@ -3,11 +3,12 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { Disposable, toDisposable } from '../../Shared/Lifecycle'; import { CommandService, CommandType, ICommand, ICommandService } from '../command/command.service'; +import { ICurrentUniverService } from '../current.service'; // TODO:: an undo redo element may be mergable to another undo redo element interface IUndoRedoItem { - /** */ + /** URI maps to unitId for UniverSheet / UniverDoc / UniverSlide */ URI: string; undo(): Promise | boolean; @@ -15,6 +16,7 @@ interface IUndoRedoItem { } export interface IUndoRedoService { + // This should be by unitId. undoRedoStatus$: Observable; pushUndoRedo(item: IUndoRedoItem): void; @@ -24,6 +26,8 @@ export interface IUndoRedoService { popUndoToRedo(): void; popRedoToUndo(): void; + + clearUndoRedo(URI: string): void; } export const IUndoRedoService = createIdentifier('univer.undo-redo.service'); @@ -33,6 +37,8 @@ export interface IUndoRedoStatus { redos: number; } +const STACK_CAPACITY = 20; + /** * This UndoRedoService is local. */ @@ -41,76 +47,145 @@ export class LocalUndoRedoService extends Disposable implements IUndoRedoService private readonly _undoRedoStatus$ = new BehaviorSubject<{ undos: number; redos: number }>({ undos: 0, redos: 0 }); - private readonly _undoStack: IUndoRedoItem[] = []; + private readonly _undoStacks = new Map(); - private readonly _redoStack: IUndoRedoItem[] = []; + private readonly _redoStacks = new Map(); - constructor(@ICommandService private readonly _commandService: CommandService) { + constructor(@ICurrentUniverService private readonly _currentUniverSheet: ICurrentUniverService, @ICommandService private readonly _commandService: CommandService) { super(); this.undoRedoStatus$ = this._undoRedoStatus$.asObservable(); this.disposeWithMe(this._commandService.registerCommand(UndoCommand)); this.disposeWithMe(this._commandService.registerCommand(RedoCommand)); - this.disposeWithMe( - toDisposable(() => { - this._undoRedoStatus$.complete(); - }) - ); + this.disposeWithMe(toDisposable(() => this._undoRedoStatus$.complete())); + this.disposeWithMe(toDisposable(this._currentUniverSheet.focused$.subscribe(() => this.updateStatus()))); } pushUndoRedo(item: IUndoRedoItem): void { - // redo stack should be cleared when push an undo - this._redoStack.length = 0; + const { URI } = item; + + const redoStack = this.getRedoStack(URI, true); + const undoStack = this.getUndoStack(URI, true); + + // redo stack should be cleared when pushing an undo + redoStack.length = 0; // TODO: undo element maybe mergeable // TODO: undo redo stack should have a maximum capacity, maybe we should get the config from IConfigService? - this._undoStack.push(item); - if (this._undoStack.length > 20) { - this._undoStack.splice(0, 1); + undoStack.push(item); + if (undoStack.length > STACK_CAPACITY) { + undoStack.splice(0, 1); + } + + // TODO: update status with URI, the UI doesn't have to update perhaps + this.updateStatus(); + } + + clearUndoRedo(URI: string): void { + const redoStack = this.getRedoStack(URI); + if (redoStack) { + redoStack.length = 0; + } + + const undoStack = this.getUndoStack(URI); + if (undoStack) { + undoStack.length = 0; } this.updateStatus(); } pitchTopUndoElement(): IUndoRedoItem | null { - if (this._undoStack.length) { - return this._undoStack[this._undoStack.length - 1]; + const undoStack = this.getUndoStackForFocused(); + if (undoStack.length) { + return undoStack[undoStack.length - 1]; } return null; } pitchTopRedoElement(): IUndoRedoItem | null { - if (this._redoStack.length) { - return this._redoStack[this._redoStack.length - 1]; + const redoStack = this.getRedoStackForFocused(); + if (redoStack.length) { + return redoStack[redoStack.length - 1]; } return null; } popUndoToRedo(): void { - const element = this._undoStack.pop(); + const undoStack = this.getUndoStackForFocused(); + const element = undoStack.pop(); if (element) { - this._redoStack.push(element); + const redoStack = this.getRedoStackForFocused(); + redoStack.push(element); this.updateStatus(); } } popRedoToUndo(): void { - const element = this._redoStack.pop(); + const redoStack = this.getRedoStackForFocused(); + const element = redoStack.pop(); if (element) { - this._undoStack.push(element); + const redoStack = this.getRedoStackForFocused(); + redoStack.push(element); this.updateStatus(); } } private updateStatus(): void { + const URI = this._currentUniverSheet.getFocusedUniverInstance()?.getUnitId(); + const undos = (URI && this._undoStacks.get(URI)?.length) || 0; + const redos = (URI && this._redoStacks.get(URI)?.length) || 0; + this._undoRedoStatus$.next({ - undos: this._undoStack.length, - redos: this._redoStack.length, + undos, + redos, }); } + + private getUndoStack(unitId: string): IUndoRedoItem[] | null; + private getUndoStack(unitId: string, createAsNeeded: true): IUndoRedoItem[]; + private getUndoStack(unitId: string, createAsNeeded = false): IUndoRedoItem[] | null { + let stack = this._undoStacks.get(unitId); + if (!stack && createAsNeeded) { + stack = []; + this._undoStacks.set(unitId, stack); + } + + return stack || null; + } + + private getRedoStack(unitId: string): IUndoRedoItem[] | null; + private getRedoStack(unitId: string, createAsNeeded: true): IUndoRedoItem[]; + private getRedoStack(unitId: string, createAsNeeded = false): IUndoRedoItem[] | null { + let stack = this._redoStacks.get(unitId); + if (!stack && createAsNeeded) { + stack = []; + this._redoStacks.set(unitId, stack); + } + + return stack || null; + } + + private getUndoStackForFocused(): IUndoRedoItem[] { + const URI = this._currentUniverSheet.getFocusedUniverInstance()?.getUnitId(); + if (!URI) { + throw new Error('No focused univer instance!'); + } + + return this.getUndoStack(URI, true); + } + + private getRedoStackForFocused(): IUndoRedoItem[] { + const URI = this._currentUniverSheet.getFocusedUniverInstance()?.getUnitId(); + if (!URI) { + throw new Error('No focused univer instance!'); + } + + return this.getRedoStack(URI, true); + } } abstract class MultiImplementationCommand implements IDisposable { diff --git a/packages/ui-plugin-docs/src/Controller/AppUIController.ts b/packages/ui-plugin-docs/src/Controller/AppUIController.ts index 28cd28e476..b83fe7c58e 100644 --- a/packages/ui-plugin-docs/src/Controller/AppUIController.ts +++ b/packages/ui-plugin-docs/src/Controller/AppUIController.ts @@ -1,9 +1,7 @@ -import { ComponentManager } from '@univerjs/base-ui'; import { LocaleService, LocaleType, ObserverManager } from '@univerjs/core'; import { Inject, Injector, SkipSelf } from '@wendellhu/redi'; import { IDocUIPluginConfig } from '../Basics'; -import { UI } from '../View'; import { DocContainerUIController } from './DocContainerUIController'; export class AppUIController { @@ -13,22 +11,9 @@ export class AppUIController { _config: IDocUIPluginConfig, @Inject(LocaleService) private readonly _localeService: LocaleService, @SkipSelf() @Inject(ObserverManager) private readonly _globalObserverManager: ObserverManager, - @Inject(Injector) private readonly _injector: Injector, - @Inject(ComponentManager) private readonly _componentManager: ComponentManager + @Inject(Injector) private readonly _injector: Injector ) { this._docContainerController = this._injector.createInstance(DocContainerUIController, _config); - - const UIConfig = this._docContainerController.getUIConfig(); - - UI.create({ - injector: this._injector, - locale: this._localeService.getLocale().getCurrentLocale(), - localeService: this._localeService, - componentManager: this._componentManager, - changeLocale: this.changeLocale, - UIConfig, - container: _config.container, - }); } /** diff --git a/packages/ui-plugin-docs/src/Controller/DocContainerUIController.ts b/packages/ui-plugin-docs/src/Controller/DocContainerUIController.ts index fef8627074..89e73b655a 100644 --- a/packages/ui-plugin-docs/src/Controller/DocContainerUIController.ts +++ b/packages/ui-plugin-docs/src/Controller/DocContainerUIController.ts @@ -38,7 +38,9 @@ export class DocContainerUIController { } const engine = this._renderingEngine; - engine.setContainer(container); + // engine.setContainer(container); + + // TODO: rendering engine should handle this problem internally window.addEventListener('resize', () => { engine.resize(); diff --git a/packages/ui-plugin-docs/src/DocUIPlugin.ts b/packages/ui-plugin-docs/src/DocUIPlugin.ts index 975d0bc1b0..c0179c0882 100644 --- a/packages/ui-plugin-docs/src/DocUIPlugin.ts +++ b/packages/ui-plugin-docs/src/DocUIPlugin.ts @@ -1,4 +1,3 @@ -import { ComponentManager, RegisterManager } from '@univerjs/base-ui'; import { LocaleService, Plugin, PluginType, Tools } from '@univerjs/core'; import { Dependency, Inject, Injector } from '@wendellhu/redi'; @@ -12,10 +11,6 @@ export class DocUIPlugin extends Plugin { private _appUIController: AppUIController; - private _registerManager: RegisterManager; - - private _componentManager: ComponentManager; - constructor( private readonly _config: IDocUIPluginConfig, @Inject(Injector) override _injector: Injector, @@ -27,7 +22,7 @@ export class DocUIPlugin extends Plugin { this._initializeDependencies(_injector); } - initialize(): void { + override onMounted(): void { this._localService.getLocale().load({ en, zh, @@ -35,36 +30,13 @@ export class DocUIPlugin extends Plugin { installObserver(this); - this._componentManager = this._injector.get(ComponentManager); this._appUIController = this._injector.get(AppUIController); } - /** - * usage this._clipboardExtensionManager.handle(data); - * @returns - */ - getRegisterManager(): RegisterManager { - return this._registerManager; - } - - /** - * This API is used in plugins for initialization that depends on UI rendering - * @param cb - * @returns - */ - UIDidMount(cb: Function) { - this._appUIController.getDocContainerController().UIDidMount(cb); - } - - override onMounted(): void { - this.initialize(); - } - override onDestroy(): void {} private _initializeDependencies(injector: Injector) { const dependencies: Dependency[] = [ - [ComponentManager], [ AppUIController, { diff --git a/packages/ui-plugin-sheets/package.json b/packages/ui-plugin-sheets/package.json index fb51082ccb..9e9371b44c 100644 --- a/packages/ui-plugin-sheets/package.json +++ b/packages/ui-plugin-sheets/package.json @@ -54,6 +54,7 @@ "dependencies": { "@univerjs/base-render": "workspace:^0.0.1", "@univerjs/base-sheets": "workspace:^0.0.1", + "@univerjs/base-docs": "workspace:^0.0.1", "@univerjs/base-ui": "workspace:^0.0.1", "@univerjs/core": "workspace:^0.0.1", "@wendellhu/redi": "^0.12.7", @@ -62,4 +63,4 @@ "react-dom": "^18.2.0", "rxjs": "^7.8.1" } -} \ No newline at end of file +} diff --git a/packages/ui-plugin-sheets/src/Basics/Observer.ts b/packages/ui-plugin-sheets/src/Basics/Observer.ts index b2f1b73743..22670d69c9 100644 --- a/packages/ui-plugin-sheets/src/Basics/Observer.ts +++ b/packages/ui-plugin-sheets/src/Basics/Observer.ts @@ -1,4 +1,5 @@ import { Observable } from '@univerjs/core'; + import { SheetUIPlugin } from '../SheetUIPlugin'; import { SheetContainer } from '../View'; diff --git a/packages/ui-plugin-sheets/src/Controller/AppUIController.ts b/packages/ui-plugin-sheets/src/Controller/AppUIController.ts index 4a043da9ca..78dd1226e8 100644 --- a/packages/ui-plugin-sheets/src/Controller/AppUIController.ts +++ b/packages/ui-plugin-sheets/src/Controller/AppUIController.ts @@ -1,9 +1,8 @@ -import { LocaleType, ObserverManager, LocaleService } from '@univerjs/core'; +import { LocaleService, LocaleType, ObserverManager } from '@univerjs/core'; import { Inject, Injector, SkipSelf } from '@wendellhu/redi'; -import { ComponentManager, ZIndexManager } from '@univerjs/base-ui'; -import { UI } from '../View'; -import { SheetContainerUIController } from './SheetContainerUIController'; + import { ISheetUIPluginConfig } from '../Basics'; +import { SheetContainerUIController } from './SheetContainerUIController'; export class AppUIController { private _sheetContainerController: SheetContainerUIController; @@ -12,24 +11,10 @@ export class AppUIController { config: ISheetUIPluginConfig, @Inject(Injector) private readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService, - @SkipSelf() @Inject(ObserverManager) private readonly _globalObserverManager: ObserverManager, - @Inject(ComponentManager) private readonly _componentManager: ComponentManager, - @Inject(ZIndexManager) private readonly _zIndexManager: ZIndexManager + @SkipSelf() @Inject(ObserverManager) private readonly _globalObserverManager: ObserverManager ) { this._sheetContainerController = this._injector.createInstance(SheetContainerUIController, config); this._injector.add([SheetContainerUIController, { useValue: this._sheetContainerController }]); - - const UIConfig = this._sheetContainerController.getUIConfig(); - - UI.create({ - injector: this._injector, - locale: this._localeService.getLocale().getCurrentLocale(), - componentManager: this._componentManager, - zIndexManager: this._zIndexManager, - changeLocale: this.changeLocale, - UIConfig, - container: config.container, - }); } /** diff --git a/packages/ui-plugin-sheets/src/Controller/CellEditorUIController.ts b/packages/ui-plugin-sheets/src/Controller/CellEditorUIController.ts deleted file mode 100644 index f0cd841b0a..0000000000 --- a/packages/ui-plugin-sheets/src/Controller/CellEditorUIController.ts +++ /dev/null @@ -1,466 +0,0 @@ -import { Engine, IMouseEvent, IPointerEvent, IRenderingEngine, ISelectionRangeWithStyle, ISelectionTransformerShapeManager, mergeCellHandler } from '@univerjs/base-render'; -import { CANVAS_VIEW_KEY, CanvasView, CellEditorController, SelectionManagerService } from '@univerjs/base-sheets'; -import { $$, CellEditExtensionManager, handleDomToJson, handleStringToStyle, isCtrlPressed, KeyboardManager, setLastCaretPosition } from '@univerjs/base-ui'; -import { - Direction, - handleStyleToString, - ICellData, - ICurrentUniverService, - IRangeData, - isKeyPrintable, - makeCellRangeToRangeData, - Nullable, - ObserverManager, - Tools, - UIObserver, -} from '@univerjs/core'; -import { Inject, SkipSelf } from '@wendellhu/redi'; -import { RefObject } from 'react'; - -import { RichText } from '../View/RichText'; - -export class CellEditorUIController { - // Is it in editing state - isEditMode: boolean; - - _richTextEle: HTMLElement; - - _richTextEditEle: HTMLElement; - - _richText: RichText; - - private _cellEditExtensionManager: CellEditExtensionManager; - - constructor( - private readonly _currentRefFetcher: () => RefObject, - @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, - @SkipSelf() @Inject(ObserverManager) private readonly _globalObserverManager: ObserverManager, - @IRenderingEngine private readonly _renderingEngine: Engine, - @Inject(CanvasView) private readonly _sheetCanvasView: CanvasView, - @Inject(ObserverManager) private readonly _selfObserverManager: ObserverManager, - @Inject(CellEditorController) private readonly _cellEditorController: CellEditorController, - // @ISelectionManager private readonly _selectionManager: SelectionManager, - @Inject(SelectionManagerService) private readonly _selectionManagerService: SelectionManagerService, - @ISelectionTransformerShapeManager private readonly _selectionTransformerShapeManager: ISelectionTransformerShapeManager, - @Inject(KeyboardManager) private readonly _keyboardManager: KeyboardManager - ) { - this._initialize(); - } - - listenEventManager() { - this._globalObserverManager.requiredObserver>('onUIChangeObservable', 'core').add((msg) => { - const value = msg.value; - switch (msg.name) { - case 'fontWeight': - // this.setUndo(); - console.info('fontWeightfontWeight====', msg); - this._richText.cellTextStyle.updateFormat('bl', value ? '1' : '0'); - break; - case 'redo': - // this.setRedo(); - break; - } - }); - } - - // Get the RichText component - getComponent = (ref: RichText) => { - this._richText = ref; - this.setRichText(); - }; - - getCellEditor() { - return this._richText; - } - - hideEditContainer() { - // It cannot be set to 0px, otherwise the paste event cannot be listened - this._richTextEle.style.maxHeight = '1px'; - this._richTextEle.style.maxWidth = '1px'; - - this._richTextEle.style.minWidth = `1px`; - this._richTextEle.style.minHeight = `1px`; - - this._richTextEle.style.borderWidth = '0px'; - this._richTextEle.style.transform = 'scale(0)'; - } - - /** - * 1. When a printable character is entered, trigger editing - * 2. When CompositionStart, trigger editing - * @param clear Whether to clear the cell - * @returns - */ - // eslint-disable-next-line max-lines-per-function - enterEditMode(clear: boolean = false) { - this.focusEditEle(); - // setTimeout(() => { - // this._richTextEditEle.focus(); - - // }, 1); - - if (this.isEditMode) return; - - // set focus to last position - setTimeout(() => { - setLastCaretPosition(this._richTextEditEle); - }, 1); - - this.isEditMode = true; - this._cellEditorController.setEditMode(this.isEditMode); - - const cellRange = this._selectionManagerService.getLast()?.cellRange; - - const currentCell = this._selectionTransformerShapeManager.convertCellRangeToInfo(cellRange); - - if (!currentCell) { - return false; - } - - let startX; - let endX; - let startY; - let endY; - - if (currentCell.isMerged) { - const mergeInfo = currentCell.mergeInfo; - startX = mergeInfo.startX; - endX = mergeInfo.endX; - startY = mergeInfo.startY; - endY = mergeInfo.endY; - } else { - startX = currentCell.startX; - endX = currentCell.endX; - startY = currentCell.startY; - endY = currentCell.endY; - } - - const mainScene = this._renderingEngine.getScene(CANVAS_VIEW_KEY.MAIN_SCENE); - const scrollX = mainScene?.getViewport(CANVAS_VIEW_KEY.VIEW_TOP)?.actualScrollX || 0; - const scrollY = mainScene?.getViewport(CANVAS_VIEW_KEY.VIEW_LEFT)?.actualScrollY || 0; - - this._richTextEle.style.left = `${startX - scrollX}px`; - this._richTextEle.style.top = `${startY - scrollY}px`; - - this._richTextEle.style.minWidth = `${endX - startX}px`; - this._richTextEle.style.minHeight = `${endY - startY}px`; - - this._richTextEle.style.borderWidth = '2px'; - const univerContainerContentRef = this._currentRefFetcher(); - const sheetContentRect = univerContainerContentRef.current?.getBoundingClientRect(); - - if (!sheetContentRect) return; - - this._richTextEle.style.maxWidth = `${sheetContentRect.width - startX + scrollX}px`; - this._richTextEle.style.maxHeight = `${sheetContentRect.height - startY + scrollY}px`; - - this._richTextEle.style.transform = ''; - - // this._plugin.showMainByName('cellEditor', true).then(() => { - let cellValue = this._cellEditorController.getSelectionValue(); - - // Intercept, the formula needs to modify the value of the edit state - const cell = this._cellEditExtensionManager.handle({ - row: currentCell.row, - column: currentCell.column, - value: cellValue, - }); - - if (cell) { - cellValue = cell.value; - } - - if (clear) { - cellValue = ''; - } - this._richText.setValue(cellValue); - - const style = this._cellEditorController.getSelectionStyle(); - - this._richTextEditEle.style.cssText = ''; - - // set cell style - if (style) { - this._richTextEditEle.style.cssText = handleStyleToString(style, true); - } - - // }); - this._cellEditorController.setCurrentEditRangeData(); - } - - exitEditMode() { - this.focusEditEle(); - - if (!this.isEditMode) return; - - this.isEditMode = false; - this._cellEditorController.setEditMode(this.isEditMode); - // this._plugin.showMainByName('cellEditor', false).then(() => { - const value = handleDomToJson(this._richTextEditEle); - const text = this._richTextEditEle.innerText; - - const cell: ICellData = {}; - - // get value - if (typeof value === 'string') { - cell.v = value; - cell.m = value; - } - // get rich text - else if (typeof value === 'object') { - cell.p = value; - cell.v = text; - cell.m = text; - } - - // get style - const style = handleStringToStyle(this._richTextEditEle); - if (Tools.isPlainObject(style)) { - cell.s = style; - } - this._cellEditorController.setCurrentEditRangeValue(cell); - this.hideEditContainer(); - } - - focusEditEle() { - // If there is no settimeout, the first letter will be intercepted in Chinese state - setTimeout(() => { - this._richTextEditEle.focus(); - }, 100); - } - - getRichTextEle() { - return this._richTextEle; - } - - getRichTextEditEle() { - return this._richTextEditEle; - } - - private _initialize() { - // this._initRegisterComponent(); - // If other plugins are loaded asynchronously, they may be initialized after the rendering layer is loaded, and they will not receive obs listeners. - - const main = this._sheetCanvasView.getSheetView().getSpreadsheet(); - - main.onDblclickObserver.add((evt: IPointerEvent | IMouseEvent) => { - // Prevent left + right double click - if (evt.button !== 2) { - this.enterEditMode(); - } - }); - main.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent) => { - this.exitEditMode(); - evt.preventDefault(); - }); - - this._cellEditExtensionManager = new CellEditExtensionManager(); - - this.listenEventManager(); - } - - private setRichText() { - const richText = this._richText.container.current; - if (!richText) return; - this._richTextEle = richText; - this._richTextEditEle = $$('div', this._richTextEle); - - // // focus - this._richTextEditEle.focus(); - - // this.focusEditEle(); - this.hideEditContainer(); - - // init event - this._keyboardManager.handleKeyboardAction(this._richTextEditEle); - this._handleKeyboardObserver(); - - // Get the display status of the formula prompt box - this._richText.hooks.set('onKeyDown', (event: KeyboardEvent) => { - this._selfObserverManager.getObserver('onRichTextKeyDownObservable')?.notifyObservers(event); - }); - // Get the display status of the formula prompt box - this._richText.hooks.set('onKeyUp', (event: KeyboardEvent) => { - this._selfObserverManager.getObserver('onRichTextKeyUpObservable')?.notifyObservers(event); - }); - } - - private _handleKeyboardObserver() { - const onKeyDownObservable = this._globalObserverManager.getObserver('onKeyDownObservable', 'core'); - const onKeyCompositionStartObservable = this._globalObserverManager.getObserver('onKeyCompositionStartObservable', 'core'); - - const selectionManager = this._selectionManagerService; - if (onKeyDownObservable && !onKeyDownObservable.hasObservers()) { - onKeyDownObservable.add((evt: KeyboardEvent) => { - if (!isCtrlPressed(evt) && isKeyPrintable(evt.key)) { - // character key - this.enterEditMode(true); - } else { - // control key - switch (evt.key) { - case 'Enter': - if (this.isEditMode) { - this.exitEditMode(); - - this._move(Direction.DOWN); - } else { - this.enterEditMode(); - } - break; - - case 'Space': - if (!this.isEditMode) { - this.enterEditMode(true); - } - break; - - case 'ArrowUp': - if (!this.isEditMode) { - this._move(Direction.UP); - } - break; - - case 'ArrowDown': - if (!this.isEditMode) { - this._move(Direction.DOWN); - } - break; - - case 'ArrowLeft': - if (!this.isEditMode) { - this._move(Direction.LEFT); - } - break; - - case 'ArrowRight': - if (!this.isEditMode) { - this._move(Direction.RIGHT); - } - break; - - case 'Tab': - if (!this.isEditMode) { - this._move(Direction.RIGHT); - } - evt.preventDefault(); - break; - - default: - break; - } - } - }); - } - - if (onKeyCompositionStartObservable && !onKeyCompositionStartObservable.hasObservers()) { - onKeyCompositionStartObservable.add((evt: CompositionEvent) => { - if (!this.isEditMode) { - this.enterEditMode(true); - } - }); - } - } - - private _move(direction: Direction) { - const worksheet = this._currentUniverService.getCurrentUniverSheetInstance().getWorkBook().getActiveSheet(); - const rowCount = worksheet.getRowCount(); - const columnCount = worksheet.getColumnCount(); - const mergeData = worksheet.getMergeData(); - const selectionData = this._selectionManagerService.getLast(); - - const moveSelectionData = this._getMoveCellInfo(direction, rowCount, columnCount, mergeData, selectionData); - - if (moveSelectionData != null) { - // move to cell below - this._selectionManagerService.replace([moveSelectionData]); - } - } - - /** - * Move the selection according to different directions, usually used for the shortcut key operation of ↑ ↓ ← → - * @param direction - * @returns - */ - // eslint-disable-next-line max-lines-per-function - private _getMoveCellInfo( - direction: Direction, - rowCount: number, - columnCount: number, - mergeData: IRangeData[], - selectionData: Nullable - ): Nullable { - const cellRange = selectionData?.cellRange; - - const style = selectionData?.style; - - if (!cellRange) return; - - let { startRow: mergeStartRow, startColumn: mergeStartColumn, endRow: mergeEndRow, endColumn: mergeEndColumn } = cellRange; - - let { row, column } = cellRange; - // const rowCount = this._skeleton?.getRowCount() || DEFAULT_WORKSHEET_ROW_COUNT; - // const columnCount = this._skeleton?.getColumnCount() || DEFAULT_WORKSHEET_COLUMN_COUNT; - switch (direction) { - case Direction.UP: - if (cellRange.isMerged || cellRange.isMergedMainCell) { - row = --mergeStartRow; - } else { - row--; - } - if (row < 0) { - row = 0; - } - break; - case Direction.DOWN: - if (cellRange.isMerged || cellRange.isMergedMainCell) { - row = ++mergeEndRow; - } else { - row++; - } - - if (row > rowCount) { - row = rowCount; - } - break; - case Direction.LEFT: - if (cellRange.isMerged || cellRange.isMergedMainCell) { - column = --mergeStartColumn; - } else { - column--; - } - - if (column < 0) { - column = 0; - } - break; - case Direction.RIGHT: - if (cellRange.isMerged || cellRange.isMergedMainCell) { - column = ++mergeEndColumn; - } else { - column++; - } - - if (column > columnCount) { - column = columnCount; - } - break; - - default: - break; - } - - const newCellRange = mergeCellHandler(row, column, mergeData); - - const newSelectionData = makeCellRangeToRangeData(newCellRange); - - if (!newSelectionData) { - return; - } - - return { - rangeData: newSelectionData, - cellRange: newCellRange, - style, - }; - } -} diff --git a/packages/ui-plugin-sheets/src/Controller/RightMenuUIController.ts b/packages/ui-plugin-sheets/src/Controller/RightMenuUIController.ts index d41a9b26ca..77e6a4cd7f 100644 --- a/packages/ui-plugin-sheets/src/Controller/RightMenuUIController.ts +++ b/packages/ui-plugin-sheets/src/Controller/RightMenuUIController.ts @@ -1,4 +1,3 @@ -import { IMouseEvent, IPointerEvent } from '@univerjs/base-render'; import { CanvasView } from '@univerjs/base-sheets'; import { BaseMenuItem, BaseSelectChildrenProps, ComponentManager, IMenuItemFactory, IMenuService, MenuPosition } from '@univerjs/base-ui'; import { Disposable } from '@univerjs/core'; @@ -64,24 +63,29 @@ export class RightMenuUIController extends Disposable { }; // 刷新 + /** + * @deprecated right menu component should listen to changes in menu service + */ setMenuList() { this._rightMenu?.setMenuListNeo(this._menuService.getMenuItems(MenuPosition.CONTEXT_MENU)); } private _initialize() { + // TODO: add these menu items to base-ui const componentManager = this._componentManager; componentManager.register(RightMenuInput.name, RightMenuInput); componentManager.register(RightMenuItem.name, RightMenuItem); - this._sheetCanvasView - .getSheetView() - .getSpreadsheet() - .onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent) => { - if (evt.button === 2) { - evt.preventDefault(); - this._rightMenu.handleContextMenu(evt); - } - }); + // // add context menu response to base-ui + // this._sheetCanvasView + // .getSheetView() + // .getSpreadsheet() + // .onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent) => { + // if (evt.button === 2) { + // evt.preventDefault(); + // this._rightMenu.handleContextMenu(evt); + // } + // }); ( [ diff --git a/packages/ui-plugin-sheets/src/Controller/SheetContainerUIController.ts b/packages/ui-plugin-sheets/src/Controller/SheetContainerUIController.ts index b9475c5924..8813f52f24 100644 --- a/packages/ui-plugin-sheets/src/Controller/SheetContainerUIController.ts +++ b/packages/ui-plugin-sheets/src/Controller/SheetContainerUIController.ts @@ -4,7 +4,6 @@ import { Inject, Injector, Self, SkipSelf } from '@wendellhu/redi'; import { ISheetUIPluginConfig } from '../Basics/Interfaces/ComponentConfig/ISheetUIPluginConfig'; import { SheetContainer } from '../View'; -import { CellEditorUIController } from './CellEditorUIController'; import { CountBarUIController } from './CountBarUIController'; import { FormulaBarUIController } from './FormulaBarUIController'; import { InfoBarUIController } from './InfoBarUIController'; @@ -23,8 +22,6 @@ export class SheetContainerUIController { // private _slotManager: SlotManager; - private _cellEditorUIController: CellEditorUIController; - private _formulaBarUIController: FormulaBarUIController; private _infoBarController: InfoBarUIController; @@ -52,7 +49,6 @@ export class SheetContainerUIController { this._slotController = new SlotController(); // this._slotManager = new SlotManager(); this._toolbarController = this._injector.createInstance(ToolbarUIController, this._config.layout?.toolbarConfig); - this._cellEditorUIController = this._injector.createInstance(CellEditorUIController, () => this.getContentRef()); this._formulaBarUIController = this._injector.createInstance(FormulaBarUIController); this._infoBarController = this._injector.createInstance(InfoBarUIController); this._rightMenuController = this._injector.createInstance(RightMenuUIController, this._config.layout?.rightMenuConfig); @@ -72,9 +68,6 @@ export class SheetContainerUIController { toolbar: { getComponent: this._toolbarController.getComponent, }, - cellEditor: { - getComponent: this._cellEditorUIController.getComponent, - }, formulaBar: { getComponent: this._formulaBarUIController.getComponent, }, @@ -133,10 +126,6 @@ export class SheetContainerUIController { return this._sheetContainer.getContentRef(); } - getCellEditorUIController() { - return this._cellEditorUIController; - } - getFormulaBarUIController() { return this._formulaBarUIController; } diff --git a/packages/ui-plugin-sheets/src/Controller/ToolbarUIController.ts b/packages/ui-plugin-sheets/src/Controller/ToolbarUIController.ts index 42c574f6ea..6587d32975 100644 --- a/packages/ui-plugin-sheets/src/Controller/ToolbarUIController.ts +++ b/packages/ui-plugin-sheets/src/Controller/ToolbarUIController.ts @@ -66,6 +66,7 @@ export class ToolbarUIController extends Disposable { super(); this._initialize(); + this._initializeToolbar(); } // 获取Toolbar组件 diff --git a/packages/ui-plugin-sheets/src/Controller/menu.ts b/packages/ui-plugin-sheets/src/Controller/menu.ts index d96eb86b3c..a90c9229f4 100644 --- a/packages/ui-plugin-sheets/src/Controller/menu.ts +++ b/packages/ui-plugin-sheets/src/Controller/menu.ts @@ -54,7 +54,6 @@ import { SHEET_UI_PLUGIN_NAME } from '../Basics/Const/PLUGIN_NAME'; import { RenameSheetCommand } from '../commands/rename.command'; import { ShowMenuListCommand } from '../commands/unhide.command'; import { RightMenuInput } from '../View'; -import { FONT_FAMILY_CHILDREN, FONT_SIZE_CHILDREN, HORIZONTAL_ALIGN_CHILDREN, TEXT_ROTATE_CHILDREN, TEXT_WRAP_CHILDREN, VERTICAL_ALIGN_CHILDREN } from '../View/Toolbar/Const'; export { SetBorderColorMenuItemFactory, SetBorderStyleMenuItemFactory } from './menu/border.menu'; @@ -299,6 +298,154 @@ export function StrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonIt }; } +export const FONT_SIZE_CHILDREN = [ + { + label: '9', + value: 9, + }, + { + label: '10', + value: 10, + }, + { + label: '11', + value: 11, + }, + { + label: '12', + value: 12, + }, + { + label: '14', + value: 14, + }, + { + label: '16', + value: 16, + }, + { + label: '18', + value: 18, + }, + { + label: '20', + value: 20, + }, + + { + label: '22', + value: 22, + }, + { + label: '24', + value: 24, + }, + { + label: '26', + value: 26, + }, + { + label: '28', + value: 28, + }, + { + label: '36', + value: 36, + }, + { + label: '48', + value: 48, + }, + { + label: '72', + value: 72, + }, +]; + +export const FONT_FAMILY_CHILDREN = [ + { + label: 'fontFamily.TimesNewRoman', + style: { 'font-family': 'Times New Roman' }, + value: 'Times New Roman', + selected: true, + }, + { + label: 'fontFamily.Arial', + style: { 'font-family': 'Arial' }, + value: 'Arial', + }, + { + label: 'fontFamily.Tahoma', + style: { 'font-family': 'Tahoma' }, + value: 'Tahoma', + }, + { + label: 'fontFamily.Verdana', + style: { 'font-family': 'Verdana' }, + value: 'Verdana', + }, + { + label: 'fontFamily.MicrosoftAccorblack', + style: { 'font-family': '微软雅黑' }, + value: '微软雅黑', + }, + { + label: 'fontFamily.SimSun', + style: { 'font-family': '宋体' }, + value: '宋体', + }, + { + label: 'fontFamily.SimHei', + style: { 'font-family': '黑体' }, + value: '黑体', + }, + { + label: 'fontFamily.Kaiti', + style: { 'font-family': '楷体' }, + value: '楷体', + }, + { + label: 'fontFamily.FangSong', + style: { 'font-family': '仿宋' }, + value: '仿宋', + }, + { + label: 'fontFamily.NSimSun', + style: { 'font-family': '新宋体' }, + value: '新宋体', + }, + { + label: 'fontFamily.STXinwei', + style: { 'font-family': '华文新魏' }, + value: '华文新魏', + }, + { + label: 'fontFamily.STXingkai', + style: { 'font-family': '华文行楷' }, + value: '华文行楷', + }, + { + label: 'fontFamily.STLiti', + style: { 'font-family': '华文隶书' }, + value: '华文隶书', + }, + { + label: 'fontFamily.HanaleiFill', + style: { 'font-family': 'HanaleiFill' }, + value: 'HanaleiFill', + }, + { + label: 'fontFamily.Anton', + style: { 'font-family': 'Anton' }, + value: 'Anton', + }, + { + label: 'fontFamily.Pacifico', + style: { 'font-family': 'Pacifico' }, + value: 'Pacifico', + }, +]; + export function FontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const permissionService = accessor.get(IPermissionService); const commandService = accessor.get(ICommandService); @@ -497,11 +644,23 @@ export function BackgroundColorSelectorMenuItemFactory(accessor: IAccessor): IMe }; } -// Merge cell command is not ready yet. -// export function MergeCellMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { -// return { -// } -// } +export const HORIZONTAL_ALIGN_CHILDREN = [ + { + label: 'align.left', + icon: 'LeftAlignIcon', + value: HorizontalAlign.LEFT, + }, + { + label: 'align.center', + icon: 'CenterAlignIcon', + value: HorizontalAlign.CENTER, + }, + { + label: 'align.right', + icon: 'RightAlignIcon', + value: HorizontalAlign.RIGHT, + }, +]; export function HorizontalAlignMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const currentUniverService = accessor.get(ICurrentUniverService); @@ -541,6 +700,24 @@ export function HorizontalAlignMenuItemFactory(accessor: IAccessor): IMenuSelect }; } +export const VERTICAL_ALIGN_CHILDREN = [ + { + label: 'align.top', + icon: 'TopVerticalIcon', + value: VerticalAlign.TOP, + }, + { + label: 'align.middle', + icon: 'CenterVerticalIcon', + value: VerticalAlign.MIDDLE, + }, + { + label: 'align.bottom', + icon: 'BottomVerticalIcon', + value: VerticalAlign.BOTTOM, + }, +]; + export function VerticalAlignMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const currentUniverService = accessor.get(ICurrentUniverService); const selectionManagerService = accessor.get(SelectionManagerService); @@ -579,6 +756,24 @@ export function VerticalAlignMenuItemFactory(accessor: IAccessor): IMenuSelector }; } +export const TEXT_WRAP_CHILDREN = [ + { + label: 'textWrap.overflow', + icon: 'OverflowIcon', + value: WrapStrategy.OVERFLOW, + }, + { + label: 'textWrap.wrap', + icon: 'BrIcon', + value: WrapStrategy.WRAP, + }, + { + label: 'textWrap.clip', + icon: 'CutIcon', + value: WrapStrategy.CLIP, + }, +]; + export function WrapTextMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const currentUniverService = accessor.get(ICurrentUniverService); const selectionManagerService = accessor.get(SelectionManagerService); @@ -617,6 +812,39 @@ export function WrapTextMenuItemFactory(accessor: IAccessor): IMenuSelectorItem< }; } +export const TEXT_ROTATE_CHILDREN = [ + { + label: 'textRotate.none', + icon: 'TextRotateIcon', + value: 0, + }, + { + label: 'textRotate.angleUp', + icon: 'TextRotateAngleUpIcon', + value: -45, + }, + { + label: 'textRotate.angleDown', + icon: 'TextRotateAngleDownIcon', + value: 45, + }, + { + label: 'textRotate.vertical', + icon: 'TextRotateVerticalIcon', + value: 'v', + }, + { + label: 'textRotate.rotationUp', + icon: 'TextRotateRotationUpIcon', + value: -90, + }, + { + label: 'textRotate.rotationDown', + icon: 'TextRotateRotationDownIcon', + value: 90, + }, +]; + export function TextRotateMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const currentUniverService = accessor.get(ICurrentUniverService); const selectionManagerService = accessor.get(SelectionManagerService); diff --git a/packages/ui-plugin-sheets/src/Controller/shortcut.controller.ts b/packages/ui-plugin-sheets/src/Controller/shortcut.controller.ts index 7ffd0beb9d..994922b266 100644 --- a/packages/ui-plugin-sheets/src/Controller/shortcut.controller.ts +++ b/packages/ui-plugin-sheets/src/Controller/shortcut.controller.ts @@ -11,6 +11,8 @@ import { import { IShortcutService } from '@univerjs/base-ui'; import { Disposable, ICommandService } from '@univerjs/core'; +import { QuitCellEditorCommand } from '../services/cell-editor/cell-editor.command'; +import { QuitCellEditorShortcutItem } from '../services/shortcuts/shortcuts'; import { ExpandSelectionDownShortcutItem, ExpandSelectionEndDownShortcutItem, @@ -27,6 +29,7 @@ import { MoveSelectionEndUpShortcutItem, MoveSelectionLeftShortcutItem, MoveSelectionRightShortcutItem, + MoveSelectionTabShortcutItem, MoveSelectionUpShortcutItem, } from './shortcuts/selection.shortcut'; import { SetBoldShortcutItem, SetItalicShortcutItem, SetStrikeThroughShortcutItem, SetUnderlineShortcutItem } from './shortcuts/style.shortcut'; @@ -45,6 +48,8 @@ export class DesktopSheetShortcutController extends Disposable { SetUnderlineCommand, SetFontFamilyCommand, SetFontSizeCommand, + + QuitCellEditorCommand, ].forEach((c) => { this.disposeWithMe(this._commandService.registerCommand(c)); }); @@ -54,6 +59,7 @@ export class DesktopSheetShortcutController extends Disposable { MoveSelectionUpShortcutItem, MoveSelectionLeftShortcutItem, MoveSelectionRightShortcutItem, + MoveSelectionTabShortcutItem, MoveSelectionEndDownShortcutItem, MoveSelectionEndUpShortcutItem, MoveSelectionEndLeftShortcutItem, @@ -73,6 +79,8 @@ export class DesktopSheetShortcutController extends Disposable { SetStrikeThroughShortcutItem, ClearSelectionValueShortcutItem, + + QuitCellEditorShortcutItem, ].forEach((item) => { this.disposeWithMe(this._shortcutService.registerShortcut(item)); }); diff --git a/packages/ui-plugin-sheets/src/Controller/shortcuts/selection.shortcut.ts b/packages/ui-plugin-sheets/src/Controller/shortcuts/selection.shortcut.ts index 5f3bb6854b..7f2abffb37 100644 --- a/packages/ui-plugin-sheets/src/Controller/shortcuts/selection.shortcut.ts +++ b/packages/ui-plugin-sheets/src/Controller/shortcuts/selection.shortcut.ts @@ -1,10 +1,11 @@ import { ChangeSelectionCommand, ExpandSelectionCommand, IChangeSelectionCommandParams, IExpandSelectionCommandParams } from '@univerjs/base-sheets'; -import { IShortcutItem, KeyCode, MetaKeys } from '@univerjs/base-ui'; +import { FOCUSING_SHEET, IShortcutItem, KeyCode, MetaKeys } from '@univerjs/base-ui'; import { Direction } from '@univerjs/core'; export const MoveSelectionDownShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_DOWN, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.DOWN, }, @@ -13,6 +14,7 @@ export const MoveSelectionDownShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_UP, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.UP, }, @@ -21,6 +23,7 @@ export const MoveSelectionUpShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_LEFT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.LEFT, }, @@ -29,6 +32,16 @@ export const MoveSelectionLeftShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_RIGHT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), + staticParameters: { + direction: Direction.RIGHT, + }, +}; + +export const MoveSelectionTabShortcutItem: IShortcutItem = { + id: ChangeSelectionCommand.id, + binding: KeyCode.TAB, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.RIGHT, }, @@ -39,6 +52,7 @@ export const MoveSelectionRightShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_DOWN | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.LEFT, toEnd: true, @@ -48,6 +62,7 @@ export const MoveSelectionEndDownShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_UP | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.UP, toEnd: true, @@ -57,6 +72,7 @@ export const MoveSelectionEndUpShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_LEFT | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.LEFT, toEnd: true, @@ -66,6 +82,7 @@ export const MoveSelectionEndLeftShortcutItem: IShortcutItem = { id: ChangeSelectionCommand.id, binding: KeyCode.ARROW_RIGHT | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.RIGHT, toEnd: true, @@ -77,6 +94,7 @@ export const MoveSelectionEndRightShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_DOWN | MetaKeys.SHIFT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.DOWN, }, @@ -85,6 +103,7 @@ export const ExpandSelectionDownShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_UP | MetaKeys.SHIFT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.UP, }, @@ -93,6 +112,7 @@ export const ExpandSelectionUpShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_LEFT | MetaKeys.SHIFT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.LEFT, }, @@ -101,6 +121,7 @@ export const ExpandSelectionLeftShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_RIGHT | MetaKeys.SHIFT, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.RIGHT, }, @@ -111,6 +132,7 @@ export const ExpandSelectionRightShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_DOWN | MetaKeys.SHIFT | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.DOWN, toEnd: true, @@ -120,6 +142,7 @@ export const ExpandSelectionEndDownShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_UP | MetaKeys.SHIFT | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.UP, toEnd: true, @@ -129,6 +152,7 @@ export const ExpandSelectionEndUpShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_LEFT | MetaKeys.SHIFT | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.LEFT, toEnd: true, @@ -138,6 +162,7 @@ export const ExpandSelectionEndLeftShortcutItem: IShortcutItem = { id: ExpandSelectionCommand.id, binding: KeyCode.ARROW_RIGHT | MetaKeys.SHIFT | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), staticParameters: { direction: Direction.RIGHT, toEnd: true, diff --git a/packages/ui-plugin-sheets/src/Controller/shortcuts/value.shortcut.ts b/packages/ui-plugin-sheets/src/Controller/shortcuts/value.shortcut.ts index 3b42a5c9a3..226c6dd06a 100644 --- a/packages/ui-plugin-sheets/src/Controller/shortcuts/value.shortcut.ts +++ b/packages/ui-plugin-sheets/src/Controller/shortcuts/value.shortcut.ts @@ -1,7 +1,8 @@ import { ClearSelectionContentCommand } from '@univerjs/base-sheets'; -import { IShortcutItem, KeyCode } from '@univerjs/base-ui'; +import { FOCUSING_SHEET, IShortcutItem, KeyCode } from '@univerjs/base-ui'; export const ClearSelectionValueShortcutItem: IShortcutItem = { id: ClearSelectionContentCommand.id, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET), binding: KeyCode.BACKSPACE, }; diff --git a/packages/ui-plugin-sheets/src/SheetUIPlugin.ts b/packages/ui-plugin-sheets/src/SheetUIPlugin.ts index 39f6860c4e..9cacb27aa8 100644 --- a/packages/ui-plugin-sheets/src/SheetUIPlugin.ts +++ b/packages/ui-plugin-sheets/src/SheetUIPlugin.ts @@ -1,27 +1,13 @@ -import { IRenderingEngine } from '@univerjs/base-render'; -import { - ComponentManager, - DesktopMenuService, - DesktopPlatformService, - DesktopShortcutService, - DragManager, - IMenuService, - IPlatformService, - IShortcutService, - KeyboardManager, - SharedController, - SlotComponent, - SlotManager, - ZIndexManager, -} from '@univerjs/base-ui'; -import { IUndoRedoService, LocaleService, Plugin, PluginType, Tools } from '@univerjs/core'; +import { DragManager, SharedController, SlotManager, ZIndexManager } from '@univerjs/base-ui'; +import { ICurrentUniverService, IUndoRedoService, LocaleService, Plugin, PluginType, Tools } from '@univerjs/core'; import { Dependency, Inject, Injector } from '@wendellhu/redi'; import { DefaultSheetUIConfig, installObserver, ISheetUIPluginConfig, SHEET_UI_PLUGIN_NAME, SheetUIPluginObserve } from './Basics'; import { AppUIController } from './Controller/AppUIController'; import { DesktopSheetShortcutController } from './Controller/shortcut.controller'; -import { SlotComponentProps } from './Controller/SlotController'; import { en, zh } from './Locale'; +import { ICellEditorService } from './services/cell-editor/cell-editor.service'; +import { DesktopCellEditorService } from './services/cell-editor/cell-editor-desktop.service'; import { SheetBarService } from './services/sheet-bar.service'; import { Fx } from './View/FormulaBar'; @@ -30,23 +16,19 @@ export class SheetUIPlugin extends Plugin { private _appUIController: AppUIController; - private _keyboardManager: KeyboardManager; - private _config: ISheetUIPluginConfig; private _zIndexManager: ZIndexManager; private _dragManager: DragManager; - private _componentManager: ComponentManager; - constructor(config: ISheetUIPluginConfig, @Inject(Injector) override readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService) { super(SHEET_UI_PLUGIN_NAME); this._config = Tools.deepMerge({}, DefaultSheetUIConfig, config); } - initialize(): void { + override onMounted(): void { installObserver(this); /** @@ -59,39 +41,8 @@ export class SheetUIPlugin extends Plugin { en, }); - this.initializeDependencies(); - - // AppUIController initializes the DOM as an asynchronous rendering process, and must wait for the UI rendering to complete before starting to render the canvas - this.UIDidMount(() => { - this.initRender(); - }); - } - - initRender() { - const engine = this._injector.get(IRenderingEngine); - const container = this._appUIController.getSheetContainerController().getContentRef().current; - - if (!container) { - throw new Error('container is not ready'); - } - - // mount canvas to DOM container - engine.setContainer(container); - - window.addEventListener('resize', () => { - engine.resize(); - }); - - // should be clear - setTimeout(() => { - engine.resize(); - }, 0); - } - - initUI() {} - - override onMounted(): void { - this.initialize(); + this.initDependencies(); + this._markSheetAsFocused(); } override onDestroy(): void {} @@ -100,22 +51,10 @@ export class SheetUIPlugin extends Plugin { return this._appUIController; } - getComponentManager() { - return this._componentManager; - } - getZIndexManager() { return this._zIndexManager; } - /** - * usage this._clipboardExtensionManager.handle(data); - * @returns - */ - getKeyboardManager(): KeyboardManager { - return this._keyboardManager; - } - /** * Formula Bar API * @param str @@ -128,46 +67,25 @@ export class SheetUIPlugin extends Plugin { this._appUIController.getSheetContainerController().getFormulaBarUIController().getFormulaBar().setFx(fx); } - /** - * This API is used in plugins for initialization that depends on UI rendering - * @param cb - * @returns - */ - UIDidMount(cb: Function) { - this._appUIController.getSheetContainerController().UIDidMount(cb); + private _markSheetAsFocused() { + const currentService = this._injector.get(ICurrentUniverService); + const c = currentService.getCurrentUniverSheetInstance(); + currentService.focusUniverInstance(c.getUnitId()); } - setSlot(slotName: string, component: SlotComponent, cb?: () => {}) { - this._appUIController.getSheetContainerController().getSlotManager().setSlotComponent(slotName, component, cb); - } - - addSlot(name: string, slot: SlotComponentProps, cb?: () => void) { - this._appUIController.getSheetContainerController().getMainSlotController().addSlot(name, slot, cb); - } - - getSlot(name: string) { - // return this._appUIController.getSheetContainerController().getMainSlotController().getSlot(name); - } - - private initializeDependencies(): void { - const dependencies: Dependency[] = [ - [DragManager], - [KeyboardManager], - [ComponentManager], - [ZIndexManager], - [SlotManager], - [IShortcutService, { useClass: DesktopShortcutService }], - [IPlatformService, { useClass: DesktopPlatformService }], - [SharedController], - [IMenuService, { useClass: DesktopMenuService }], - [DesktopSheetShortcutController], - [SheetBarService], - ]; - dependencies.forEach((d) => this._injector.add(d)); + private initDependencies(): void { + ( + [ + [DragManager], + [ZIndexManager], + [SlotManager], + [DesktopSheetShortcutController], + [SheetBarService], + [ICellEditorService, { useClass: DesktopCellEditorService }], + ] as Dependency[] + ).forEach((d) => this._injector.add(d)); this._dragManager = this._injector.get(DragManager); - this._componentManager = this._injector.get(ComponentManager); - this._keyboardManager = this._injector.get(KeyboardManager); this._zIndexManager = this._injector.get(ZIndexManager); this._appUIController = this._injector.createInstance(AppUIController, this._config); @@ -176,5 +94,7 @@ export class SheetUIPlugin extends Plugin { this._injector.get(IUndoRedoService); this._injector.get(SharedController); this._injector.get(DesktopSheetShortcutController); + + this._injector.get(ICellEditorService).initialize(); } } diff --git a/packages/ui-plugin-sheets/src/View/App.tsx b/packages/ui-plugin-sheets/src/View/App.tsx index 791f105b1a..b7b825c5cf 100644 --- a/packages/ui-plugin-sheets/src/View/App.tsx +++ b/packages/ui-plugin-sheets/src/View/App.tsx @@ -1,6 +1,7 @@ import { AppContext, BaseComponentProps, ComponentManager, ZIndexManager } from '@univerjs/base-ui'; -import { Component } from 'react'; import { LocaleService, LocaleType, ObserverManager } from '@univerjs/core'; +import { Component } from 'react'; + import { BaseSheetContainerProps, SheetContainer } from './SheetContainer'; export interface BaseUIProps extends BaseComponentProps { @@ -31,7 +32,7 @@ export class App extends Component { }); } - render() { + override render() { const { injector, UIConfig, componentManager, zIndexManager } = this.props; const { locale } = this.state; const localeService = injector.get(LocaleService); @@ -58,6 +59,7 @@ export class App extends Component { }} className="univer-dev-operation" > + {/* language selector */} { static override contextType = AppContext; - root = createRef(); + private root = createRef(); constructor(props: BaseRightMenuProps) { super(props); diff --git a/packages/ui-plugin-sheets/src/View/Toolbar/Const.ts b/packages/ui-plugin-sheets/src/View/Toolbar/Const.ts deleted file mode 100644 index 6439fe6941..0000000000 --- a/packages/ui-plugin-sheets/src/View/Toolbar/Const.ts +++ /dev/null @@ -1,236 +0,0 @@ -import { HorizontalAlign, VerticalAlign, WrapStrategy } from '@univerjs/core'; - -export const FONT_SIZE_CHILDREN = [ - { - label: '9', - value: 9, - }, - { - label: '10', - value: 10, - }, - { - label: '11', - value: 11, - }, - { - label: '12', - value: 12, - }, - { - label: '14', - value: 14, - }, - { - label: '16', - value: 16, - }, - { - label: '18', - value: 18, - }, - { - label: '20', - value: 20, - }, - - { - label: '22', - value: 22, - }, - { - label: '24', - value: 24, - }, - { - label: '26', - value: 26, - }, - { - label: '28', - value: 28, - }, - { - label: '36', - value: 36, - }, - { - label: '48', - value: 48, - }, - { - label: '72', - value: 72, - }, -]; - -export const FONT_FAMILY_CHILDREN = [ - { - label: 'fontFamily.TimesNewRoman', - style: { 'font-family': 'Times New Roman' }, - value: 'Times New Roman', - selected: true, - }, - { - label: 'fontFamily.Arial', - style: { 'font-family': 'Arial' }, - value: 'Arial', - }, - { - label: 'fontFamily.Tahoma', - style: { 'font-family': 'Tahoma' }, - value: 'Tahoma', - }, - { - label: 'fontFamily.Verdana', - style: { 'font-family': 'Verdana' }, - value: 'Verdana', - }, - { - label: 'fontFamily.MicrosoftAccorblack', - style: { 'font-family': '微软雅黑' }, - value: '微软雅黑', - }, - { - label: 'fontFamily.SimSun', - style: { 'font-family': '宋体' }, - value: '宋体', - }, - { - label: 'fontFamily.SimHei', - style: { 'font-family': '黑体' }, - value: '黑体', - }, - { - label: 'fontFamily.Kaiti', - style: { 'font-family': '楷体' }, - value: '楷体', - }, - { - label: 'fontFamily.FangSong', - style: { 'font-family': '仿宋' }, - value: '仿宋', - }, - { - label: 'fontFamily.NSimSun', - style: { 'font-family': '新宋体' }, - value: '新宋体', - }, - { - label: 'fontFamily.STXinwei', - style: { 'font-family': '华文新魏' }, - value: '华文新魏', - }, - { - label: 'fontFamily.STXingkai', - style: { 'font-family': '华文行楷' }, - value: '华文行楷', - }, - { - label: 'fontFamily.STLiti', - style: { 'font-family': '华文隶书' }, - value: '华文隶书', - }, - { - label: 'fontFamily.HanaleiFill', - style: { 'font-family': 'HanaleiFill' }, - value: 'HanaleiFill', - }, - { - label: 'fontFamily.Anton', - style: { 'font-family': 'Anton' }, - value: 'Anton', - }, - { - label: 'fontFamily.Pacifico', - style: { 'font-family': 'Pacifico' }, - value: 'Pacifico', - }, -]; - -export const HORIZONTAL_ALIGN_CHILDREN = [ - { - label: 'align.left', - icon: 'LeftAlignIcon', - value: HorizontalAlign.LEFT, - }, - { - label: 'align.center', - icon: 'CenterAlignIcon', - value: HorizontalAlign.CENTER, - }, - { - label: 'align.right', - icon: 'RightAlignIcon', - value: HorizontalAlign.RIGHT, - }, -]; - -export const VERTICAL_ALIGN_CHILDREN = [ - { - label: 'align.top', - icon: 'TopVerticalIcon', - value: VerticalAlign.TOP, - }, - { - label: 'align.middle', - icon: 'CenterVerticalIcon', - value: VerticalAlign.MIDDLE, - }, - { - label: 'align.bottom', - icon: 'BottomVerticalIcon', - value: VerticalAlign.BOTTOM, - }, -]; - -export const TEXT_WRAP_CHILDREN = [ - { - label: 'textWrap.overflow', - icon: 'OverflowIcon', - value: WrapStrategy.OVERFLOW, - }, - { - label: 'textWrap.wrap', - icon: 'BrIcon', - value: WrapStrategy.WRAP, - }, - { - label: 'textWrap.clip', - icon: 'CutIcon', - value: WrapStrategy.CLIP, - }, -]; - -export const TEXT_ROTATE_CHILDREN = [ - { - label: 'textRotate.none', - icon: 'TextRotateIcon', - value: 0, - }, - { - label: 'textRotate.angleUp', - icon: 'TextRotateAngleUpIcon', - value: -45, - }, - { - label: 'textRotate.angleDown', - icon: 'TextRotateAngleDownIcon', - value: 45, - }, - { - label: 'textRotate.vertical', - icon: 'TextRotateVerticalIcon', - value: 'v', - }, - { - label: 'textRotate.rotationUp', - icon: 'TextRotateRotationUpIcon', - value: -90, - }, - { - label: 'textRotate.rotationDown', - icon: 'TextRotateRotationDownIcon', - value: 90, - }, -]; diff --git a/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar-zh.md b/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar-zh.md deleted file mode 100644 index 42531d12a6..0000000000 --- a/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar-zh.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -category: Components -type: 定制 -title: Toolbar -subtitle: 工具栏 -cover: '' ---- - -## 介绍 - -工具栏 - -## API - -工具栏的属性说明如下: - -| 属性 | 说明 | 类型 | 默认值 | -| ---------- | --------------------- | --------------------------- | -------------------------- | -| style | 自定义 css 央样式对象 | `JSX.CSSProperties` | - | -| config | 配置项 | `IShowToolbarConfig` | `IShowToolbarConfig` | -| forwardRef | preact refs 引用 | `RefObject` | - | -| func | 回调挂载 | `object` | `{ addButton: Function; }` | - - - -```json -{ - "undoRedo": false, //撤销重做,注意撤消重做是两个按钮,由这一个配置决定显示还是隐藏 - "paintFormat": false, //格式刷 - "currencyFormat": false, //货币格式 - "percentageFormat": false, //百分比格式 - "numberDecrease": false, // '减少小数位数' - "numberIncrease": false, // '增加小数位数 - "moreFormats": false, // '更多格式' - "font": false, // '字体' - "fontSize": false, // '字号大小' - "bold": false, // '粗体 (Ctrl+B)' - "italic": false, // '斜体 (Ctrl+I)' - "strikethrough": false, // '删除线 (Alt+Shift+5)' - "underline": false, // '下划线 (Alt+Shift+6)' - "textColor": false, // '文本颜色' - "fillColor": false, // '单元格颜色' - "border": false, // '边框' - "mergeCell": false, // '合并单元格' - "horizontalAlignMode": false, // '水平对齐方式' - "verticalAlignMode": false, // '垂直对齐方式' - "textWrapMode": false, // '换行方式' - "textRotateMode": false // '文本旋转方式' -} -``` - -以下这些配置已经分配到各个插件中了 - -```json -{ - "link": false, // '插入链接' - "image": false, // '插入图片' - "chart": false, // '图表' - "comment": false, //'批注' - "pivotTable": false, //'数据透视表' - "function": false, // '公式' - "frozenMode": false, // '冻结方式' - "sort": false, // '排序' - "filter": false, // '筛选' - "conditionalalFormat": false, // '条件格式' - "dataValidation": false, // '数据验证' - "splitColumn": false, // '分列' - "screenshot": false, // '截图' - "findAndReplace": false, // '查找替换' - "protection": false, // '工作表保护' - "print": false // '打印' -} -``` - -## 案例 - -```jsx -import { Toolbar } from '@univerjs/style-univer'; - -const SheetContainer = () => { - return ( - - ); -}; -``` \ No newline at end of file diff --git a/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar.tsx b/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar.tsx index 2a34cf47a4..6f410de3d2 100644 --- a/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar.tsx +++ b/packages/ui-plugin-sheets/src/View/Toolbar/Toolbar.tsx @@ -1,4 +1,4 @@ -import { AppContext, BaseComponentProps, Container, IDisplayMenuItem, IMenuItem } from '@univerjs/base-ui'; +import { AppContext, AppContextValues, BaseComponentProps, Container, IDisplayMenuItem, IMenuItem } from '@univerjs/base-ui'; import { Component, createRef } from 'react'; import { IToolbarItemProps, SheetContainerUIController } from '../../Controller'; @@ -14,7 +14,7 @@ interface IState { menuItems: Array>; } -export class Toolbar extends Component { +export class Toolbar extends Component { static override contextType = AppContext; toolbarRef = createRef(); @@ -45,7 +45,7 @@ export class Toolbar extends Component { }; resetUl = () => { - const wrapper = this.context.injector.get(SheetContainerUIController).getContentRef().current!; + const wrapper = (this.context as AppContextValues).injector.get(SheetContainerUIController).getContentRef().current!; const height = `${(wrapper as HTMLDivElement).offsetHeight}px`; const ul = this.toolbarRef.current?.querySelectorAll('ul'); if (!ul) return; @@ -58,8 +58,6 @@ export class Toolbar extends Component { this.props.getComponent?.(this); // pass the UI to the controller, which is not good... } - override componentWillUnmount() {} - neoRenderToolbarList() { return this.state.menuItems.map((item) => ); } diff --git a/packages/ui-plugin-sheets/src/View/index.tsx b/packages/ui-plugin-sheets/src/View/index.tsx index 77e98dc1e5..d43ebf7cb2 100644 --- a/packages/ui-plugin-sheets/src/View/index.tsx +++ b/packages/ui-plugin-sheets/src/View/index.tsx @@ -7,15 +7,12 @@ interface BaseSheetUIConfig extends BaseUIProps { container?: HTMLElement | string; } +// TODO@wzhudev: this is not necessary to be a class. A function is enough. export class UI { constructor(props: BaseSheetUIConfig) { this._initialize(props); } - static create(props: BaseSheetUIConfig) { - return new UI(props); - } - private _initialize(props: BaseSheetUIConfig) { let renderContainer: HTMLElement; const container = props.container; diff --git a/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor-desktop.service.ts b/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor-desktop.service.ts new file mode 100644 index 0000000000..ecd08e928b --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor-desktop.service.ts @@ -0,0 +1,324 @@ +import './desktop.module.less'; + +import { CoverCommand, DocsView, DocsViewManagerService, ICoverCommandParams } from '@univerjs/base-docs'; +import { DocsEditor, Engine, IMouseEvent, IPointerEvent, IRenderingEngine, ISelectionTransformerShapeManager, Layer, Scene, Viewport } from '@univerjs/base-render'; +import { CANVAS_VIEW_KEY, CanvasView, ISetRangeValuesCommandParams, SelectionManagerService, SetRangeValuesCommand } from '@univerjs/base-sheets'; +import { ContextService, IContextService } from '@univerjs/base-ui'; +import { + createEmptyDocSnapshot, + Disposable, + handleJsonToDom, + ICellRange, + ICommandService, + ICurrentUniverService, + IDocumentData, + IRangeData, + IStyleData, + Nullable, + toDisposable, +} from '@univerjs/core'; +import { Inject, Injector } from '@wendellhu/redi'; + +import { FOCUSING_SHEET_CELL_EDITOR, FOCUSING_SHEET_EDITOR } from '../context/context'; +import { ICellEditorService } from './cell-editor.service'; +import { getPositionOfCurrentCell, ICellPosition } from './utils'; + +const SHEET_CELL_EDITOR_CLASSNAME = 'univer-cell-editor-container'; +const SHEET_CELL_EDITOR_MODEL_ID = 'sheet.model.cell-editor'; + +// TODO: active cell editor when on input or IME input. Should clear the cell editor when activate it with a keystroke. + +/** + * Cell editor for Univer on desktop. Reuse by multi univer sheet documents. + */ +export class DesktopCellEditorService extends Disposable implements ICellEditorService { + private _containerElement: HTMLDivElement; + + private _editorEngine: Engine; + + private _editorScene: Scene; + + /** This flag indicated whether the cell editor is visible (as it always handles keyboard events when a UniverSheet is focused). */ + private _activated = false; + + private _currentEditingCell: Nullable = null; + + constructor( + @Inject(Injector) private readonly _injector: Injector, + @Inject(CanvasView) private readonly _sheetCanvasView: CanvasView, + @Inject(SelectionManagerService) private _selectionManagerService: SelectionManagerService, + @ISelectionTransformerShapeManager private readonly _selectionTransformerShapeManager: ISelectionTransformerShapeManager, + @ICommandService private readonly _commandService: ICommandService, + @IRenderingEngine private readonly _renderingEngine: Engine, + @ICurrentUniverService private readonly _currentUniverService: ICurrentUniverService, + @IContextService private readonly _contextService: ContextService + ) { + super(); + } + + initialize(): void { + this._mountCellEditor(); + this._initListeners(); + this._focusCurrentSheet(); + } + + override dispose(): void { + super.dispose(); + + document.body.removeChild(this._containerElement); + } + + async enterEditing() { + if (this._activated) { + return; + } + + this._activated = true; + + // Steps of starting a cell editor: + // 1. get cell info at the given positions + // 2. mount cell editor at the given position. + // 3. set quitting editor callbacks. + const currentCell = this._selectionManagerService.getLast()?.cellRange; + if (!currentCell) { + return; + } + + this._currentEditingCell = getRangeFromCellInfo(currentCell); + const position = this._positionCellEditor(currentCell); + await this._updateDocumentModelFromCellModel(position); + + this._contextService.setContextValue(FOCUSING_SHEET_EDITOR, true); + this._contextService.setContextValue(FOCUSING_SHEET_CELL_EDITOR, true); + + this._currentUniverService.focusUniverInstance(SHEET_CELL_EDITOR_MODEL_ID); // focus the UniverDoc instance to enable rich text editing + this._getCellEditor()?.activate(); + } + + quitEditing(): void { + if (!this._activated) { + return; + } + + this._activated = false; + this._hideCellEditor(); + + const currentUniverSheet = this._currentUniverService.getCurrentUniverSheetInstance(); + const workbookId = currentUniverSheet.getUnitId(); + this._currentUniverService.focusUniverInstance(workbookId); + this._contextService.setContextValue(FOCUSING_SHEET_EDITOR, false); + this._contextService.setContextValue(FOCUSING_SHEET_CELL_EDITOR, false); + + const model = this._currentUniverService.getUniverDocInstance(SHEET_CELL_EDITOR_MODEL_ID)!.getDocument(); + const dataStream = model.getBodyModel().getText(); + this._getCellEditor()?.deactivate(true); + + this._commandService.executeCommand(SetRangeValuesCommand.id, { + workbookId, + worksheetId: currentUniverSheet.getWorkBook().getActiveSheet().getSheetId(), + range: this._currentEditingCell, + value: { + v: dataStream, + }, + }); + } + + private _focusCurrentSheet(): void { + this._currentUniverService.focusUniverInstance(this._currentUniverService.getCurrentUniverSheetInstance().getUnitId()); + } + + private _mountCellEditor(): void { + // container element + const containerElement = (this._containerElement = document.createElement('div')); + containerElement.classList.add(SHEET_CELL_EDITOR_CLASSNAME); + containerElement.style.position = 'fixed'; + containerElement.style.boxSizing = 'border-box'; + containerElement.style.background = 'white'; + containerElement.style.outline = '2px solid #a8c7fa'; + containerElement.style.border = '2px solid #0b57d0'; + containerElement.setAttribute('spellcheck', 'false'); + + // set basic docModel + // use the model to create a UniverDoc + const cellEditorDoc = this._currentUniverService.createDoc({ + ...createEmptyDocSnapshot(), + id: SHEET_CELL_EDITOR_MODEL_ID, + documentStyle: { + marginLeft: 2, + marginTop: 2, + marginRight: 2, + marginBottom: 2, + marginHeader: 2, + marginFooter: 2, + pageSize: { + height: 200, + width: 200, + }, + }, + }); + const docModel = cellEditorDoc.getDocument(); + + // rendering engine add document canvas + const engine = (this._editorEngine = new Engine()); + const scene = (this._editorScene = new Scene(CANVAS_VIEW_KEY.CELL_EDITOR, engine, { + width: 200, + height: 100, + })); + scene.openTransformer(); + const viewMain = new Viewport(CANVAS_VIEW_KEY.CELL_EDITOR, scene, { + left: 0, + top: 0, + bottom: 0, + right: 0, + isWheelPreventDefaultX: true, + }); + scene.addViewport(viewMain).attachControl(); + scene.addLayer(Layer.create(scene, [], 0), Layer.create(scene, [], 2)); + const docsView = this._injector.createInstance(DocsView, docModel); + docsView.initialize(scene); + + engine.runRenderLoop(() => { + scene.render(); + }); + + document.body.appendChild(this._containerElement); + engine.setContainer(this._containerElement); + } + + private _positionCellEditor(currentCell: ICellRange) { + const cellInfo = this._selectionTransformerShapeManager.convertCellRangeToInfo(currentCell)!; + const position = getPositionOfCurrentCell(cellInfo, this._renderingEngine); + + this._containerElement.style.display = 'block'; + this._containerElement.style.left = `${position.left}px`; + this._containerElement.style.top = `${position.top}px`; + this._containerElement.style.minHeight = `${position.minHeight}px`; + this._containerElement.style.minWidth = `${position.minWidth}px`; + this._containerElement.style.maxHeight = `${position.maxHeight}px`; + this._containerElement.style.maxWidth = `${position.maxWidth}px`; + + this._editorEngine.resize(); + this._containerElement.focus(); + + return position; + } + + private _hideCellEditor(): void { + this._containerElement.style.display = 'none'; + } + + private _initListeners(): void { + const main = this._sheetCanvasView.getSheetView().getSpreadsheet(); + const onDbclickObserver = main.onDblclickObserver.add((evt: IPointerEvent | IMouseEvent) => { + if (evt.button !== 2) { + this.enterEditing(); + evt.preventDefault(); + } + }); + const pointerDownObserver = main.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent) => { + this.quitEditing(); + }); + + this.disposeWithMe( + toDisposable(() => { + main.onDblclickObserver.remove(onDbclickObserver); + main.onPointerDownObserver.remove(pointerDownObserver); + }) + ); + } + + private async _updateDocumentModelFromCellModel(position: ICellPosition): Promise { + const cellEditorModel = this._currentUniverService.getUniverDocInstance(SHEET_CELL_EDITOR_MODEL_ID)?.getDocument(); + if (!cellEditorModel) { + throw new Error('Cell editor model not found!'); + } + + const cellValue = this._getCellValue(); + const style = this._getCellStyle(); + + const snapshot: IDocumentData = { + id: SHEET_CELL_EDITOR_MODEL_ID, + body: { + dataStream: `${cellValue}\r\n\0`, + }, + documentStyle: { + marginLeft: 2, + marginTop: 2, + marginRight: 2, + marginBottom: 2, + marginHeader: 2, + marginFooter: 2, + // FIXME: width here is not correct + pageSize: { + height: position.minHeight - 2 * 2, + width: position.minWidth - 2 * 2, + }, + }, + }; + + return this._commandService.executeCommand(CoverCommand.id, { + unitId: SHEET_CELL_EDITOR_MODEL_ID, + snapshot, + clearUndoRedoStack: true, + }); + } + + private _getCellValue(): string { + const range = this._getActiveRange(); + if (!range) { + return ''; + } + + const value = range && range.getDisplayValue(); + if (typeof value === 'string') { + return value; + } + + if (typeof value === 'object' && value !== null) { + return handleJsonToDom(value); + } + + return ''; + } + + private _getCellStyle(): Nullable { + return this._getActiveRange()?.getTextStyle(); + } + + private _getCellEditor(): Nullable { + return this._injector.get(DocsViewManagerService).getDocsView(SHEET_CELL_EDITOR_MODEL_ID)?.getDocs().getEditor(); + } + + private _getActiveRange() { + const cellRange = this._selectionManagerService.getLast()?.cellRange; + if (!cellRange) return; + let { row, column } = cellRange; + + if (cellRange.isMerged) { + row = cellRange.startRow; + column = cellRange.startColumn; + } + + return this._currentUniverService.getCurrentUniverSheetInstance().getWorkBook().getActiveSheet().getRange(row, column); + } +} + +function getRangeFromCellInfo(cellInfo: ICellRange): IRangeData { + let row: number; + let column: number; + + if (cellInfo.isMerged) { + row = cellInfo.startRow; + column = cellInfo.startColumn; + } else { + row = cellInfo.row; + column = cellInfo.column; + } + + return { + startRow: row, + startColumn: column, + endRow: row, + endColumn: column, + }; +} diff --git a/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor.command.ts b/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor.command.ts new file mode 100644 index 0000000000..c8adada3b7 --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor.command.ts @@ -0,0 +1,12 @@ +import { CommandType, ICommand } from '@univerjs/core'; + +import { ICellEditorService } from './cell-editor.service'; + +export const QuitCellEditorCommand: ICommand = { + id: 'sheet.command.quit-cell-editor', + type: CommandType.COMMAND, + handler: async (accessor) => { + const cellEditorService = accessor.get(ICellEditorService); + cellEditorService.quitEditing(); + }, +}; diff --git a/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor.service.ts b/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor.service.ts new file mode 100644 index 0000000000..8adf89dcc3 --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/cell-editor/cell-editor.service.ts @@ -0,0 +1,12 @@ +import { createIdentifier } from '@wendellhu/redi'; + +// On mobile platforms cell editor maybe implemented with a very different tech +// stack. So cell editor service must be an abstract dependency. + +export interface ICellEditorService { + initialize(): void; + enterEditing(): void; + quitEditing(): void; +} + +export const ICellEditorService = createIdentifier('univer.cell-editor-service'); diff --git a/packages/ui-plugin-sheets/src/services/cell-editor/desktop.module.less b/packages/ui-plugin-sheets/src/services/cell-editor/desktop.module.less new file mode 100644 index 0000000000..6d2d89f171 --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/cell-editor/desktop.module.less @@ -0,0 +1,3 @@ +.univer-cell-editor-container { + position: fixed; +} \ No newline at end of file diff --git a/packages/ui-plugin-sheets/src/services/cell-editor/utils.ts b/packages/ui-plugin-sheets/src/services/cell-editor/utils.ts new file mode 100644 index 0000000000..d046493426 --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/cell-editor/utils.ts @@ -0,0 +1,48 @@ +import { Engine } from '@univerjs/base-render'; +import { CANVAS_VIEW_KEY } from '@univerjs/base-sheets'; +import { ICellInfo } from '@univerjs/core'; + +export interface ICellPosition { + left: number; + top: number; + minWidth: number; + minHeight: number; + maxWidth: number; + maxHeight: number; +} + +export function getPositionOfCurrentCell(currentCell: ICellInfo, renderingEngine: Engine): ICellPosition { + let startX: number; + let endX: number; + let startY: number; + let endY: number; + + if (currentCell.isMerged) { + const mergeInfo = currentCell.mergeInfo; + startX = mergeInfo.startX; + endX = mergeInfo.endX; + startY = mergeInfo.startY; + endY = mergeInfo.endY; + } else { + startX = currentCell.startX; + endX = currentCell.endX; + startY = currentCell.startY; + endY = currentCell.endY; + } + + const mainScene = renderingEngine.getScene(CANVAS_VIEW_KEY.MAIN_SCENE); + const scrollX = mainScene?.getViewport(CANVAS_VIEW_KEY.VIEW_TOP)?.actualScrollX || 0; + const scrollY = mainScene?.getViewport(CANVAS_VIEW_KEY.VIEW_LEFT)?.actualScrollY || 0; + + const canvasElement = renderingEngine.getCanvasElement(); + const sheetContentRect = canvasElement.getBoundingClientRect(); + + return { + left: startX - scrollX, + top: startY - scrollY + 40, // FIXME: dirty hack here + minWidth: endX - startX, + minHeight: endY - startY, + maxWidth: sheetContentRect.width - startX + scrollX, + maxHeight: sheetContentRect.height - startY + scrollY, + }; +} diff --git a/packages/ui-plugin-sheets/src/services/context/context.ts b/packages/ui-plugin-sheets/src/services/context/context.ts new file mode 100644 index 0000000000..ac19d86f17 --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/context/context.ts @@ -0,0 +1,2 @@ +export const FOCUSING_SHEET_CELL_EDITOR = 'FOCUSING_SHEET_CELL_EDITOR'; +export const FOCUSING_SHEET_EDITOR = 'FOCUSING_SHEET_EDITOR'; diff --git a/packages/ui-plugin-sheets/src/services/shortcuts/shortcuts.ts b/packages/ui-plugin-sheets/src/services/shortcuts/shortcuts.ts new file mode 100644 index 0000000000..c5e2f41892 --- /dev/null +++ b/packages/ui-plugin-sheets/src/services/shortcuts/shortcuts.ts @@ -0,0 +1,10 @@ +import { IShortcutItem, KeyCode } from '@univerjs/base-ui'; + +import { QuitCellEditorCommand } from '../cell-editor/cell-editor.command'; +import { FOCUSING_SHEET_CELL_EDITOR } from '../context/context'; + +export const QuitCellEditorShortcutItem: IShortcutItem = { + id: QuitCellEditorCommand.id, + binding: KeyCode.ENTER, + preconditions: (contextService) => contextService.getContextValue(FOCUSING_SHEET_CELL_EDITOR), +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f58c65c502..ad1f47eb7e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -183,6 +183,9 @@ importers: examples/univer-sheet-ts: dependencies: + '@univerjs/base-docs': + specifier: workspace:^0.0.1 + version: link:../../packages/base-docs '@univerjs/base-render': specifier: workspace:^0.0.1 version: link:../../packages/base-render @@ -216,6 +219,9 @@ importers: '@univerjs/sheets-plugin-operation': specifier: workspace:^0.0.1 version: link:../../packages/sheets-plugin-operation + '@univerjs/ui-plugin-docs': + specifier: workspace:^0.0.1 + version: link:../../packages/ui-plugin-docs '@univerjs/ui-plugin-sheets': specifier: workspace:^0.0.1 version: link:../../packages/ui-plugin-sheets @@ -432,6 +438,9 @@ importers: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + rxjs: + specifier: ^7.8.1 + version: 7.8.1 devDependencies: '@types/jest': specifier: ^29.5.4 @@ -760,12 +769,24 @@ importers: '@types/react-dom': specifier: ^18.2.7 version: 18.2.7 + debug: + specifier: ^4.3.3 + version: 4.3.4 + enzyme: + specifier: ^3.11.0 + version: 3.11.0 + identity-obj-proxy: + specifier: ^3.0.0 + version: 3.0.0 jest: specifier: 29.6.4 version: 29.6.4(@types/node@18.14.0)(ts-node@10.9.1) jest-css-modules-transform: specifier: ^4.4.2 version: 4.4.2 + jest-dev-server: + specifier: ^6.1.1 + version: 6.1.1(debug@4.3.4) jest-environment-jsdom: specifier: ^29.6.4 version: 29.6.4 @@ -863,7 +884,7 @@ importers: version: 29.6.4(@types/node@18.14.0)(ts-node@10.9.1) jest-dev-server: specifier: ^6.1.1 - version: 6.1.1 + version: 6.1.1(debug@4.3.4) less: specifier: ^4.1.3 version: 4.1.3 @@ -1337,6 +1358,9 @@ importers: packages/ui-plugin-sheets: dependencies: + '@univerjs/base-docs': + specifier: workspace:^0.0.1 + version: link:../base-docs '@univerjs/base-render': specifier: workspace:^0.0.1 version: link:../base-render @@ -7130,6 +7154,17 @@ packages: engines: {node: '>=8'} dev: true + /array.prototype.filter@1.0.3: + resolution: {integrity: sha512-VizNcj/RGJiUyQBgzwxzE5oHdeuXY5hSbbmKMlphj1cy1Vl7Pn2asCGbSrru6hSQjmCzqTBPVWAF/whmEOVHbw==} + engines: {node: '>= 0.4'} + dependencies: + call-bind: 1.0.2 + define-properties: 1.2.0 + es-abstract: 1.22.1 + es-array-method-boxes-properly: 1.0.0 + is-string: 1.0.7 + dev: true + /array.prototype.findlastindex@1.2.3: resolution: {integrity: sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==} engines: {node: '>= 0.4'} @@ -7232,10 +7267,10 @@ packages: engines: {node: '>=4'} dev: true - /axios@0.25.0: + /axios@0.25.0(debug@4.3.4): resolution: {integrity: sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==} dependencies: - follow-redirects: 1.15.2 + follow-redirects: 1.15.2(debug@4.3.4) transitivePeerDependencies: - debug dev: true @@ -7426,6 +7461,10 @@ packages: - supports-color dev: true + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + dev: true + /bplist-parser@0.2.0: resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} engines: {node: '>= 5.10.0'} @@ -7618,6 +7657,30 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true + /cheerio-select@2.1.0: + resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + dependencies: + boolbase: 1.0.0 + css-select: 5.1.0 + css-what: 6.1.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + dev: true + + /cheerio@1.0.0-rc.12: + resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} + engines: {node: '>= 6'} + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.1.0 + htmlparser2: 8.0.2 + parse5: 7.1.2 + parse5-htmlparser2-tree-adapter: 7.0.0 + dev: true + /chokidar@3.5.3: resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} engines: {node: '>= 8.10.0'} @@ -8034,6 +8097,16 @@ packages: resolution: {integrity: sha512-TcB+ZH9wZBG314jAUpKHPl1oYbRJV+nAT2YwZ9y4fmUN0FkEJa8e/hKZoOgzLYp1Z/CJdFhbhhGIGh0XG8W54Q==} dev: true + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: true + /css-vars-ponyfill@2.4.6: resolution: {integrity: sha512-FqUFzMlI5jpPl73R9FrUuittWM/t7RDEnyp0603MCNEy6PI/1PALIX1d15cui6UAN/V8tnIWZDMz6WoZD87eGQ==} dependencies: @@ -8041,6 +8114,11 @@ packages: get-css-data: 2.1.0 dev: false + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: true + /cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -8350,6 +8428,10 @@ packages: path-type: 4.0.0 dev: true + /discontinuous-range@1.0.0: + resolution: {integrity: sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==} + dev: true + /doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -8368,6 +8450,18 @@ packages: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} dev: true + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: true + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: true + /domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} @@ -8375,6 +8469,21 @@ packages: webidl-conversions: 7.0.0 dev: true + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: true + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: true + /dot-prop@5.3.0: resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} engines: {node: '>=8'} @@ -8472,6 +8581,40 @@ packages: hasBin: true dev: true + /enzyme-shallow-equal@1.0.5: + resolution: {integrity: sha512-i6cwm7hN630JXenxxJFBKzgLC3hMTafFQXflvzHgPmDhOBhxUWDe8AeRv1qp2/uWJ2Y8z5yLWMzmAfkTOiOCZg==} + dependencies: + has: 1.0.3 + object-is: 1.1.5 + dev: true + + /enzyme@3.11.0: + resolution: {integrity: sha512-Dw8/Gs4vRjxY6/6i9wU0V+utmQO9kvh9XLnz3LIudviOnVYDEe2ec+0k+NQoMamn1VrjKgCUOWj5jG/5M5M0Qw==} + dependencies: + array.prototype.flat: 1.3.1 + cheerio: 1.0.0-rc.12 + enzyme-shallow-equal: 1.0.5 + function.prototype.name: 1.1.6 + has: 1.0.3 + html-element-map: 1.3.1 + is-boolean-object: 1.1.2 + is-callable: 1.2.7 + is-number-object: 1.0.7 + is-regex: 1.1.4 + is-string: 1.0.7 + is-subset: 0.1.1 + lodash.escape: 4.0.1 + lodash.isequal: 4.5.0 + object-inspect: 1.12.3 + object-is: 1.1.5 + object.assign: 4.1.4 + object.entries: 1.1.7 + object.values: 1.1.7 + raf: 3.4.1 + rst-selector-parser: 2.2.3 + string.prototype.trim: 1.2.7 + dev: true + /errno@0.1.8: resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==} hasBin: true @@ -8532,6 +8675,10 @@ packages: which-typed-array: 1.1.11 dev: true + /es-array-method-boxes-properly@1.0.0: + resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} + dev: true + /es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} dependencies: @@ -9475,7 +9622,7 @@ packages: engines: {node: '>=0.4.0'} dev: true - /follow-redirects@1.15.2: + /follow-redirects@1.15.2(debug@4.3.4): resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} engines: {node: '>=4.0'} peerDependencies: @@ -9483,6 +9630,8 @@ packages: peerDependenciesMeta: debug: optional: true + dependencies: + debug: 4.3.4 dev: true /for-each@0.3.3: @@ -9890,6 +10039,10 @@ packages: engines: {node: '>=6'} dev: true + /harmony-reflect@1.6.2: + resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} + dev: true + /has-bigints@1.0.2: resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} dev: true @@ -9952,6 +10105,13 @@ packages: lru-cache: 6.0.0 dev: true + /html-element-map@1.3.1: + resolution: {integrity: sha512-6XMlxrAFX4UEEGxctfFnmrFaaZFNf9i5fNuV5wZ3WWQ4FVaNP1aX1LkX9j2mfEx1NpjeE/rL3nmgEn23GdFmrg==} + dependencies: + array.prototype.filter: 1.0.3 + call-bind: 1.0.2 + dev: true + /html-encoding-sniffer@3.0.0: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} @@ -9968,6 +10128,15 @@ packages: engines: {node: '>=8'} dev: true + /htmlparser2@8.0.2: + resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.1.0 + entities: 4.5.0 + dev: true + /http-errors@2.0.0: resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} engines: {node: '>= 0.8'} @@ -10063,6 +10232,13 @@ packages: postcss: 8.4.29 dev: true + /identity-obj-proxy@3.0.0: + resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} + engines: {node: '>=4'} + dependencies: + harmony-reflect: 1.6.2 + dev: true + /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -10420,6 +10596,10 @@ packages: better-path-resolve: 1.0.0 dev: true + /is-subset@0.1.1: + resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} + dev: true + /is-symbol@1.0.4: resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} engines: {node: '>= 0.4'} @@ -10770,7 +10950,7 @@ packages: postcss-nested: 5.0.6(postcss@8.4.29) dev: true - /jest-dev-server@6.1.1: + /jest-dev-server@6.1.1(debug@4.3.4): resolution: {integrity: sha512-z5LnaGDvlIkdMv/rppSO4+rq+GyQKf1xI9oiBxf9/2EBeN2hxRaWiMvaLNDnHPZj2PAhBXsycrKslDDoZO2Xtw==} dependencies: chalk: 4.1.2 @@ -10779,7 +10959,7 @@ packages: prompts: 2.4.2 spawnd: 6.2.0 tree-kill: 1.2.2 - wait-on: 6.0.1 + wait-on: 6.0.1(debug@4.3.4) transitivePeerDependencies: - debug - supports-color @@ -11692,6 +11872,14 @@ packages: resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} dev: true + /lodash.escape@4.0.1: + resolution: {integrity: sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==} + dev: true + + /lodash.flattendeep@4.4.0: + resolution: {integrity: sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==} + dev: true + /lodash.get@4.4.2: resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==} dev: true @@ -12096,6 +12284,10 @@ packages: hasBin: true dev: true + /moo@0.5.2: + resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} + dev: true + /mri@1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -12133,6 +12325,16 @@ packages: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /nearley@2.20.1: + resolution: {integrity: sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==} + hasBin: true + dependencies: + commander: 2.20.3 + moo: 0.5.2 + railroad-diagrams: 1.0.0 + randexp: 0.4.6 + dev: true + /needle@2.9.1: resolution: {integrity: sha512-6R9fqJ5Zcmf+uYaFgdIHmLwNldn5HbK8L5ybn7Uz+ylX/rnOsSp1AHcvQSrCaFN+qNM1wpymHqD7mVasEOlHGQ==} engines: {node: '>= 4.4.x'} @@ -12239,6 +12441,12 @@ packages: path-key: 4.0.0 dev: true + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 + dev: true + /numeral@2.0.6: resolution: {integrity: sha512-qaKRmtYPZ5qdw4jWJD6bxEf1FJEqllJrwxCLIm0sQU/A7v2/czigzOb+C2uSiFsa9lBUzeH7M1oK+Q+OLxL3kA==} dev: false @@ -12505,6 +12713,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /parse5-htmlparser2-tree-adapter@7.0.0: + resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + dependencies: + domhandler: 5.0.3 + parse5: 7.1.2 + dev: true + /parse5@7.1.2: resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} dependencies: @@ -12578,6 +12793,10 @@ packages: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true + /performance-now@2.1.0: + resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} + dev: true + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -12994,10 +13213,28 @@ packages: engines: {node: '>=8'} dev: true + /raf@3.4.1: + resolution: {integrity: sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==} + dependencies: + performance-now: 2.1.0 + dev: true + + /railroad-diagrams@1.0.0: + resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} + dev: true + /ramda@0.29.0: resolution: {integrity: sha512-BBea6L67bYLtdbOqfp8f58fPMqEwx0doL+pAi8TZyp2YWz8R9G8z9x75CZI8W+ftqhFHCpEX2cRnUUXK130iKA==} dev: true + /randexp@0.4.6: + resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} + engines: {node: '>=0.12'} + dependencies: + discontinuous-range: 1.0.0 + ret: 0.1.15 + dev: true + /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -13416,6 +13653,11 @@ packages: signal-exit: 3.0.7 dev: true + /ret@0.1.15: + resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} + engines: {node: '>=0.12'} + dev: true + /retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -13464,6 +13706,13 @@ packages: fsevents: 2.3.3 dev: true + /rst-selector-parser@2.2.3: + resolution: {integrity: sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==} + dependencies: + lodash.flattendeep: 4.4.0 + nearley: 2.20.1 + dev: true + /run-applescript@5.0.0: resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} engines: {node: '>=12'} @@ -14988,12 +15237,12 @@ packages: xml-name-validator: 4.0.0 dev: true - /wait-on@6.0.1: + /wait-on@6.0.1(debug@4.3.4): resolution: {integrity: sha512-zht+KASY3usTY5u2LgaNqn/Cd8MukxLGjdcZxT2ns5QzDmTFc4XoWBgC+C/na+sMRZTuVygQoMYwdcVjHnYIVw==} engines: {node: '>=10.0.0'} hasBin: true dependencies: - axios: 0.25.0 + axios: 0.25.0(debug@4.3.4) joi: 17.10.1 lodash: 4.17.21 minimist: 1.2.8 diff --git a/tsconfig.json b/tsconfig.json index c491ce2468..893c127c56 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,6 @@ { "compilerOptions": { - "lib": [ - "ESNext", - "DOM", - "WebWorker", - "dom.iterable" - ], + "lib": ["ESNext", "DOM", "WebWorker", "dom.iterable"], "target": "ESNext", "module": "ESNext", "declaration": true, @@ -17,18 +12,11 @@ "sourceMap": true, "resolveJsonModule": true, "strict": true, + "strictPropertyInitialization": true, "noImplicitOverride": true, "allowJs": true, "skipLibCheck": true, - "types": [ - "jest", - "node", - "@types/jest", - "esbuild-style-plugin" - ] + "types": ["jest", "node", "@types/jest", "esbuild-style-plugin"] }, - "include": [ - "packages/*/src", - "examples/*/src", - ] + "include": ["packages/*/src", "examples/*/src"] }