diff --git a/e2e/disposing/disposing.spec.ts b/e2e/disposing/disposing.spec.ts index 501e83ed69..9992982efe 100644 --- a/e2e/disposing/disposing.spec.ts +++ b/e2e/disposing/disposing.spec.ts @@ -43,7 +43,7 @@ test('no error on constructing and disposing', async ({ page }) => { await page.waitForTimeout(2000); await page.evaluate(() => window.E2EControllerAPI.loadDefaultSheet()); - await page.waitForTimeout(2000); + await page.waitForTimeout(6000); // wait for features loaded in STEADY stage (5s) await page.evaluate(() => window.E2EControllerAPI.disposeUniver()); await page.waitForTimeout(2000); diff --git a/examples/src/docs/main.ts b/examples/src/docs/main.ts index 1762644c48..9bd7211c3b 100644 --- a/examples/src/docs/main.ts +++ b/examples/src/docs/main.ts @@ -15,18 +15,17 @@ */ /* eslint-disable node/prefer-global/process */ + import { LocaleType, Univer, UniverInstanceType } from '@univerjs/core'; import { defaultTheme } from '@univerjs/design'; import { UniverDocsPlugin } from '@univerjs/docs'; import { UniverDocsUIPlugin } from '@univerjs/docs-ui'; import { UniverRenderEnginePlugin } from '@univerjs/engine-render'; import { UniverUIPlugin } from '@univerjs/ui'; -import { UniverDrawingPlugin } from '@univerjs/drawing'; import { UniverFormulaEnginePlugin } from '@univerjs/engine-formula'; import { UniverDebuggerPlugin } from '@univerjs/debugger'; -import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; -import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'; import { UniverDocsDrawingUIPlugin } from '@univerjs/docs-drawing-ui'; + import { DEFAULT_DOCUMENT_DATA_CN } from '../data'; import { enUS, ruRU, zhCN } from '../locales'; @@ -58,7 +57,7 @@ univer.registerPlugin(UniverUIPlugin, { container: 'app', footer: false, }); -univer.registerPlugin(UniverDrawingPlugin); + univer.registerPlugin(UniverDocsPlugin); univer.registerPlugin(UniverDocsUIPlugin, { container: 'univerdoc', @@ -69,8 +68,6 @@ univer.registerPlugin(UniverDocsUIPlugin, { }, }); -univer.registerPlugin(UniverDrawingUIPlugin); -univer.registerPlugin(UniverDocsDrawingPlugin); univer.registerPlugin(UniverDocsDrawingUIPlugin); univer.createUnit(UniverInstanceType.UNIVER_DOC, DEFAULT_DOCUMENT_DATA_CN); diff --git a/examples/src/sheets/main.ts b/examples/src/sheets/main.ts index e2e51809cb..1ece1430cb 100644 --- a/examples/src/sheets/main.ts +++ b/examples/src/sheets/main.ts @@ -32,10 +32,10 @@ import type { IUniverRPCMainThreadConfig } from '@univerjs/rpc'; import { UniverRPCMainThreadPlugin } from '@univerjs/rpc'; import { UniverSheetsFormulaPlugin } from '@univerjs/sheets-formula'; import { UniverSheetsNumfmtPlugin } from '@univerjs/sheets-numfmt'; -import { UniverSheetsZenEditorPlugin } from '@univerjs/sheets-zen-editor'; import { UniverSheetsDataValidationPlugin } from '@univerjs/sheets-data-validation'; import { UniverSheetsDrawingUIPlugin } from '@univerjs/sheets-drawing-ui'; import { FUniver } from '@univerjs/facade'; +import { UniverSheetsZenEditorPlugin } from '@univerjs/sheets-zen-editor'; import { UniverSheetsSortPlugin } from '@univerjs/sheets-sort'; import { UniverSheetsSortUIPlugin } from '@univerjs/sheets-sort-ui'; import { enUS, ruRU, zhCN } from '../locales'; diff --git a/packages/core/src/common/const.ts b/packages/core/src/common/const.ts index 46c68bd3f5..4e51e17460 100644 --- a/packages/core/src/common/const.ts +++ b/packages/core/src/common/const.ts @@ -14,16 +14,18 @@ * limitations under the License. */ -export const DOCS_NORMAL_EDITOR_UNIT_ID_KEY = '__defaultDocumentNormalEditorSpecialUnitId_20231006__'; +const PREFIX = '__INTERNAL_EDITOR__'; -export const DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY = '__defaultDocumentFormulaBarEditorSpecialUnitId_20231012__'; +export const DOCS_NORMAL_EDITOR_UNIT_ID_KEY = `${PREFIX}DOCS_NORMAL`; + +export const DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY = `${PREFIX}DOCS_FORMULA_BAR`; export const DEFAULT_EMPTY_DOCUMENT_VALUE = '\r\n'; export function createInternalEditorID(id: string) { - return `__internalEditorId__${id}`; + return `${PREFIX}${id}`; } export function isInternalEditorID(id: string) { - return id.startsWith('__'); + return id.startsWith(PREFIX); } diff --git a/packages/core/src/services/instance/instance.service.ts b/packages/core/src/services/instance/instance.service.ts index 8613bcb93d..b1486ac5d2 100644 --- a/packages/core/src/services/instance/instance.service.ts +++ b/packages/core/src/services/instance/instance.service.ts @@ -41,7 +41,7 @@ export interface IUniverInstanceService { /** Subscribe to curtain type of units' creation. */ getTypeOfUnitAdded$(type: UnitType): Observable; - /** @interal */ + /** @ignore */ __addUnit(unit: UnitModel): void; /** Omits value when a UnitModel is disposed. */ diff --git a/packages/core/src/services/undoredo/undoredo.service.ts b/packages/core/src/services/undoredo/undoredo.service.ts index bc36829057..6fd9391890 100644 --- a/packages/core/src/services/undoredo/undoredo.service.ts +++ b/packages/core/src/services/undoredo/undoredo.service.ts @@ -49,7 +49,7 @@ export interface IUndoRedoService { popUndoToRedo(): void; popRedoToUndo(): void; - clearUndoRedo(unitID: string): void; + clearUndoRedo(unitId: string): void; /** * Batch undo redo elements into a single `IUndoRedoItem` util the returned `IDisposable` is called. diff --git a/packages/debugger/src/controllers/performance-monitor.controller.ts b/packages/debugger/src/controllers/performance-monitor.controller.ts index cf5a8825d5..08f7cd3990 100644 --- a/packages/debugger/src/controllers/performance-monitor.controller.ts +++ b/packages/debugger/src/controllers/performance-monitor.controller.ts @@ -15,9 +15,9 @@ */ import { IUniverInstanceService, LifecycleStages, OnLifecycle, RxDisposable, UniverInstanceType } from '@univerjs/core'; -import { DocCanvasView } from '@univerjs/docs-ui'; +import { DocRenderController } from '@univerjs/docs-ui'; import { Inject, Injector } from '@wendellhu/redi'; -import { interval, takeUntil, throttle } from 'rxjs'; +import { takeUntil } from 'rxjs'; @OnLifecycle(LifecycleStages.Rendered, PerformanceMonitorController) export class PerformanceMonitorController extends RxDisposable { @@ -27,7 +27,7 @@ export class PerformanceMonitorController extends RxDisposable { private _styleElement!: HTMLStyleElement; constructor( - @Inject(DocCanvasView) private _docCanvasView: DocCanvasView, + @Inject(DocRenderController) private _DocRenderController: DocRenderController, @Inject(Injector) private _injector: Injector, @IUniverInstanceService private _instanceService: IUniverInstanceService ) { @@ -86,13 +86,6 @@ export class PerformanceMonitorController extends RxDisposable { this._styleElement = document.createElement('style'); document.head.appendChild(this._styleElement).innerText = style; - if (this._documentType === UniverInstanceType.UNIVER_DOC) { - this._docCanvasView.fps$ - .pipe(takeUntil(this.dispose$)) - .pipe(throttle(() => interval(THROTTLE_TIME))) - .subscribe((fps) => { - container.innerText = `FPS: ${fps}`; - }); - } + // TODO@wzhudev: monitor fps from engine } } diff --git a/packages/debugger/src/debugger-plugin.ts b/packages/debugger/src/debugger-plugin.ts index 6c07885c39..bf1ed95d35 100644 --- a/packages/debugger/src/debugger-plugin.ts +++ b/packages/debugger/src/debugger-plugin.ts @@ -20,7 +20,6 @@ import { Inject, Injector } from '@wendellhu/redi'; import type { IUniverDebuggerConfig } from './controllers/debugger.controller'; import { DebuggerController, DefaultDebuggerConfig } from './controllers/debugger.controller'; -import { PerformanceMonitorController } from './controllers/performance-monitor.controller'; import { E2EMemoryController } from './controllers/e2e/e2e-memory.controller'; export class UniverDebuggerPlugin extends Plugin { @@ -39,7 +38,7 @@ export class UniverDebuggerPlugin extends Plugin { override onStarting(injector: Injector): void { ([ - [PerformanceMonitorController], + // [PerformanceMonitorController], [E2EMemoryController], ] as Dependency[]).forEach((d) => injector.add(d)); } diff --git a/packages/docs-drawing-ui/src/commands/operations/insert-image.operation.ts b/packages/docs-drawing-ui/src/commands/operations/insert-image.operation.ts index b6e01bc977..151e010c0e 100644 --- a/packages/docs-drawing-ui/src/commands/operations/insert-image.operation.ts +++ b/packages/docs-drawing-ui/src/commands/operations/insert-image.operation.ts @@ -21,10 +21,11 @@ export interface IInsertImageOperationParams { files: Nullable; }; +/** + * @deprecated Do not use command as event! + */ export const InsertDocImageOperation: IOperation = { id: 'doc.operation.insert-float-image', type: CommandType.OPERATION, - handler: (accessor, params) => { - return true; - }, + handler: () => true, }; diff --git a/packages/docs-drawing-ui/src/controllers/doc-drawing-update.controller.ts b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts similarity index 57% rename from packages/docs-drawing-ui/src/controllers/doc-drawing-update.controller.ts rename to packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts index 014fc87952..d3969e1104 100644 --- a/packages/docs-drawing-ui/src/controllers/doc-drawing-update.controller.ts +++ b/packages/docs-drawing-ui/src/controllers/render-controllers/doc-drawing-update.render-controller.ts @@ -15,7 +15,7 @@ */ import type { DocumentDataModel, ICommandInfo, IDocDrawingPosition, Nullable } from '@univerjs/core'; -import { Disposable, FOCUSING_COMMON_DRAWINGS, ICommandService, IContextService, IUniverInstanceService, LifecycleStages, LocaleService, ObjectRelativeFromH, ObjectRelativeFromV, OnLifecycle, PositionedObjectLayoutType, UniverInstanceType } from '@univerjs/core'; +import { Disposable, FOCUSING_COMMON_DRAWINGS, ICommandService, IContextService, LocaleService, ObjectRelativeFromH, ObjectRelativeFromV, PositionedObjectLayoutType } from '@univerjs/core'; import { Inject } from '@wendellhu/redi'; import type { IImageIoServiceParam } from '@univerjs/drawing'; import { DRAWING_IMAGE_ALLOW_SIZE, DRAWING_IMAGE_COUNT_LIMIT, DRAWING_IMAGE_HEIGHT_LIMIT, DRAWING_IMAGE_WIDTH_LIMIT, DrawingTypeEnum, getImageSize, IDrawingManagerService, IImageIoService, ImageUploadStatusType } from '@univerjs/drawing'; @@ -25,46 +25,36 @@ import type { IDocDrawing } from '@univerjs/docs-drawing'; import { IDocDrawingService } from '@univerjs/docs-drawing'; import { DocSkeletonManagerService, TextSelectionManagerService } from '@univerjs/docs'; import { docDrawingPositionToTransform, transformToDocDrawingPosition } from '@univerjs/docs-ui'; -import type { Documents } from '@univerjs/engine-render'; -import { IRenderManagerService, Liquid } from '@univerjs/engine-render'; -import type { IInsertImageOperationParams } from '../commands/operations/insert-image.operation'; -import { InsertDocImageOperation } from '../commands/operations/insert-image.operation'; -import type { IInsertDrawingCommandParams, ISetDrawingCommandParams } from '../commands/commands/interfaces'; -import { type ISetDrawingArrangeCommandParams, SetDocDrawingArrangeCommand } from '../commands/commands/set-drawing-arrange.command'; -import { InsertDocDrawingCommand } from '../commands/commands/insert-doc-drawing.command'; -import { GroupDocDrawingCommand } from '../commands/commands/group-doc-drawing.command'; -import { UngroupDocDrawingCommand } from '../commands/commands/ungroup-doc-drawing.command'; -import { SetDocDrawingCommand } from '../commands/commands/set-doc-drawing.command'; - -@OnLifecycle(LifecycleStages.Rendered, DocDrawingUpdateController) -export class DocDrawingUpdateController extends Disposable { +import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; + +import type { IInsertImageOperationParams } from '../../commands/operations/insert-image.operation'; +import { InsertDocImageOperation } from '../../commands/operations/insert-image.operation'; +import type { IInsertDrawingCommandParams, ISetDrawingCommandParams } from '../../commands/commands/interfaces'; +import { type ISetDrawingArrangeCommandParams, SetDocDrawingArrangeCommand } from '../../commands/commands/set-drawing-arrange.command'; +import { InsertDocDrawingCommand } from '../../commands/commands/insert-doc-drawing.command'; +import { GroupDocDrawingCommand } from '../../commands/commands/group-doc-drawing.command'; +import { UngroupDocDrawingCommand } from '../../commands/commands/ungroup-doc-drawing.command'; +import { SetDocDrawingCommand } from '../../commands/commands/set-doc-drawing.command'; + +export class DocDrawingUpdateRenderController extends Disposable implements IRenderModule { constructor( + private readonly _context: IRenderContext, @ICommandService private readonly _commandService: ICommandService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @IImageIoService private readonly _imageIoService: IImageIoService, + @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IDocDrawingService private readonly _sheetDrawingService: IDocDrawingService, + @IImageIoService private readonly _imageIoService: IImageIoService, @IDrawingManagerService private readonly _drawingManagerService: IDrawingManagerService, @IContextService private readonly _contextService: IContextService, @IMessageService private readonly _messageService: IMessageService, - @Inject(LocaleService) private readonly _localeService: LocaleService, - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService + @Inject(LocaleService) private readonly _localeService: LocaleService ) { super(); - this._init(); - } - - private _init(): void { this._initCommandListeners(); - this._updateDrawingListener(); - this._updateOrderListener(); - this._groupDrawingListener(); - this._focusDrawingListener(); } @@ -72,32 +62,30 @@ export class DocDrawingUpdateController extends Disposable { * Upload image to cell or float image */ private _initCommandListeners() { - this.disposeWithMe( - this._commandService.onCommandExecuted(async (command: ICommandInfo) => { - if (command.id === InsertDocImageOperation.id) { - const params = command.params as IInsertImageOperationParams; - if (params.files == null) { - return; - } - - const fileLength = params.files.length; - - if (fileLength > DRAWING_IMAGE_COUNT_LIMIT) { - this._messageService.show({ - type: MessageType.Error, - content: this._localeService.t('update-status.exceedMaxCount', String(DRAWING_IMAGE_COUNT_LIMIT)), - }); - return; - } - - this._imageIoService.setWaitCount(fileLength); - - params.files.forEach(async (file) => { - await this._insertFloatImage(file); + this.disposeWithMe(this._commandService.onCommandExecuted(async (command: ICommandInfo) => { + if (command.id === InsertDocImageOperation.id) { + const params = command.params as IInsertImageOperationParams; + if (params.files == null) { + return; + } + + const fileLength = params.files.length; + + if (fileLength > DRAWING_IMAGE_COUNT_LIMIT) { + this._messageService.show({ + type: MessageType.Error, + content: this._localeService.t('update-status.exceedMaxCount', String(DRAWING_IMAGE_COUNT_LIMIT)), }); + return; } - }) - ); + + this._imageIoService.setWaitCount(fileLength); + + params.files.forEach(async (file) => { + await this._insertFloatImage(file); + }); + } + })); } private async _insertFloatImage(file: File) { @@ -129,22 +117,10 @@ export class DocDrawingUpdateController extends Disposable { return; } - const info = this._getUnitInfo(); - if (info == null) { - return; - } - const { unitId, subUnitId } = info; + const { unitId } = this._context; const { imageId, imageSourceType, source, base64Cache } = imageParam; const { width, height, image } = await getImageSize(base64Cache || ''); - const renderObject = this._renderManagerService.getRenderById(unitId); - - if (renderObject == null) { - return; - } - - const { width: sceneWidth, height: sceneHeight } = renderObject.scene; - this._imageIoService.addImageSourceCache(imageId, imageSourceType, image); let scale = 1; @@ -154,7 +130,7 @@ export class DocDrawingUpdateController extends Disposable { scale = Math.max(scaleWidth, scaleHeight); } - const docTransform = this._getImagePosition(width * scale, height * scale, sceneWidth, sceneHeight); + const docTransform = this._getImagePosition(width * scale, height * scale); if (docTransform == null) { return; @@ -162,7 +138,7 @@ export class DocDrawingUpdateController extends Disposable { const docDrawingParam: IDocDrawing = { unitId, - subUnitId, + subUnitId: unitId, drawingId: imageId, drawingType: DrawingTypeEnum.DRAWING_IMAGE, imageSourceType, @@ -177,25 +153,10 @@ export class DocDrawingUpdateController extends Disposable { drawings: [docDrawingParam], } as IInsertDrawingCommandParams); - this._docSkeletonManagerService.getCurrent()?.skeleton.calculate(); - } - - private _getUnitInfo() { - const documentDataModel = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_DOC); - if (documentDataModel == null) { - return; - } - - const unitId = documentDataModel.getUnitId(); - const subUnitId = unitId; - - return { - unitId, - subUnitId, - }; + this._docSkeletonManagerService.getSkeleton()?.calculate(); } - private _getImagePosition(imageWidth: number, imageHeight: number, sceneWidth: number, sceneHeight: number): Nullable { + private _getImagePosition(imageWidth: number, imageHeight: number): Nullable { const activeTextRange = this._textSelectionManagerService.getActiveTextRange(); const position = activeTextRange?.getAbsolutePosition() || { left: 0, @@ -235,32 +196,23 @@ export class DocDrawingUpdateController extends Disposable { private _updateDrawingListener() { this._drawingManagerService.featurePluginUpdate$.subscribe((params) => { - const drawings: Partial[] = []; - if (params.length === 0) { return; } - // const offsetInfo = this._getDocsOffsetInfo(); - - // const { pageMarginCache, docsLeft, docsTop } = offsetInfo; - + const drawings: Partial[] = []; (params as IDocDrawing[]).forEach((param) => { - const { unitId, subUnitId, drawingId, drawingType, transform } = param; + const { unitId, subUnitId, drawingId, transform } = param; if (transform == null) { return; } const sheetDrawing = this._sheetDrawingService.getDrawingByParam({ unitId, subUnitId, drawingId }); - if (sheetDrawing == null) { return; } - // const { marginLeft, marginTop } = pageMarginCache.get(drawingId) || { marginLeft: 0, marginTop: 0 }; - const docTransform = transformToDocDrawingPosition({ ...sheetDrawing.transform, ...transform }); - if (docTransform == null) { return; } @@ -275,8 +227,9 @@ export class DocDrawingUpdateController extends Disposable { }); if (drawings.length > 0) { + const unitId = params[0].unitId; this._commandService.syncExecuteCommand(SetDocDrawingCommand.id, { - unitId: params[0].unitId, + unitId, drawings, } as ISetDrawingCommandParams); @@ -285,97 +238,10 @@ export class DocDrawingUpdateController extends Disposable { }); } - private _getDocsOffsetInfo() { - const docsSkeletonObject = this._docSkeletonManagerService.getCurrent(); - if (docsSkeletonObject == null) { - return { - pageMarginCache: new Map(), - docsLeft: 0, - docsTop: 0, - }; - } - - const { unitId, skeleton } = docsSkeletonObject; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - const skeletonData = skeleton?.getSkeletonData(); - - if (currentRender == null || !skeletonData) { - return { - pageMarginCache: new Map(), - docsLeft: 0, - docsTop: 0, - }; - } - - const { mainComponent } = currentRender; - - const documentComponent = mainComponent as Documents; - - const { left: docsLeft, top: docsTop, pageLayoutType, pageMarginLeft, pageMarginTop } = documentComponent; - - const { pages } = skeletonData; - - const liquid = new Liquid(); - - const pageMarginCache = new Map(); - - for (let i = 0, len = pages.length; i < len; i++) { - const page = pages[i]; - const { skeDrawings, marginLeft, marginTop } = page; - // cumPageLeft + = pageWidth + documents.pageMarginLeft; - - liquid.translatePagePadding(page); - - skeDrawings.forEach((drawing) => { - const { aLeft, aTop, height, width, drawingId, drawingOrigin } = drawing; - // const behindText = drawingOrigin.layoutType === PositionedObjectLayoutType.WRAP_NONE && drawingOrigin.behindDoc === BooleanNumber.TRUE; - // floatObjects.push({ - // unitId, - // subUnitId: DEFAULT_DOCUMENT_SUB_COMPONENT_ID, - // floatingObjectId: drawingId, - // behindText, - // floatingObject: { - // left: aLeft + docsLeft + liquid.x, - // top: aTop + docsTop + liquid.y, - // width, - // height, - // }, - // }); - - pageMarginCache.set(drawingId, { - marginLeft: liquid.x, - marginTop: liquid.y, - }); - }); - - liquid.restorePagePadding(page); - - liquid.translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); - } - - return { pageMarginCache, docsLeft, docsTop }; - } - private _refreshDocSkeleton() { - const docsSkeletonObject = this._docSkeletonManagerService.getCurrent(); - if (docsSkeletonObject == null) { - return; - } - - const { unitId, skeleton } = docsSkeletonObject; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - const { mainComponent } = currentRender; - + const skeleton = this._docSkeletonManagerService.getSkeleton(); + const { mainComponent } = this._context; skeleton?.calculate(); - mainComponent?.makeDirty(); } diff --git a/packages/docs-drawing-ui/src/plugin.ts b/packages/docs-drawing-ui/src/plugin.ts index 024795686c..1747fe8d22 100644 --- a/packages/docs-drawing-ui/src/plugin.ts +++ b/packages/docs-drawing-ui/src/plugin.ts @@ -14,41 +14,44 @@ * limitations under the License. */ -import { LocaleService, Plugin, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, Plugin, UniverInstanceType } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; +import { UniverDrawingUIPlugin } from '@univerjs/drawing-ui'; +import { UniverDrawingPlugin } from '@univerjs/drawing'; +import { UniverDocsDrawingPlugin } from '@univerjs/docs-drawing'; +import { IRenderManagerService } from '@univerjs/engine-render'; import { DocDrawingPopupMenuController } from './controllers/drawing-popup-menu.controller'; import { DocDrawingUIController } from './controllers/doc-drawing.controller'; -import { DocDrawingUpdateController } from './controllers/doc-drawing-update.controller'; +import { DocDrawingUpdateRenderController } from './controllers/render-controllers/doc-drawing-update.render-controller'; -const PLUGIN_NAME = 'Docs_Drawing_UI_PLUGIN'; +const PLUGIN_NAME = 'DOCS_DRAWING_UI_PLUGIN'; +@DependentOn(UniverDrawingUIPlugin, UniverDrawingPlugin, UniverDocsDrawingPlugin) export class UniverDocsDrawingUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_DOC; static override pluginName = PLUGIN_NAME; + constructor( - config: undefined, + _config: undefined, @Inject(Injector) protected _injector: Injector, - @Inject(LocaleService) private readonly _localeService: LocaleService + @IRenderManagerService private readonly _renderManagerSrv: IRenderManagerService ) { super(); } - override onStarting(_injector: Injector): void { - this._initDependencies(_injector); - } - - private _initDependencies(injector: Injector): void { + override onStarting(injector: Injector): void { const dependencies: Dependency[] = [ - - // services - - // controllers [DocDrawingUIController], - [DocDrawingUpdateController], [DocDrawingPopupMenuController], ]; dependencies.forEach((dependency) => injector.add(dependency)); } + + override onReady(): void { + ([ + DocDrawingUpdateRenderController, + ]).forEach((m) => this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_DOC, m)); + } } diff --git a/packages/docs-ui/src/controllers/doc-editor-bridge.controller.ts b/packages/docs-ui/src/controllers/doc-editor-bridge.controller.ts index 40b9bb9091..6a60d68f2d 100644 --- a/packages/docs-ui/src/controllers/doc-editor-bridge.controller.ts +++ b/packages/docs-ui/src/controllers/doc-editor-bridge.controller.ts @@ -16,7 +16,6 @@ import type { ICommandInfo, Nullable, Workbook } from '@univerjs/core'; import { checkForSubstrings, Disposable, ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; -import { Inject } from '@wendellhu/redi'; import { IRenderManagerService, ITextSelectionRenderManager, ScrollBar } from '@univerjs/engine-render'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import { CoverContentCommand, DocSkeletonManagerService, RichTextEditingMutation, VIEWPORT_KEY } from '@univerjs/docs'; @@ -29,12 +28,10 @@ export class DocEditorBridgeController extends Disposable { constructor( @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IEditorService private readonly _editorService: IEditorService, @ICommandService private readonly _commandService: ICommandService, @ITextSelectionRenderManager private readonly _textSelectionRenderManager: ITextSelectionRenderManager, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService - ) { super(); this._initialize(); @@ -78,10 +75,13 @@ export class DocEditorBridgeController extends Disposable { return; } - const skeleton = this._docSkeletonManagerService.getSkeletonByUnitId(unitId)?.skeleton; - const editorDataModel = this._univerInstanceService.getUniverDocInstance(unitId); + if (!editorDataModel) { + return; + } + const skeleton = this._renderManagerService.getRenderById(editorDataModel.getUnitId()) + ?.with(DocSkeletonManagerService).getSkeleton(); if (editor == null || editor.render == null || skeleton == null || editorDataModel == null) { return; } diff --git a/packages/docs-ui/src/controllers/doc-floating-object.controller.ts b/packages/docs-ui/src/controllers/doc-floating-object.controller.ts deleted file mode 100644 index b5bca74aaa..0000000000 --- a/packages/docs-ui/src/controllers/doc-floating-object.controller.ts +++ /dev/null @@ -1,240 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICommandInfo } from '@univerjs/core'; -import { - BooleanNumber, - DEFAULT_DOCUMENT_SUB_COMPONENT_ID, - Disposable, - ICommandService, - LifecycleStages, - OnLifecycle, - PositionedObjectLayoutType, -} from '@univerjs/core'; -import type { IRichTextEditingMutationParams } from '@univerjs/docs'; -import { DocSkeletonManagerService, RichTextEditingMutation, SetDocZoomRatioOperation } from '@univerjs/docs'; -import type { Documents, DocumentSkeleton, IRender } from '@univerjs/engine-render'; -import { IRenderManagerService, Liquid } from '@univerjs/engine-render'; -import { IEditorService } from '@univerjs/ui'; -import { Inject } from '@wendellhu/redi'; - -@OnLifecycle(LifecycleStages.Steady, DocFloatingObjectController) -export class DocFloatingObjectController extends Disposable { - private _liquid = new Liquid(); - - private _pageMarginCache = new Map(); - - constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @ICommandService private readonly _commandService: ICommandService, - @IEditorService private readonly _editorService: IEditorService - ) { - super(); - - this._initialize(); - - this._commandExecutedListener(); - } - - private _initialize() { - this._initialRenderRefresh(); - - this._updateOnPluginChange(); - } - - private _updateOnPluginChange() { - // this._drawingManagerService.pluginUpdate$.subscribe((params) => { - // const docsSkeletonObject = this._docSkeletonManagerService.getCurrent(); - - // if (docsSkeletonObject == null) { - // return; - // } - - // const { unitId, skeleton } = docsSkeletonObject; - - // const currentRender = this._renderManagerService.getRenderById(unitId); - - // if (currentRender == null) { - // return; - // } - - // const { mainComponent, components, scene } = currentRender; - - // const docsComponent = mainComponent as Documents; - - // const { left: docsLeft, top: docsTop } = docsComponent; - - // params.forEach((param) => { - // const { unitId, subUnitId, drawingId, drawing } = param; - - // const { left = 0, top = 0, width = 0, height = 0, angle, flipX, flipY, skewX, skewY } = drawing; - - // const cache = this._pageMarginCache.get(drawingId); - - // const marginLeft = cache?.marginLeft || 0; - // const marginTop = cache?.marginTop || 0; - - // skeleton - // ?.getViewModel() - // .getDataModel() - // .updateDrawing(drawingId, { - // left: left - docsLeft - marginLeft, - // top: top - docsTop - marginTop, - // height, - // width, - // }); - // }); - - // skeleton?.calculate(); - // mainComponent?.makeDirty(); - // }); - } - - private _initialRenderRefresh() { - this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { - if (param == null) { - return; - } - - const { skeleton: documentSkeleton, unitId } = param; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - const { mainComponent } = currentRender; - - const docsComponent = mainComponent as Documents; - - // TODO: Why NEED change skeleton here? - docsComponent.changeSkeleton(documentSkeleton); - - this._refreshDrawing(unitId, documentSkeleton, currentRender); - }); - } - - private _commandExecutedListener() { - const updateCommandList = [RichTextEditingMutation.id, SetDocZoomRatioOperation.id]; - - this.disposeWithMe( - this._commandService.onCommandExecuted((command: ICommandInfo) => { - if (updateCommandList.includes(command.id)) { - const params = command.params as IRichTextEditingMutationParams; - const { unitId: commandUnitId } = params; - - const docsSkeletonObject = this._docSkeletonManagerService.getCurrent(); - - if (docsSkeletonObject == null) { - return; - } - - const { unitId, skeleton } = docsSkeletonObject; - - if (commandUnitId !== unitId) { - return; - } - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - if (this._editorService.isEditor(unitId)) { - currentRender.mainComponent?.makeDirty(); - return; - } - - this._refreshDrawing(unitId, skeleton, currentRender); - - // this.calculatePagePosition(currentRender); - } - }) - ); - } - - private _refreshDrawing(unitId: string, skeleton: DocumentSkeleton, currentRender: IRender) { - const skeletonData = skeleton?.getSkeletonData(); - - const { mainComponent, scene } = currentRender; - - const documentComponent = mainComponent as Documents; - - if (!skeletonData) { - return; - } - - const { left: docsLeft, top: docsTop, pageLayoutType, pageMarginLeft, pageMarginTop } = documentComponent; - - const { pages } = skeletonData; - - const floatObjects: any[] = []; // IFloatingObjectManagerParam - - const { scaleX, scaleY } = scene.getAncestorScale(); - - this._liquid.reset(); - - this._pageMarginCache.clear(); - - // const objectList: BaseObject[] = []; - // const pageMarginCache = new Map(); - - // const cumPageLeft = 0; - // const cumPageTop = 0; - /** - * TODO: @DR-Univer We should not refresh all floating elements, but instead make a diff. - */ - for (let i = 0, len = pages.length; i < len; i++) { - const page = pages[i]; - const { skeDrawings, marginLeft, marginTop } = page; - // cumPageLeft + = pageWidth + documents.pageMarginLeft; - - this._liquid.translatePagePadding(page); - - skeDrawings.forEach((drawing) => { - const { aLeft, aTop, height, width, drawingId, drawingOrigin } = drawing; - const behindText = drawingOrigin.layoutType === PositionedObjectLayoutType.WRAP_NONE && drawingOrigin.behindDoc === BooleanNumber.TRUE; - - floatObjects.push({ - unitId, - subUnitId: DEFAULT_DOCUMENT_SUB_COMPONENT_ID, - floatingObjectId: drawingId, - behindText, - floatingObject: { - left: aLeft + docsLeft + this._liquid.x, - top: aTop + docsTop + this._liquid.y, - width, - height, - }, - }); - - this._pageMarginCache.set(drawingId, { - marginLeft: this._liquid.x, - marginTop: this._liquid.y, - }); - }); - - this._liquid.restorePagePadding(page); - - this._liquid.translatePage(page, pageLayoutType, pageMarginLeft, pageMarginTop); - } - - // this._floatingObjectManagerService.batchAddOrUpdate(floatObjects); - } -} diff --git a/packages/docs-ui/src/controllers/doc-render.controller.ts b/packages/docs-ui/src/controllers/doc-render.controller.ts deleted file mode 100644 index ebb0bd9882..0000000000 --- a/packages/docs-ui/src/controllers/doc-render.controller.ts +++ /dev/null @@ -1,173 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICommandInfo, Nullable } from '@univerjs/core'; -import { - ICommandService, - LifecycleStages, - OnLifecycle, - RxDisposable } from '@univerjs/core'; -import type { DocBackground, Documents, DocumentSkeleton, IRender } from '@univerjs/engine-render'; -import { IRenderManagerService, PageLayoutType } from '@univerjs/engine-render'; -import { Inject } from '@wendellhu/redi'; -import { takeUntil } from 'rxjs'; - -import { IEditorService } from '@univerjs/ui'; -import type { IDocSkeletonManagerParam, IRichTextEditingMutationParams } from '@univerjs/docs'; -import { DOCS_VIEW_KEY, DocSkeletonManagerService, RichTextEditingMutation } from '@univerjs/docs'; - -@OnLifecycle(LifecycleStages.Rendered, DocRenderController) -export class DocRenderController extends RxDisposable { - private _docRenderMap = new Set(); - - constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @ICommandService private readonly _commandService: ICommandService, - @IEditorService private readonly _editorService: IEditorService - ) { - super(); - - this._initialRenderRefresh(); - - this._commandExecutedListener(); - } - - private _initialRenderRefresh() { - this._docSkeletonManagerService.currentSkeletonBefore$.pipe(takeUntil(this.dispose$)).subscribe((param) => { - this._create(param); - }); - } - - private _create(param: Nullable) { - if (param == null) { - return; - } - - const { skeleton: documentSkeleton, unitId } = param; - - let currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - this._renderManagerService.create(unitId); - currentRender = this._renderManagerService.getRenderById(unitId)!; - } - - const { mainComponent, components } = currentRender; - - const docsComponent = mainComponent as Documents; - - const docBackground = components.get(DOCS_VIEW_KEY.BACKGROUND) as DocBackground; - - docsComponent.changeSkeleton(documentSkeleton); - - docBackground.changeSkeleton(documentSkeleton); - - this._recalculateSizeBySkeleton(currentRender, documentSkeleton); - } - - private _recalculateSizeBySkeleton(currentRender: IRender, skeleton: DocumentSkeleton) { - const { mainComponent, scene, unitId, components } = currentRender; - - const docsComponent = mainComponent as Documents; - - const docBackground = components.get(DOCS_VIEW_KEY.BACKGROUND) as DocBackground; - - const pages = skeleton.getSkeletonData()?.pages; - - if (pages == null) { - return; - } - - let width = 0; - let height = 0; - - for (let i = 0, len = pages.length; i < len; i++) { - const page = pages[i]; - const { pageWidth, pageHeight } = page; - - if (docsComponent.pageLayoutType === PageLayoutType.VERTICAL) { - height += pageHeight; - - height += docsComponent.pageMarginTop; - - if (i === len - 1) { - height += docsComponent.pageMarginTop; - } - - width = Math.max(width, pageWidth); - } else if (docsComponent.pageLayoutType === PageLayoutType.HORIZONTAL) { - width += pageWidth; - - if (i !== len - 1) { - width += docsComponent.pageMarginLeft; - } - height = Math.max(height, pageHeight); - } - } - - docsComponent.resize(width, height); - docBackground.resize(width, height); - - if (!this._editorService.isEditor(unitId)) { - scene.resize(width, height); - } - } - - private _commandExecutedListener() { - const updateCommandList = [RichTextEditingMutation.id]; - - this.disposeWithMe( - this._commandService.onCommandExecuted((command: ICommandInfo) => { - if (updateCommandList.includes(command.id)) { - const params = command.params as IRichTextEditingMutationParams; - const { unitId } = params; - - const docsSkeletonObject = this._docSkeletonManagerService.getSkeletonByUnitId(unitId); - - if (docsSkeletonObject == null) { - return; - } - - const { skeleton } = docsSkeletonObject; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - // TODO: `disabled` is only used for read only demo, and will be removed in the future. - const disabled = !!skeleton.getViewModel().getDataModel().getSnapshot().disabled; - - if (disabled) { - return; - } - - skeleton.calculate(); - - if (this._editorService.isEditor(unitId)) { - currentRender.mainComponent?.makeDirty(); - - return; - } - - this._recalculateSizeBySkeleton(currentRender, skeleton); - } - }) - ); - } -} diff --git a/packages/docs-ui/src/controllers/back-scroll.controller.ts b/packages/docs-ui/src/controllers/render-controllers/back-scroll.render-controller.ts similarity index 77% rename from packages/docs-ui/src/controllers/back-scroll.controller.ts rename to packages/docs-ui/src/controllers/render-controllers/back-scroll.render-controller.ts index 19e885d624..d634f08cc6 100644 --- a/packages/docs-ui/src/controllers/back-scroll.controller.ts +++ b/packages/docs-ui/src/controllers/render-controllers/back-scroll.render-controller.ts @@ -14,23 +14,23 @@ * limitations under the License. */ -import { IUniverInstanceService, LifecycleStages, OnLifecycle, RxDisposable } from '@univerjs/core'; -import { DocSkeletonManagerService, getDocObject, TextSelectionManagerService, VIEWPORT_KEY } from '@univerjs/docs'; -import { getAnchorBounding, IRenderManagerService, NodePositionConvertToCursor } from '@univerjs/engine-render'; +import type { DocumentDataModel } from '@univerjs/core'; +import { RxDisposable } from '@univerjs/core'; +import { DocSkeletonManagerService, neoGetDocObject, TextSelectionManagerService, VIEWPORT_KEY } from '@univerjs/docs'; +import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; +import { getAnchorBounding, NodePositionConvertToCursor } from '@univerjs/engine-render'; import { IEditorService } from '@univerjs/ui'; import { Inject } from '@wendellhu/redi'; import { takeUntil } from 'rxjs'; const ANCHOR_WIDTH = 1.5; -@OnLifecycle(LifecycleStages.Rendered, BackScrollController) -export class BackScrollController extends RxDisposable { +export class DocBackScrollRenderController extends RxDisposable implements IRenderModule { constructor( + private readonly _context: IRenderContext, @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @IEditorService private readonly _editorService: IEditorService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService + @IEditorService private readonly _editorService: IEditorService ) { super(); @@ -54,10 +54,10 @@ export class BackScrollController extends RxDisposable { // Let the selection show on the current screen. private _scrollToSelection(unitId: string) { const activeTextRange = this._textSelectionManagerService.getActiveRange(); - const docObject = this._getDocObject(); - const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; + const docObject = neoGetDocObject(this._context); + const skeleton = this._docSkeletonManagerService.getSkeleton(); - if (activeTextRange == null || docObject == null || skeleton == null) { + if (activeTextRange == null || docObject == null) { return; } @@ -115,8 +115,4 @@ export class BackScrollController extends RxDisposable { const config = viewportMain.transViewportScroll2ScrollValue(offsetX, offsetY); viewportMain.scrollBy(config); } - - private _getDocObject() { - return getDocObject(this._univerInstanceService, this._renderManagerService); - } } diff --git a/packages/docs-ui/src/controllers/drawing.controller.ts b/packages/docs-ui/src/controllers/render-controllers/doc-floating-object.render-controller.ts similarity index 55% rename from packages/docs-ui/src/controllers/drawing.controller.ts rename to packages/docs-ui/src/controllers/render-controllers/doc-floating-object.render-controller.ts index b5bca74aaa..3417379832 100644 --- a/packages/docs-ui/src/controllers/drawing.controller.ts +++ b/packages/docs-ui/src/controllers/render-controllers/doc-floating-object.render-controller.ts @@ -14,32 +14,29 @@ * limitations under the License. */ -import type { ICommandInfo } from '@univerjs/core'; +import type { DocumentDataModel, ICommandInfo } from '@univerjs/core'; import { BooleanNumber, DEFAULT_DOCUMENT_SUB_COMPONENT_ID, Disposable, ICommandService, - LifecycleStages, - OnLifecycle, PositionedObjectLayoutType, } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import { DocSkeletonManagerService, RichTextEditingMutation, SetDocZoomRatioOperation } from '@univerjs/docs'; -import type { Documents, DocumentSkeleton, IRender } from '@univerjs/engine-render'; -import { IRenderManagerService, Liquid } from '@univerjs/engine-render'; +import type { Documents, DocumentSkeleton, IRenderContext, IRenderModule } from '@univerjs/engine-render'; +import { Liquid } from '@univerjs/engine-render'; import { IEditorService } from '@univerjs/ui'; import { Inject } from '@wendellhu/redi'; -@OnLifecycle(LifecycleStages.Steady, DocFloatingObjectController) -export class DocFloatingObjectController extends Disposable { +export class DocFloatingObjectRenderController extends Disposable implements IRenderModule { private _liquid = new Liquid(); private _pageMarginCache = new Map(); constructor( + private readonly _context: IRenderContext, @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @ICommandService private readonly _commandService: ICommandService, @IEditorService private readonly _editorService: IEditorService ) { @@ -51,81 +48,12 @@ export class DocFloatingObjectController extends Disposable { } private _initialize() { - this._initialRenderRefresh(); - - this._updateOnPluginChange(); - } - - private _updateOnPluginChange() { - // this._drawingManagerService.pluginUpdate$.subscribe((params) => { - // const docsSkeletonObject = this._docSkeletonManagerService.getCurrent(); - - // if (docsSkeletonObject == null) { - // return; - // } - - // const { unitId, skeleton } = docsSkeletonObject; - - // const currentRender = this._renderManagerService.getRenderById(unitId); - - // if (currentRender == null) { - // return; - // } - - // const { mainComponent, components, scene } = currentRender; - - // const docsComponent = mainComponent as Documents; - - // const { left: docsLeft, top: docsTop } = docsComponent; - - // params.forEach((param) => { - // const { unitId, subUnitId, drawingId, drawing } = param; - - // const { left = 0, top = 0, width = 0, height = 0, angle, flipX, flipY, skewX, skewY } = drawing; - - // const cache = this._pageMarginCache.get(drawingId); - - // const marginLeft = cache?.marginLeft || 0; - // const marginTop = cache?.marginTop || 0; - - // skeleton - // ?.getViewModel() - // .getDataModel() - // .updateDrawing(drawingId, { - // left: left - docsLeft - marginLeft, - // top: top - docsTop - marginTop, - // height, - // width, - // }); - // }); - - // skeleton?.calculate(); - // mainComponent?.makeDirty(); - // }); - } - - private _initialRenderRefresh() { - this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { - if (param == null) { + this._docSkeletonManagerService.currentSkeleton$.subscribe((skeleton) => { + if (skeleton == null) { return; } - const { skeleton: documentSkeleton, unitId } = param; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - const { mainComponent } = currentRender; - - const docsComponent = mainComponent as Documents; - - // TODO: Why NEED change skeleton here? - docsComponent.changeSkeleton(documentSkeleton); - - this._refreshDrawing(unitId, documentSkeleton, currentRender); + this._refreshDrawing(skeleton); }); } @@ -136,43 +64,28 @@ export class DocFloatingObjectController extends Disposable { this._commandService.onCommandExecuted((command: ICommandInfo) => { if (updateCommandList.includes(command.id)) { const params = command.params as IRichTextEditingMutationParams; - const { unitId: commandUnitId } = params; - - const docsSkeletonObject = this._docSkeletonManagerService.getCurrent(); + const { unitId } = params; - if (docsSkeletonObject == null) { - return; - } - - const { unitId, skeleton } = docsSkeletonObject; - - if (commandUnitId !== unitId) { - return; - } - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { + const skeleton = this._docSkeletonManagerService.getSkeleton(); + if (!skeleton) { return; } if (this._editorService.isEditor(unitId)) { - currentRender.mainComponent?.makeDirty(); + this._context.mainComponent?.makeDirty(); return; } - this._refreshDrawing(unitId, skeleton, currentRender); - - // this.calculatePagePosition(currentRender); + this._refreshDrawing(skeleton); } }) ); } - private _refreshDrawing(unitId: string, skeleton: DocumentSkeleton, currentRender: IRender) { + private _refreshDrawing(skeleton: DocumentSkeleton) { const skeletonData = skeleton?.getSkeletonData(); - const { mainComponent, scene } = currentRender; + const { mainComponent, scene, unitId } = this._context; const documentComponent = mainComponent as Documents; diff --git a/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts b/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts new file mode 100644 index 0000000000..07ee01bf07 --- /dev/null +++ b/packages/docs-ui/src/controllers/render-controllers/doc.render-controller.ts @@ -0,0 +1,228 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel, EventState, ICommandInfo, Nullable } from '@univerjs/core'; +import { ICommandService, IConfigService, RxDisposable } from '@univerjs/core'; +import type { IRichTextEditingMutationParams } from '@univerjs/docs'; +import { DOCS_COMPONENT_BACKGROUND_LAYER_INDEX, DOCS_COMPONENT_DEFAULT_Z_INDEX, DOCS_COMPONENT_HEADER_LAYER_INDEX, DOCS_COMPONENT_MAIN_LAYER_INDEX, DOCS_VIEW_KEY, DocSkeletonManagerService, RichTextEditingMutation, VIEWPORT_KEY } from '@univerjs/docs'; +import type { DocumentSkeleton, IRenderContext, IRenderModule, IWheelEvent } from '@univerjs/engine-render'; +import { DocBackground, Documents, EVENT_TYPE, Layer, PageLayoutType, ScrollBar, Viewport } from '@univerjs/engine-render'; +import { IEditorService } from '@univerjs/ui'; +import { Inject } from '@wendellhu/redi'; +import { takeUntil } from 'rxjs'; + +export class DocRenderController extends RxDisposable implements IRenderModule { + constructor( + private readonly _context: IRenderContext, + @ICommandService private readonly _commandService: ICommandService, + @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, + @IConfigService private readonly _configService: IConfigService, + @IEditorService private readonly _editorService: IEditorService + ) { + super(); + + this._addNewRender(); + this._initRenderRefresh(); + this._initCommandListener(); + } + + private _addNewRender() { + const { scene, engine, unit } = this._context; + + const viewMain = new Viewport(VIEWPORT_KEY.VIEW_MAIN, scene, { + left: 0, + top: 0, + bottom: 0, + right: 0, + isRelativeX: true, + isRelativeY: true, + isWheelPreventDefaultX: true, + }); + + scene.attachControl(); + + scene.on(EVENT_TYPE.wheel, (evt: unknown, state: EventState) => { + const e = evt as IWheelEvent; + + if (e.ctrlKey) { + const deltaFactor = Math.abs(e.deltaX); + let scrollNum = deltaFactor < 40 ? 0.2 : deltaFactor < 80 ? 0.4 : 0.2; + scrollNum *= e.deltaY > 0 ? -1 : 1; + if (scene.scaleX < 1) { + scrollNum /= 2; + } + + if (scene.scaleX + scrollNum > 4) { + scene.scale(4, 4); + } else if (scene.scaleX + scrollNum < 0.1) { + scene.scale(0.1, 0.1); + } else { + // const value = e.deltaY > 0 ? 0.1 : -0.1; + // scene.scaleBy(scrollNum, scrollNum); + e.preventDefault(); + } + } else { + viewMain.onMouseWheel(e, state); + } + }); + + // TODO@wzhudev: this shouldn't be a config, because we may render different units at the same time. + const hasScroll = this._configService.getConfig('hasScroll') as Nullable; + if (hasScroll !== false) { + new ScrollBar(viewMain); + } + + scene.addLayer( + new Layer(scene, [], DOCS_COMPONENT_MAIN_LAYER_INDEX), + new Layer(scene, [], DOCS_COMPONENT_HEADER_LAYER_INDEX) + ); + + this._addComponent(); + + const should = unit.getShouldRenderLoopImmediately(); + if (should) { + engine.runRenderLoop(() => { + scene.render(); + }); + } + } + + private _addComponent() { + const { scene, unit: documentModel, components } = this._context; + const config = { + pageMarginLeft: documentModel.documentStyle.marginLeft || 0, + pageMarginTop: documentModel.documentStyle.marginTop || 0, + }; + + const documents = new Documents(DOCS_VIEW_KEY.MAIN, undefined, config); + documents.zIndex = DOCS_COMPONENT_DEFAULT_Z_INDEX; + const docBackground = new DocBackground(DOCS_VIEW_KEY.BACKGROUND, undefined, config); + docBackground.zIndex = DOCS_COMPONENT_DEFAULT_Z_INDEX; + + this._context.mainComponent = documents; + components.set(DOCS_VIEW_KEY.MAIN, documents); + components.set(DOCS_VIEW_KEY.BACKGROUND, docBackground); + + scene.addObjects([documents], DOCS_COMPONENT_MAIN_LAYER_INDEX); + scene.addObjects([docBackground], DOCS_COMPONENT_BACKGROUND_LAYER_INDEX); + + if (this._editorService.getEditor(documentModel.getUnitId()) == null) { + scene.enableLayerCache(DOCS_COMPONENT_MAIN_LAYER_INDEX); + } + } + + private _initRenderRefresh() { + this._docSkeletonManagerService.currentSkeletonBefore$.pipe(takeUntil(this.dispose$)).subscribe((param) => { + this._create(param); + }); + } + + private _create(skeleton: Nullable) { + if (!skeleton) { + return; + } + + const { mainComponent, components } = this._context; + + const docsComponent = mainComponent as Documents; + const docBackground = components.get(DOCS_VIEW_KEY.BACKGROUND) as DocBackground; + + docsComponent.changeSkeleton(skeleton); + docBackground.changeSkeleton(skeleton); + + this._recalculateSizeBySkeleton(skeleton); + } + + private _initCommandListener() { + const updateCommandList = [RichTextEditingMutation.id]; + + this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { + // TODO@Jocs: performance, only update the skeleton when the command is related to the current unit. + if (updateCommandList.includes(command.id)) { + const params = command.params as IRichTextEditingMutationParams; + const { unitId } = params; + + const skeleton = this._docSkeletonManagerService.getSkeleton(); + if (!skeleton) { + return; + } + + // TODO: `disabled` is only used for read only demo, and will be removed in the future. + const disabled = !!skeleton.getViewModel().getDataModel().getSnapshot().disabled; + if (disabled) { + return; + } + + skeleton.calculate(); + + if (this._editorService.isEditor(unitId)) { + this._context.mainComponent?.makeDirty(); + + return; + } + + this._recalculateSizeBySkeleton(skeleton); + } + })); + } + + private _recalculateSizeBySkeleton(skeleton: DocumentSkeleton) { + const { mainComponent, scene, unitId, components } = this._context; + + const docsComponent = mainComponent as Documents; + + const docBackground = components.get(DOCS_VIEW_KEY.BACKGROUND) as DocBackground; + + const pages = skeleton.getSkeletonData()?.pages; + if (pages == null) { + return; + } + + let width = 0; + let height = 0; + + for (let i = 0, len = pages.length; i < len; i++) { + const page = pages[i]; + const { pageWidth, pageHeight } = page; + + if (docsComponent.pageLayoutType === PageLayoutType.VERTICAL) { + height += pageHeight; + + height += docsComponent.pageMarginTop; + + if (i === len - 1) { + height += docsComponent.pageMarginTop; + } + + width = Math.max(width, pageWidth); + } else if (docsComponent.pageLayoutType === PageLayoutType.HORIZONTAL) { + width += pageWidth; + + if (i !== len - 1) { + width += docsComponent.pageMarginLeft; + } + height = Math.max(height, pageHeight); + } + } + + docsComponent.resize(width, height); + docBackground.resize(width, height); + + if (!this._editorService.isEditor(unitId)) { + scene.resize(width, height); + } + } +} diff --git a/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts b/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts new file mode 100644 index 0000000000..2d6179564f --- /dev/null +++ b/packages/docs-ui/src/controllers/render-controllers/text-selection.render-controller.ts @@ -0,0 +1,203 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel, ICommandInfo } from '@univerjs/core'; +import { Disposable, ICommandService, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import type { Documents, IMouseEvent, IPointerEvent, IRenderContext, IRenderModule, RenderComponentType } from '@univerjs/engine-render'; +import { CURSOR_TYPE, ITextSelectionRenderManager } from '@univerjs/engine-render'; +import { Inject } from '@wendellhu/redi'; + +import { IEditorService } from '@univerjs/ui'; +import type { ISetDocZoomRatioOperationParams } from '@univerjs/docs'; +import { DocSkeletonManagerService, neoGetDocObject, SetDocZoomRatioOperation, TextSelectionManagerService } from '@univerjs/docs'; + +export class DocTextSelectionRenderController extends Disposable implements IRenderModule { + private _loadedMap = new WeakSet(); + + constructor( + private readonly _context: IRenderContext, + @ICommandService private readonly _commandService: ICommandService, + @IEditorService private readonly _editorService: IEditorService, + @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, + @ITextSelectionRenderManager private readonly _textSelectionRenderManager: ITextSelectionRenderManager, + @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, + @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService + ) { + super(); + + this._initialize(); + } + + private _initialize() { + this._init(); + this._skeletonListener(); + this._commandExecutedListener(); + } + + private _init() { + const { unitId } = this._context; + const docObject = neoGetDocObject(this._context); + if (docObject == null || docObject.document == null) { + return; + } + + if (!this._loadedMap.has(docObject.document)) { + this._initialMain(unitId); + this._loadedMap.add(docObject.document); + } + } + + private _initialMain(unitId: string) { + const docObject = neoGetDocObject(this._context); + const { document, scene } = docObject; + this.disposeWithMe(document.onPointerEnterObserver.add(() => { + if (this._isEditorReadOnly(unitId)) { + return; + } + document.cursor = CURSOR_TYPE.TEXT; + })); + + this.disposeWithMe(document.onPointerLeaveObserver.add(() => { + document.cursor = CURSOR_TYPE.DEFAULT; + scene.resetCursor(); + })); + + this.disposeWithMe(document.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { + if (this._isEditorReadOnly(unitId)) { + return; + } + + // FIXME:@Jocs: editor status should not be coupled with the instance service. + const currentDocInstance = this._instanceSrv.getCurrentUnitForType(UniverInstanceType.UNIVER_DOC); + if (currentDocInstance?.getUnitId() !== unitId) { + this._instanceSrv.setCurrentUnitForType(unitId); + } + + this._textSelectionRenderManager.eventTrigger(evt); + + if (this._editorService.getEditor(unitId)) { + /** + * To accommodate focus switching between different editors. + * Since the editor for Univer is canvas-based, + * it primarily relies on focus and cannot use the focus event. + * Our editor's focus monitoring is based on PointerDown. + * The order of occurrence is such that PointerDown comes first. + * Translate the above text into English. + */ + this._setEditorFocus(unitId); + const { offsetX, offsetY } = evt; + + setTimeout(() => { + this._setEditorFocus(unitId); + this._textSelectionRenderManager.setCursorManually(offsetX, offsetY); + }, 0); + } + + if (evt.button !== 2) { + state.stopPropagation(); + } + })); + + this.disposeWithMe(document.onDblclickObserver.add((evt: IPointerEvent | IMouseEvent) => { + if (this._isEditorReadOnly(unitId)) { + return; + } + + this._textSelectionRenderManager.handleDblClick(evt); + })); + + this.disposeWithMe(document.onTripleClickObserver.add((evt: IPointerEvent | IMouseEvent) => { + if (this._isEditorReadOnly(unitId)) { + return; + } + this._textSelectionRenderManager.handleTripleClick(evt); + })); + } + + private _isEditorReadOnly(unitId: string) { + const editor = this._editorService.getEditor(unitId); + if (!editor) { + return false; + } + + return editor.isReadOnly(); + } + + private _setEditorFocus(unitId: string) { + // TODO@wzhudev: fix + /** + * The object for selecting data in the editor is set to the current sheet. + */ + // const sheetInstances = this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET); + // if (sheetInstances.length > 0) { + // const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; + // this._editorService.setOperationSheetUnitId(workbook.getUnitId()); + // // this._editorService.setOperationSheetSubUnitId(workbook.getActiveSheet().getSheetId()); + // } + + this._editorService.focusStyle(unitId); + } + + private _commandExecutedListener() { + const updateCommandList = [SetDocZoomRatioOperation.id]; + + this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { + if (updateCommandList.includes(command.id)) { + const params = command.params as ISetDocZoomRatioOperationParams; + const { unitId: documentId } = params; + + const unitId = this._textSelectionManagerService.getCurrentSelection()?.unitId; + + if (documentId !== unitId) { + return; + } + + this._textSelectionManagerService.refreshSelection(); + } + }) + ); + } + + private _skeletonListener() { + // Change text selection runtime(skeleton, scene) and update text selection manager current selection. + this.disposeWithMe(this._docSkeletonManagerService.currentSkeleton$.subscribe((skeleton) => { + if (skeleton == null) { + return; + } + + const { scene, mainComponent, unitId } = this._context; + + this._textSelectionRenderManager.changeRuntime(skeleton, scene, mainComponent as Documents); + + this._textSelectionManagerService.setCurrentSelectionNotRefresh({ + unitId, + subUnitId: '', + }); + + // The initial cursor is set at the beginning of the document, + // and can be set to the previous cursor position in the future. + if (!this._editorService.isEditor(unitId)) { + this._textSelectionManagerService.replaceTextRanges([ + { + startOffset: 0, + endOffset: 0, + }, + ], false); + } + }) + ); + } +} diff --git a/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts b/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts new file mode 100644 index 0000000000..061b928a06 --- /dev/null +++ b/packages/docs-ui/src/controllers/render-controllers/zoom.render-controller.ts @@ -0,0 +1,206 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel, ICommandInfo } from '@univerjs/core'; +import { + Disposable, + ICommandService, + IUniverInstanceService, +} from '@univerjs/core'; +import type { IDocObjectParam } from '@univerjs/docs'; +import { DocSkeletonManagerService, neoGetDocObject, SetDocZoomRatioCommand, SetDocZoomRatioOperation, TextSelectionManagerService, VIEWPORT_KEY } from '@univerjs/docs'; +import type { IRenderContext, IRenderModule, IWheelEvent } from '@univerjs/engine-render'; +import { IEditorService } from '@univerjs/ui'; +import { Inject } from '@wendellhu/redi'; + +interface ISetDocMutationParams { + unitId: string; +} + +export class DocZoomRenderController extends Disposable implements IRenderModule { + constructor( + private readonly _context: IRenderContext, + @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @ICommandService private readonly _commandService: ICommandService, + @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, + @IEditorService private readonly _editorService: IEditorService + ) { + super(); + + this._init(); + } + + private _init() { + this._initSkeletonListener(); + this._initCommandExecutedListener(); + this._initRenderRefresher(); + + setTimeout(() => this._updateViewZoom(1, true), 20); + } + + private _initRenderRefresher() { + this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { + if (param == null) { + return; + } + + const { unitId, scene } = this._context; + if (this._editorService.isEditor(unitId)) { + return; + } + + this.disposeWithMe(scene.onMouseWheelObserver.add((e: IWheelEvent) => { + if (!e.ctrlKey) { + return; + } + + const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); + if (!documentModel) { + return; + } + + const deltaFactor = Math.abs(e.deltaX); + let ratioDelta = deltaFactor < 40 ? 0.2 : deltaFactor < 80 ? 0.4 : 0.2; + ratioDelta *= e.deltaY > 0 ? -1 : 1; + if (scene.scaleX < 1) { + ratioDelta /= 2; + } + + const currentRatio = documentModel.zoomRatio; + + let nextRatio = +Number.parseFloat(`${currentRatio + ratioDelta}`).toFixed(1); + nextRatio = nextRatio >= 4 ? 4 : nextRatio <= 0.1 ? 0.1 : nextRatio; + + this._commandService.executeCommand(SetDocZoomRatioCommand.id, { + zoomRatio: nextRatio, + unitId: documentModel.getUnitId(), + }); + + e.preventDefault(); + })); + }); + } + + private _initSkeletonListener() { + this.disposeWithMe(this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { + if (param == null) { + return; + } + + const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); + if (!documentModel) return; + + const zoomRatio = documentModel.zoomRatio || 1; + + this._updateViewZoom(zoomRatio, false); + })); + } + + private _initCommandExecutedListener() { + const updateCommandList = [SetDocZoomRatioOperation.id]; + + this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { + if (updateCommandList.includes(command.id)) { + const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); + if (!documentModel) return; + + const params = command.params; + const { unitId } = params as ISetDocMutationParams; + if (!(unitId === documentModel.getUnitId())) { + return; + } + + const zoomRatio = documentModel.zoomRatio || 1; + + this._updateViewZoom(zoomRatio); + } + })); + } + + private _updateViewZoom(zoomRatio: number, needRefreshSelection = true) { + const docObject = neoGetDocObject(this._context); + docObject.scene.scale(zoomRatio, zoomRatio); + + this._calculatePagePosition(docObject, zoomRatio); + + if (needRefreshSelection) { + this._textSelectionManagerService.refreshSelection(); + } + + docObject.scene.getTransformer()?.clearSelectedObjects(); + } + + private _calculatePagePosition(currentRender: IDocObjectParam, zoomRatio: number) { + const { document: docsComponent, scene, docBackground } = currentRender; + + const parent = scene?.getParent(); + + const { width: docsWidth, height: docsHeight, pageMarginLeft, pageMarginTop } = docsComponent; + if (parent == null || docsWidth === Number.POSITIVE_INFINITY || docsHeight === Number.POSITIVE_INFINITY) { + return; + } + const { width: engineWidth, height: engineHeight } = parent; + let docsLeft = 0; + let docsTop = 0; + + let sceneWidth = 0; + + let sceneHeight = 0; + + let scrollToX = Number.POSITIVE_INFINITY; + + if (engineWidth > (docsWidth + pageMarginLeft * 2) * zoomRatio) { + docsLeft = engineWidth / 2 - (docsWidth * zoomRatio) / 2; + docsLeft /= zoomRatio; + sceneWidth = (engineWidth - pageMarginLeft * 2) / zoomRatio; + + scrollToX = 0; + } else { + docsLeft = pageMarginLeft; + sceneWidth = docsWidth + pageMarginLeft * 2; + + scrollToX = (sceneWidth - engineWidth / zoomRatio) / 2; + } + + if (engineHeight > docsHeight) { + docsTop = engineHeight / 2 - docsHeight / 2; + sceneHeight = (engineHeight - pageMarginTop * 2) / zoomRatio; + } else { + docsTop = pageMarginTop; + sceneHeight = docsHeight + pageMarginTop * 2; + } + + // this.docsLeft = docsLeft; + + // this.docsTop = docsTop; + + scene.resize(sceneWidth, sceneHeight + 200); + + docsComponent.translate(docsLeft, docsTop); + docBackground.translate(docsLeft, docsTop); + + const viewport = scene.getViewport(VIEWPORT_KEY.VIEW_MAIN); + if (scrollToX !== Number.POSITIVE_INFINITY && viewport != null) { + const actualX = viewport.transViewportScroll2ScrollValue(scrollToX, 0).x; + viewport.scrollTo({ + x: actualX, + }); + } + + return this; + } +} diff --git a/packages/docs-ui/src/controllers/text-selection.controller.ts b/packages/docs-ui/src/controllers/text-selection.controller.ts deleted file mode 100644 index 27d1f95492..0000000000 --- a/packages/docs-ui/src/controllers/text-selection.controller.ts +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICommandInfo, Nullable, Workbook } from '@univerjs/core'; -import { Disposable, ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle, toDisposable, UniverInstanceType } from '@univerjs/core'; -import type { Documents, IMouseEvent, IPointerEvent, RenderComponentType } from '@univerjs/engine-render'; -import { CURSOR_TYPE, IRenderManagerService, ITextSelectionRenderManager } from '@univerjs/engine-render'; -import { Inject } from '@wendellhu/redi'; - -import { IEditorService } from '@univerjs/ui'; -import type { ISetDocZoomRatioOperationParams } from '@univerjs/docs'; -import { DocSkeletonManagerService, getDocObjectById, SetDocZoomRatioOperation, TextSelectionManagerService } from '@univerjs/docs'; - -@OnLifecycle(LifecycleStages.Rendered, TextSelectionController) -export class TextSelectionController extends Disposable { - private _loadedMap = new WeakSet(); - - constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @ICommandService private readonly _commandService: ICommandService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @ITextSelectionRenderManager - private readonly _textSelectionRenderManager: ITextSelectionRenderManager, - @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @IEditorService private readonly _editorService: IEditorService - ) { - super(); - - this._initialize(); - } - - private _initialize() { - this._init(); - this._skeletonListener(); - this._commandExecutedListener(); - } - - private _init() { - this.disposeWithMe( - this._renderManagerService.currentRender$.subscribe((unitId) => { - this._create(unitId); - }) - ); - - this._renderManagerService.getRenderAll().forEach((_, unitId) => { - this._create(unitId); - }); - } - - private _create(unitId: Nullable) { - if (unitId == null) { - return; - } - - if (this._univerInstanceService.getUniverDocInstance(unitId) == null) { - return; - } - - const docObject = this._getDocObjectById(unitId); - if (docObject == null || docObject.document == null) { - return; - } - - if (!this._loadedMap.has(docObject.document)) { - this._initialMain(unitId); - this._loadedMap.add(docObject.document); - } - } - - // eslint-disable-next-line max-lines-per-function - private _initialMain(unitId: string) { - const docObject = this._getDocObjectById(unitId); - if (docObject == null) { - return; - } - - const { document, scene } = docObject; - this.disposeWithMe( - toDisposable( - document.onPointerEnterObserver.add(() => { - if (this._isEditorReadOnly(unitId)) { - return; - } - document.cursor = CURSOR_TYPE.TEXT; - }) - ) - ); - - this.disposeWithMe( - toDisposable( - document.onPointerLeaveObserver.add(() => { - document.cursor = CURSOR_TYPE.DEFAULT; - scene.resetCursor(); - }) - ) - ); - - this.disposeWithMe( - toDisposable( - document?.onPointerDownObserver.add((evt: IPointerEvent | IMouseEvent, state) => { - if (this._isEditorReadOnly(unitId)) { - return; - } - - const currentDocInstance = this._univerInstanceService.getCurrentUniverDocInstance(); - if (currentDocInstance?.getUnitId() !== unitId) { - this._univerInstanceService.setCurrentUnitForType(unitId); - } - - this._textSelectionRenderManager.eventTrigger(evt); - - const { offsetX, offsetY } = evt; - - if (this._editorService.getEditor(unitId)) { - /** - * To accommodate focus switching between different editors. - * Since the editor for Univer is canvas-based, - * it primarily relies on focus and cannot use the focus event. - * Our editor's focus monitoring is based on PointerDown. - * The order of occurrence is such that PointerDown comes first. - * Translate the above text into English. - */ - this._setEditorFocus(unitId); - setTimeout(() => { - this._setEditorFocus(unitId); - this._textSelectionRenderManager.setCursorManually(offsetX, offsetY); - }, 0); - } - - if (evt.button !== 2) { - state.stopPropagation(); - } - }) - ) - ); - - this.disposeWithMe( - toDisposable( - document?.onDblclickObserver.add((evt: IPointerEvent | IMouseEvent) => { - if (this._isEditorReadOnly(unitId)) { - return; - } - this._textSelectionRenderManager.handleDblClick(evt); - }) - ) - ); - - this.disposeWithMe( - toDisposable( - document?.onTripleClickObserver.add((evt: IPointerEvent | IMouseEvent) => { - if (this._isEditorReadOnly(unitId)) { - return; - } - this._textSelectionRenderManager.handleTripleClick(evt); - }) - ) - ); - } - - private _isEditorReadOnly(unitId: string) { - const editor = this._editorService.getEditor(unitId); - if (!editor) { - return false; - } - - return editor.isReadOnly(); - } - - private _setEditorFocus(unitId: string) { - /** - * The object for selecting data in the editor is set to the current sheet. - */ - const sheetInstances = this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET); - if (sheetInstances.length > 0) { - const workbook = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; - this._editorService.setOperationSheetUnitId(workbook.getUnitId()); - // this._editorService.setOperationSheetSubUnitId(workbook.getActiveSheet().getSheetId()); - } - - this._editorService.focusStyle(unitId); - } - - private _commandExecutedListener() { - const updateCommandList = [SetDocZoomRatioOperation.id]; - - this.disposeWithMe( - this._commandService.onCommandExecuted((command: ICommandInfo) => { - if (updateCommandList.includes(command.id)) { - const params = command.params as ISetDocZoomRatioOperationParams; - const { unitId: documentId } = params; - - const unitId = this._textSelectionManagerService.getCurrentSelection()?.unitId; - - if (documentId !== unitId) { - return; - } - - this._textSelectionManagerService.refreshSelection(); - } - }) - ); - } - - private _skeletonListener() { - // Change text selection runtime(skeleton, scene) and update text selection manager current selection. - this.disposeWithMe( - this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { - if (param == null) { - return; - } - const { unitId, skeleton } = param; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - const { scene, mainComponent } = currentRender; - - this._textSelectionRenderManager.changeRuntime(skeleton, scene, mainComponent as Documents); - - this._textSelectionManagerService.setCurrentSelectionNotRefresh({ - unitId, - subUnitId: '', - }); - - // The initial cursor is set at the beginning of the document, - // and can be set to the previous cursor position in the future. - if (!this._editorService.isEditor(unitId)) { - this._textSelectionManagerService.replaceTextRanges([ - { - startOffset: 0, - endOffset: 0, - }, - ], false); - } - }) - ); - } - - private _getDocObjectById(unitId: string) { - if (this._univerInstanceService.getUnitType(unitId) !== UniverInstanceType.UNIVER_DOC) { - return null; - } - - return getDocObjectById(unitId, this._renderManagerService); - } -} diff --git a/packages/docs-ui/src/controllers/zoom.controller.ts b/packages/docs-ui/src/controllers/zoom.controller.ts deleted file mode 100644 index 88373a2fb0..0000000000 --- a/packages/docs-ui/src/controllers/zoom.controller.ts +++ /dev/null @@ -1,255 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICommandInfo } from '@univerjs/core'; -import { - Disposable, - ICommandService, - IUniverInstanceService, - LifecycleStages, - OnLifecycle, - toDisposable, -} from '@univerjs/core'; -import type { IDocObjectParam } from '@univerjs/docs'; -import { DocSkeletonManagerService, getDocObject, SetDocZoomRatioCommand, SetDocZoomRatioOperation, TextSelectionManagerService, VIEWPORT_KEY } from '@univerjs/docs'; -import type { IWheelEvent } from '@univerjs/engine-render'; -import { IRenderManagerService } from '@univerjs/engine-render'; -import { IEditorService } from '@univerjs/ui'; -import { Inject } from '@wendellhu/redi'; - -interface ISetDocMutationParams { - unitId: string; -} - -@OnLifecycle(LifecycleStages.Rendered, ZoomController) -export class ZoomController extends Disposable { - private _initializedRender = new Set(); - - constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @ICommandService private readonly _commandService: ICommandService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @IEditorService private readonly _editorService: IEditorService - ) { - super(); - - this._initialize(); - } - - override dispose(): void { - super.dispose(); - } - - private _initialize() { - this._skeletonListener(); - this._commandExecutedListener(); - this._initialRenderRefresh(); - } - - private _initialRenderRefresh() { - this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { - if (param == null) { - return; - } - - const { unitId } = param; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - if ( - this._initializedRender.has(unitId) || this._editorService.isEditor(unitId) - ) { - return; - } - - this._initializedRender.add(unitId); - - const { scene } = currentRender; - - this.disposeWithMe( - toDisposable( - scene.onMouseWheelObserver.add((e: IWheelEvent) => { - if (!e.ctrlKey) { - return; - } - - const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); - if (!documentModel) { - return; - } - - const deltaFactor = Math.abs(e.deltaX); - let ratioDelta = deltaFactor < 40 ? 0.2 : deltaFactor < 80 ? 0.4 : 0.2; - ratioDelta *= e.deltaY > 0 ? -1 : 1; - if (scene.scaleX < 1) { - ratioDelta /= 2; - } - - const currentRatio = documentModel.zoomRatio; - - let nextRatio = +Number.parseFloat(`${currentRatio + ratioDelta}`).toFixed(1); - nextRatio = nextRatio >= 4 ? 4 : nextRatio <= 0.1 ? 0.1 : nextRatio; - - this._commandService.executeCommand(SetDocZoomRatioCommand.id, { - zoomRatio: nextRatio, - unitId: documentModel.getUnitId(), - }); - - e.preventDefault(); - }) - ) - ); - }); - } - - // private _zoomEventBinding() { - // const scene = this._getDocObject()?.scene; - // if (scene == null) { - // return; - // } - - // const viewportMain = scene.getViewport(VIEWPORT_KEY.VIEW_MAIN); - // } - - private _skeletonListener() { - this.disposeWithMe( - toDisposable( - this._docSkeletonManagerService.currentSkeleton$.subscribe((param) => { - if (param == null) { - return; - } - - const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); - if (!documentModel) return; - - const zoomRatio = documentModel.zoomRatio || 1; - - this._updateViewZoom(zoomRatio, false); - }) - ) - ); - } - - private _commandExecutedListener() { - const updateCommandList = [SetDocZoomRatioOperation.id]; - - this.disposeWithMe( - this._commandService.onCommandExecuted((command: ICommandInfo) => { - if (updateCommandList.includes(command.id)) { - const documentModel = this._univerInstanceService.getCurrentUniverDocInstance(); - if (!documentModel) return; - - const params = command.params; - const { unitId } = params as ISetDocMutationParams; - if (!(unitId === documentModel.getUnitId())) { - return; - } - - const zoomRatio = documentModel.zoomRatio || 1; - - this._updateViewZoom(zoomRatio); - } - }) - ); - } - - private _updateViewZoom(zoomRatio: number, needRefreshSelection = true) { - const docObject = this._getDocObject(); - if (docObject == null) { - return; - } - - docObject.scene.scale(zoomRatio, zoomRatio); - - this._calculatePagePosition(docObject, zoomRatio); - - if (needRefreshSelection) { - this._textSelectionManagerService.refreshSelection(); - } - - docObject.scene.getTransformer()?.clearSelectedObjects(); - } - - private _calculatePagePosition(currentRender: IDocObjectParam, zoomRatio: number) { - const { document: docsComponent, scene, docBackground } = currentRender; - - const parent = scene?.getParent(); - - const { width: docsWidth, height: docsHeight, pageMarginLeft, pageMarginTop } = docsComponent; - if (parent == null || docsWidth === Number.POSITIVE_INFINITY || docsHeight === Number.POSITIVE_INFINITY) { - return; - } - const { width: engineWidth, height: engineHeight } = parent; - let docsLeft = 0; - let docsTop = 0; - - let sceneWidth = 0; - - let sceneHeight = 0; - - let scrollToX = Number.POSITIVE_INFINITY; - - if (engineWidth > (docsWidth + pageMarginLeft * 2) * zoomRatio) { - docsLeft = engineWidth / 2 - (docsWidth * zoomRatio) / 2; - docsLeft /= zoomRatio; - sceneWidth = (engineWidth - pageMarginLeft * 2) / zoomRatio; - - scrollToX = 0; - } else { - docsLeft = pageMarginLeft; - sceneWidth = docsWidth + pageMarginLeft * 2; - - scrollToX = (sceneWidth - engineWidth / zoomRatio) / 2; - } - - if (engineHeight > docsHeight) { - docsTop = engineHeight / 2 - docsHeight / 2; - sceneHeight = (engineHeight - pageMarginTop * 2) / zoomRatio; - } else { - docsTop = pageMarginTop; - sceneHeight = docsHeight + pageMarginTop * 2; - } - - // this.docsLeft = docsLeft; - - // this.docsTop = docsTop; - - scene.resize(sceneWidth, sceneHeight + 200); - - docsComponent.translate(docsLeft, docsTop); - docBackground.translate(docsLeft, docsTop); - - const viewport = scene.getViewport(VIEWPORT_KEY.VIEW_MAIN); - if (scrollToX !== Number.POSITIVE_INFINITY && viewport != null) { - const actualX = viewport.transViewportScroll2ScrollValue(scrollToX, 0).x; - viewport.scrollTo({ - x: actualX, - }); - } - - return this; - } - - private _getDocObject() { - return getDocObject(this._univerInstanceService, this._renderManagerService); - } -} diff --git a/packages/docs-ui/src/docs-ui-plugin.ts b/packages/docs-ui/src/docs-ui-plugin.ts index 2f57ddf3bd..8c6fef3531 100644 --- a/packages/docs-ui/src/docs-ui-plugin.ts +++ b/packages/docs-ui/src/docs-ui-plugin.ts @@ -17,7 +17,6 @@ import { ILogService, IUniverInstanceService, - LocaleService, Plugin, Tools, UniverInstanceType, @@ -27,6 +26,8 @@ import { Inject, Injector } from '@wendellhu/redi'; import { IEditorService, IShortcutService } from '@univerjs/ui'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { DocSkeletonManagerService } from '@univerjs/docs'; import { MoveCursorDownShortcut, MoveCursorLeftShortcut, @@ -48,13 +49,13 @@ import { BreakLineShortcut, DeleteLeftShortcut, DeleteRightShortcut } from './sh import { DocClipboardService, IDocClipboardService } from './services/clipboard/clipboard.service'; import { DocClipboardController } from './controllers/clipboard.controller'; import { DocEditorBridgeController } from './controllers/doc-editor-bridge.controller'; -import { DocRenderController } from './controllers/doc-render.controller'; -import { DocCanvasView } from './views/doc-canvas-view'; -import { DocFloatingObjectController } from './controllers/doc-floating-object.controller'; -import { ZoomController } from './controllers/zoom.controller'; -import { TextSelectionController } from './controllers/text-selection.controller'; -import { BackScrollController } from './controllers/back-scroll.controller'; +import { DocRenderController } from './controllers/render-controllers/doc.render-controller'; +import { DocFloatingObjectRenderController } from './controllers/render-controllers/doc-floating-object.render-controller'; +import { DocZoomRenderController } from './controllers/render-controllers/zoom.render-controller'; +import { DocTextSelectionRenderController } from './controllers/render-controllers/text-selection.render-controller'; +import { DocBackScrollRenderController } from './controllers/render-controllers/back-scroll.render-controller'; import { DocCanvasPopManagerService } from './services/doc-popup-manager.service'; +import { DocsRenderService } from './services/docs-render.service'; export class UniverDocsUIPlugin extends Plugin { static override pluginName = DOC_UI_PLUGIN_NAME; @@ -63,7 +64,7 @@ export class UniverDocsUIPlugin extends Plugin { constructor( private readonly _config: IUniverDocsUIConfig, @Inject(Injector) override _injector: Injector, - @Inject(LocaleService) private readonly _localeService: LocaleService, + @IRenderManagerService private readonly _renderManagerSrv: IRenderManagerService, @ILogService private _logService: ILogService ) { super(); @@ -73,8 +74,13 @@ export class UniverDocsUIPlugin extends Plugin { this._initializeCommands(); } + override onReady(): void { + this._initRenderBasics(); + } + override onRendered(): void { - this._initModules(); + this._initUI(); + this._initRenderModules(); this._markDocAsFocused(); } @@ -99,49 +105,23 @@ export class UniverDocsUIPlugin extends Plugin { private _initDependencies(injector: Injector) { const dependencies: Dependency[] = [ - // Controller - [ - DocUIController, - { - useFactory: () => this._injector.createInstance(DocUIController, this._config), - }, - ], + [DocUIController, { useFactory: () => this._injector.createInstance(DocUIController, this._config) }], [DocClipboardController], [DocEditorBridgeController], - [DocRenderController], - [DocFloatingObjectController], - [ZoomController], - [TextSelectionController], - [BackScrollController], - [ - // controllers - AppUIController, - { - useFactory: () => this._injector.createInstance(AppUIController, this._config), - }, - ], - [ - IDocClipboardService, - { - useClass: DocClipboardService, - }, - ], + [DocsRenderService], + [AppUIController, { useFactory: () => this._injector.createInstance(AppUIController, this._config) }], + [IDocClipboardService, { useClass: DocClipboardService }], [DocCanvasPopManagerService], - - // Render views - [DocCanvasView], ]; - dependencies.forEach((d) => { - injector.add(d); - }); + dependencies.forEach((d) => injector.add(d)); } private _markDocAsFocused() { const currentService = this._injector.get(IUniverInstanceService); const editorService = this._injector.get(IEditorService); try { - const doc = currentService.getCurrentUniverDocInstance(); + const doc = currentService.getCurrentUnitForType(UniverInstanceType.UNIVER_DOC); if (!doc) return; const id = doc.getUnitId(); @@ -153,7 +133,27 @@ export class UniverDocsUIPlugin extends Plugin { } } - private _initModules(): void { + private _initUI(): void { this._injector.get(AppUIController); } + + private _initRenderBasics(): void { + ([ + DocSkeletonManagerService, + DocRenderController, + DocZoomRenderController, + ]).forEach((m) => { + this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_DOC, m); + }); + } + + private _initRenderModules(): void { + ([ + DocBackScrollRenderController, + DocFloatingObjectRenderController, + DocTextSelectionRenderController, + ]).forEach((m) => { + this._renderManagerSrv.registerRenderModule(UniverInstanceType.UNIVER_DOC, m); + }); + } } diff --git a/packages/docs-ui/src/index.ts b/packages/docs-ui/src/index.ts index 212d79eed9..1faa26530e 100644 --- a/packages/docs-ui/src/index.ts +++ b/packages/docs-ui/src/index.ts @@ -16,7 +16,7 @@ export * from './basics'; export * from './docs-ui-plugin'; -export { DocCanvasView } from './views/doc-canvas-view'; +export { DocRenderController } from './controllers/render-controllers/doc.render-controller'; export * from './services'; export { DocCanvasPopManagerService } from './services/doc-popup-manager.service'; export { docDrawingPositionToTransform, transformToDocDrawingPosition } from './basics/transform-position'; diff --git a/packages/docs-ui/src/services/docs-render.service.ts b/packages/docs-ui/src/services/docs-render.service.ts new file mode 100644 index 0000000000..7d932f67fc --- /dev/null +++ b/packages/docs-ui/src/services/docs-render.service.ts @@ -0,0 +1,68 @@ +/** + * Copyright 2023-present DreamNum Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { DocumentDataModel } from '@univerjs/core'; +import { IUniverInstanceService, LifecycleStages, OnLifecycle, RxDisposable, UniverInstanceType } from '@univerjs/core'; +import { IRenderManagerService } from '@univerjs/engine-render'; +import { takeUntil } from 'rxjs'; + +@OnLifecycle(LifecycleStages.Ready, DocsRenderService) +export class DocsRenderService extends RxDisposable { + constructor( + @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, + @IRenderManagerService private readonly _renderManagerService: IRenderManagerService + ) { + super(); + + this._init(); + } + + private _init() { + this._renderManagerService.createRender$ + .pipe(takeUntil(this.dispose$)) + .subscribe((unitId) => this._createRenderWithId(unitId)); + + this._instanceSrv.getTypeOfUnitAdded$(UniverInstanceType.UNIVER_DOC) + .pipe(takeUntil(this.dispose$)) + .subscribe((doc) => this._createRenderer(doc)); + this._instanceSrv.getAllUnitsForType(UniverInstanceType.UNIVER_DOC) + .forEach((documentModel) => this._createRenderer(documentModel)); + + this._instanceSrv.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_DOC) + .pipe(takeUntil(this.dispose$)) + .subscribe((doc) => this._disposeRenderer(doc)); + } + + private _createRenderer(doc: DocumentDataModel) { + const unitId = doc.getUnitId(); + + if (!this._renderManagerService.has(unitId)) { + this._createRenderWithId(unitId); + + // NOTE@wzhudev: maybe not in univer mode + this._renderManagerService.setCurrent(unitId); + } + } + + private _createRenderWithId(unitId: string) { + this._renderManagerService.createRender(unitId); + } + + private _disposeRenderer(doc: DocumentDataModel) { + const unitId = doc.getUnitId(); + this._renderManagerService.removeRender(unitId); + } +} diff --git a/packages/docs-ui/src/views/doc-canvas-view.ts b/packages/docs-ui/src/views/doc-canvas-view.ts deleted file mode 100644 index d39f3db66f..0000000000 --- a/packages/docs-ui/src/views/doc-canvas-view.ts +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { DocumentDataModel, EventState, Nullable } from '@univerjs/core'; -import { IConfigService, - IUniverInstanceService, - LifecycleStages, - OnLifecycle, - RxDisposable, - UniverInstanceType, -} from '@univerjs/core'; -import { DOCS_COMPONENT_BACKGROUND_LAYER_INDEX, DOCS_COMPONENT_DEFAULT_Z_INDEX, DOCS_COMPONENT_HEADER_LAYER_INDEX, DOCS_COMPONENT_MAIN_LAYER_INDEX, DOCS_VIEW_KEY, VIEWPORT_KEY } from '@univerjs/docs'; -import type { IRender, IWheelEvent, Scene } from '@univerjs/engine-render'; -import { DocBackground, Documents, EVENT_TYPE, IRenderManagerService, Layer, ScrollBar, Viewport } from '@univerjs/engine-render'; -import { IEditorService } from '@univerjs/ui'; -import { BehaviorSubject, takeUntil } from 'rxjs'; - -@OnLifecycle(LifecycleStages.Starting, DocCanvasView) -export class DocCanvasView extends RxDisposable { - private _scene!: Scene; - - private _currentDocumentModel!: DocumentDataModel; - - private readonly _fps$ = new BehaviorSubject(''); - - readonly fps$ = this._fps$.asObservable(); - - constructor( - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @IConfigService private readonly _configService: IConfigService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @IEditorService private readonly _editorService: IEditorService - ) { - super(); - this._initialize(); - } - - private _initialize() { - this._renderManagerService.createRender$.pipe(takeUntil(this.dispose$)).subscribe((unitId) => { - this._create(unitId); - }); - - this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_DOC).pipe(takeUntil(this.dispose$)).subscribe((documentModel) => { - this._create(documentModel?.getUnitId()); - }); - - this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_DOC).forEach((documentModel) => { - this._create(documentModel.getUnitId()); - }); - } - - override dispose(): void { - this._fps$.complete(); - } - - private _create(unitId: Nullable) { - if (unitId == null) { - return; - } - - const model = this._univerInstanceService.getUniverDocInstance(unitId); - - if (model == null) { - return; - } - - this._currentDocumentModel = model; - - if (!this._renderManagerService.has(unitId)) { - this._addNewRender(); - } - } - - private _addNewRender() { - const documentModel = this._currentDocumentModel; - - const unitId = documentModel.getUnitId(); - this._renderManagerService.createRender(unitId); - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - - const { scene, engine } = currentRender; - - this._scene = scene; - - const viewMain = new Viewport(VIEWPORT_KEY.VIEW_MAIN, scene, { - left: 0, - top: 0, - bottom: 0, - right: 0, - isRelativeX: true, - isRelativeY: true, - isWheelPreventDefaultX: true, - }); - - scene.attachControl(); - - scene.on(EVENT_TYPE.wheel, (evt: unknown, state: EventState) => { - const e = evt as IWheelEvent; - - if (e.ctrlKey) { - const deltaFactor = Math.abs(e.deltaX); - let scrollNum = deltaFactor < 40 ? 0.2 : deltaFactor < 80 ? 0.4 : 0.2; - scrollNum *= e.deltaY > 0 ? -1 : 1; - if (scene.scaleX < 1) { - scrollNum /= 2; - } - - if (scene.scaleX + scrollNum > 4) { - scene.scale(4, 4); - } else if (scene.scaleX + scrollNum < 0.1) { - scene.scale(0.1, 0.1); - } else { - // const value = e.deltaY > 0 ? 0.1 : -0.1; - // scene.scaleBy(scrollNum, scrollNum); - e.preventDefault(); - } - } else { - viewMain.onMouseWheel(e, state); - } - }); - - const hasScroll = this._configService.getConfig('hasScroll') as Nullable; - - if (hasScroll !== false) { - new ScrollBar(viewMain); - } - - scene.addLayer( - new Layer(scene, [], DOCS_COMPONENT_MAIN_LAYER_INDEX), - new Layer(scene, [], DOCS_COMPONENT_HEADER_LAYER_INDEX) - ); - - // this._viewLoader(scene); - - this._addComponent(currentRender); - - const should = this._currentDocumentModel.getShouldRenderLoopImmediately(); - - if (should) { - engine.runRenderLoop(() => { - scene.render(); - this._fps$.next(Math.round(engine.getFps()).toString()); - }); - } - - this._renderManagerService.setCurrent(unitId); - } - - private _addComponent(currentRender: IRender) { - const scene = this._scene; - const documentModel = this._currentDocumentModel; - const config = { - pageMarginLeft: documentModel.documentStyle.marginLeft || 0, - pageMarginTop: documentModel.documentStyle.marginTop || 0, - }; - const documents = new Documents(DOCS_VIEW_KEY.MAIN, undefined, config); - - const docBackground = new DocBackground(DOCS_VIEW_KEY.BACKGROUND, undefined, config); - - documents.zIndex = DOCS_COMPONENT_DEFAULT_Z_INDEX; - - docBackground.zIndex = DOCS_COMPONENT_DEFAULT_Z_INDEX; - - currentRender.mainComponent = documents; - currentRender.components.set(DOCS_VIEW_KEY.MAIN, documents); - currentRender.components.set(DOCS_VIEW_KEY.BACKGROUND, docBackground); - - scene.addObjects([documents], DOCS_COMPONENT_MAIN_LAYER_INDEX); - scene.addObjects([docBackground], DOCS_COMPONENT_BACKGROUND_LAYER_INDEX); - - if (this._editorService.getEditor(documentModel.getUnitId()) == null) { - scene.enableLayerCache(DOCS_COMPONENT_MAIN_LAYER_INDEX); - } - } -} diff --git a/packages/docs/src/basics/component-tools.ts b/packages/docs/src/basics/component-tools.ts index 9bba5df21f..b565061e60 100644 --- a/packages/docs/src/basics/component-tools.ts +++ b/packages/docs/src/basics/component-tools.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { type IUniverInstanceService, type Nullable, UniverInstanceType } from '@univerjs/core'; -import type { DocBackground, Documents, Engine, IRenderManagerService, Scene } from '@univerjs/engine-render'; +import type { DocumentDataModel, IUniverInstanceService, Nullable } from '@univerjs/core'; +import { UniverInstanceType } from '@univerjs/core'; +import type { DocBackground, Documents, Engine, IRenderContext, IRenderManagerService, Scene } from '@univerjs/engine-render'; import { DOCS_VIEW_KEY } from './docs-view-key'; export interface IDocObjectParam { @@ -25,6 +26,20 @@ export interface IDocObjectParam { engine: Engine; } +export function neoGetDocObject(renderContext: IRenderContext) { + const { mainComponent, scene, engine, components } = renderContext; + const document = mainComponent as Documents; + const docBackground = components.get(DOCS_VIEW_KEY.BACKGROUND) as DocBackground; + + return { + document, + docBackground, + scene, + engine, + }; +} + +/** @deprecated After migrating to `RenderUnit`, use `neoGetDocObject` instead. */ export function getDocObject( univerInstanceService: IUniverInstanceService, renderManagerService: IRenderManagerService diff --git a/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts b/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts index 04cbe6573b..0cae288102 100644 --- a/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts +++ b/packages/docs/src/commands/commands/__tests__/create-command-test-bed.ts @@ -14,22 +14,30 @@ * limitations under the License. */ -import type { IDocumentData } from '@univerjs/core'; +/* eslint-disable ts/no-explicit-any */ + +import type { DocumentDataModel, IDocumentData, Nullable } from '@univerjs/core'; import { BooleanNumber, + DOCS_NORMAL_EDITOR_UNIT_ID_KEY, ILogService, IUniverInstanceService, LogLevel, Plugin, + RxDisposable, Univer, + UniverInstanceType, } from '@univerjs/core'; -import type { Dependency } from '@wendellhu/redi'; +import type { Ctor, Dependency, DependencyIdentifier } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; +import type { DocumentSkeleton, IRender, IRenderContext, IRenderModule } from '@univerjs/engine-render'; +import { DocumentViewModel, IRenderManagerService } from '@univerjs/engine-render'; -import { DocViewModelManagerService } from '../../../services/doc-view-model-manager.service'; +import { BehaviorSubject, takeUntil } from 'rxjs'; import { TextSelectionManagerService } from '../../../services/text-selection-manager.service'; import { DocStateChangeManagerService } from '../../../services/doc-state-change-manager.service'; import { IMEInputManagerService } from '../../../services/ime-input-manager.service'; +import { DocSkeletonManagerService } from '../../../services/doc-skeleton-manager.service'; import { ITextSelectionRenderManager, TextSelectionRenderManager } from './mock-text-selection-render-manager'; const TEST_DOCUMENT_DATA_EN: IDocumentData = { @@ -92,27 +100,21 @@ export function createCommandTestBed(workbookData?: IDocumentData, dependencies? const injector = univer.__getInjector(); const get = injector.get.bind(injector); - /** - * This plugin hooks into Doc's DI system to expose API to test scripts - */ class TestPlugin extends Plugin { static override pluginName = 'test-plugin'; - constructor(_config: undefined, @Inject(Injector) override readonly _injector: Injector) { + constructor( + _config: undefined, + @Inject(Injector) override readonly _injector: Injector + ) { super(); } override onStarting(injector: Injector): void { injector.add([TextSelectionManagerService]); - injector.add([DocViewModelManagerService]); injector.add([DocStateChangeManagerService]); injector.add([IMEInputManagerService]); - injector.add([ - ITextSelectionRenderManager, - { - useClass: TextSelectionRenderManager, - }, - ]); + injector.add([ITextSelectionRenderManager, { useClass: TextSelectionRenderManager }]); dependencies?.forEach((d) => injector.add(d)); } @@ -121,11 +123,30 @@ export function createCommandTestBed(workbookData?: IDocumentData, dependencies? univer.registerPlugin(TestPlugin); const doc = univer.createUniverDoc(workbookData || TEST_DOCUMENT_DATA_EN); + const univerInstanceService = get(IUniverInstanceService); + + // NOTE: This is pretty hack for the test. But with these hacks we can avoid to create + // real canvas-environment in univerjs/docs. If some we have to do that, this hack could be removed. + // Refer to packages/sheets-ui/src/services/clipboard/__tests__/clipboard-test-bed.ts + const fakeDocSkeletonManager = new MockDocSkeletonManagerService({ + unit: doc, + unitId: 'test-doc', + type: UniverInstanceType.UNIVER_DOC, + engine: null as any, + scene: null as any, + mainComponent: null as any, + components: null as any, + isMainScene: true, + }, univerInstanceService); + + injector.add([DocSkeletonManagerService, { useValue: fakeDocSkeletonManager as unknown as DocSkeletonManagerService }]); + injector.add([IRenderManagerService, { useClass: MockRenderManagerService as unknown as Ctor }]); + univerInstanceService.focusUnit('test-doc'); const logService = get(ILogService); - logService.setLogLevel(LogLevel.SILENT); // change this to `LogLevel.VERBOSE` to debug tests via logs + logService.setLogLevel(LogLevel.SILENT); return { univer, @@ -133,3 +154,81 @@ export function createCommandTestBed(workbookData?: IDocumentData, dependencies? doc, }; } + +// These services are for document build and manage doc skeletons. + +export class MockRenderManagerService implements Pick { + constructor( + @Inject(Injector) private readonly _injector: Injector + ) {} + + getRenderById(_unitId: string): Nullable { + return { + with: (identifier: DependencyIdentifier) => this._injector.get(identifier), + } as unknown as IRender; + } +} + +export class MockDocSkeletonManagerService extends RxDisposable implements IRenderModule { + private _docViewModel: DocumentViewModel; + + private readonly _currentSkeleton$ = new BehaviorSubject>(null); + readonly currentSkeleton$ = this._currentSkeleton$.asObservable(); + + // CurrentSkeletonBefore for pre-triggered logic during registration + private readonly _currentSkeletonBefore$ = new BehaviorSubject>(null); + readonly currentSkeletonBefore$ = this._currentSkeletonBefore$.asObservable(); + + constructor( + private readonly _context: IRenderContext, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService + ) { + super(); + + this._update(); + + this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_DOC) + .pipe(takeUntil(this.dispose$)) + .subscribe((documentModel) => { + if (documentModel?.getUnitId() === this._context.unitId) { + this._update(); + } + }); + } + + override dispose(): void { + super.dispose(); + + this._currentSkeletonBefore$.complete(); + this._currentSkeleton$.complete(); + } + + private _update() { + const documentDataModel = this._context.unit; + const unitId = this._context.unitId; + + // No need to build view model, if data model has no body. + if (documentDataModel.getBody() == null) { + return; + } + + // Always need to reset document data model, because cell editor change doc instance every time. + if (this._docViewModel && unitId === DOCS_NORMAL_EDITOR_UNIT_ID_KEY) { + this._docViewModel.reset(documentDataModel); + } else if (!this._docViewModel) { + this._docViewModel = this._buildDocViewModel(documentDataModel); + } + } + + getSkeleton(): DocumentSkeleton { + throw new Error('[MockDocSkeletonManagerService]: cannot access to doc skeleton in unit tests!'); + } + + getViewModel(): DocumentViewModel { + return this._docViewModel; + } + + private _buildDocViewModel(documentDataModel: DocumentDataModel) { + return new DocumentViewModel(documentDataModel); + } +} diff --git a/packages/docs/src/commands/commands/__tests__/replace-content.command.spec.ts b/packages/docs/src/commands/commands/__tests__/replace-content.command.spec.ts index e6762be8f5..bdb1ddaefd 100644 --- a/packages/docs/src/commands/commands/__tests__/replace-content.command.spec.ts +++ b/packages/docs/src/commands/commands/__tests__/replace-content.command.spec.ts @@ -99,7 +99,9 @@ describe('replace or cover content of document', () => { ]); }); - afterEach(() => univer.dispose()); + afterEach(() => { + univer.dispose(); + }); describe('replace content of document and reserve undo and redo stack', () => { it('Should pass the test case when replace content', async () => { @@ -155,3 +157,4 @@ describe('replace or cover content of document', () => { }); }); }); + diff --git a/packages/docs/src/commands/commands/delete.command.ts b/packages/docs/src/commands/commands/delete.command.ts index f7093a7027..fce46dda91 100644 --- a/packages/docs/src/commands/commands/delete.command.ts +++ b/packages/docs/src/commands/commands/delete.command.ts @@ -26,39 +26,37 @@ import { } from '@univerjs/core'; import { getParagraphByGlyph, hasListGlyph, type IActiveTextRange, isFirstGlyph, isIndentByGlyph, type ITextRangeWithStyle, type TextRange } from '@univerjs/engine-render'; -import { DocSkeletonManagerService } from '../../services/doc-skeleton-manager.service'; import type { ITextActiveRange } from '../../services/text-selection-manager.service'; import { TextSelectionManagerService } from '../../services/text-selection-manager.service'; import type { IRichTextEditingMutationParams } from '../mutations/core-editing.mutation'; import { RichTextEditingMutation } from '../mutations/core-editing.mutation'; +import { getCommandSkeleton } from '../util'; import { CutContentCommand } from './clipboard.inner.command'; import { DeleteCommand, DeleteDirection, UpdateCommand } from './core-editing.command'; // Handle BACKSPACE key. export const DeleteLeftCommand: ICommand = { id: 'doc.command.delete-left', - type: CommandType.COMMAND, - // eslint-disable-next-line max-lines-per-function handler: async (accessor) => { const textSelectionManagerService = accessor.get(TextSelectionManagerService); - const docSkeletonManagerService = accessor.get(DocSkeletonManagerService); const univerInstanceService = accessor.get(IUniverInstanceService); const commandService = accessor.get(ICommandService); - const activeRange = textSelectionManagerService.getActiveRange(); - const ranges = textSelectionManagerService.getSelections(); - const skeleton = docSkeletonManagerService.getCurrent()?.skeleton; - let result = true; - if (activeRange == null || skeleton == null || ranges == null) { + const docDataModel = univerInstanceService.getCurrentUniverDocInstance(); + if (!docDataModel) { return false; } - const docDataModel = univerInstanceService.getCurrentUniverDocInstance(); - if (!docDataModel) { + const unitId = docDataModel.getUnitId(); + const docSkeletonManagerService = getCommandSkeleton(accessor, unitId); + const activeRange = textSelectionManagerService.getActiveRange(); + const ranges = textSelectionManagerService.getSelections(); + const skeleton = docSkeletonManagerService?.getSkeleton(); + if (activeRange == null || skeleton == null || ranges == null) { return false; } @@ -190,31 +188,25 @@ export const DeleteLeftCommand: ICommand = { // handle Delete key export const DeleteRightCommand: ICommand = { id: 'doc.command.delete-right', - type: CommandType.COMMAND, - handler: async (accessor) => { const textSelectionManagerService = accessor.get(TextSelectionManagerService); - const docSkeletonManagerService = accessor.get(DocSkeletonManagerService); const univerInstanceService = accessor.get(IUniverInstanceService); + const docDataModel = univerInstanceService.getCurrentUniverDocInstance(); + if (!docDataModel) { + return false; + } + + const docSkeletonManagerService = getCommandSkeleton(accessor, docDataModel.getUnitId()); const commandService = accessor.get(ICommandService); const activeRange = textSelectionManagerService.getActiveRange(); const ranges = textSelectionManagerService.getSelections(); - - const skeleton = docSkeletonManagerService.getCurrent()?.skeleton; - - let result; - + const skeleton = docSkeletonManagerService?.getSkeleton(); if (activeRange == null || skeleton == null || ranges == null) { return false; } - const docDataModel = univerInstanceService.getCurrentUniverDocInstance(); - if (!docDataModel) { - return false; - } - const { startOffset, collapsed, segmentId, style } = activeRange; // No need to delete when the cursor is at the last position of the last paragraph. @@ -222,6 +214,7 @@ export const DeleteRightCommand: ICommand = { return true; } + let result: boolean = false; if (collapsed === true) { const needDeleteSpan = skeleton.findNodeByCharIndex(startOffset)!; diff --git a/packages/docs/src/commands/mutations/core-editing.mutation.ts b/packages/docs/src/commands/mutations/core-editing.mutation.ts index 0a7ae90dc1..87f61db85e 100644 --- a/packages/docs/src/commands/mutations/core-editing.mutation.ts +++ b/packages/docs/src/commands/mutations/core-editing.mutation.ts @@ -16,12 +16,12 @@ import { CommandType, IUniverInstanceService, JSONX } from '@univerjs/core'; import type { IMutation, IMutationCommonParams, JSONXActions, Nullable } from '@univerjs/core'; -import type { ITextRangeWithStyle } from '@univerjs/engine-render'; -import { DocViewModelManagerService } from '../../services/doc-view-model-manager.service'; +import { IRenderManagerService, type ITextRangeWithStyle } from '@univerjs/engine-render'; import { serializeTextRange, TextSelectionManagerService } from '../../services/text-selection-manager.service'; import type { IDocStateChangeParams } from '../../services/doc-state-change-manager.service'; import { DocStateChangeManagerService } from '../../services/doc-state-change-manager.service'; import { IMEInputManagerService } from '../../services/ime-input-manager.service'; +import { DocSkeletonManagerService } from '../../services/doc-skeleton-manager.service'; export interface IRichTextEditingMutationParams extends IMutationCommonParams { unitId: string; @@ -62,10 +62,13 @@ export const RichTextEditingMutation: IMutation; constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @IRenderManagerService private readonly _renderManagerSrv: IRenderManagerService, @ITextSelectionRenderManager private readonly _textSelectionRenderManager: ITextSelectionRenderManager, @Inject(IMEInputManagerService) private readonly _imeInputManagerService: IMEInputManagerService, @ICommandService private readonly _commandService: ICommandService @@ -101,9 +101,7 @@ export class IMEInputController extends Disposable { } private async _updateContent(config: Nullable, isUpdate: boolean) { - const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; - - if (config == null || skeleton == null) { + if (config == null) { return; } @@ -112,6 +110,9 @@ export class IMEInputController extends Disposable { return; } + const skeleton = this._renderManagerSrv.getRenderById(documentModel.getUnitId()) + ?.with(DocSkeletonManagerService).getSkeleton(); + const { event, activeRange } = config; if (skeleton == null || activeRange == null) { diff --git a/packages/docs/src/controllers/move-cursor.controller.ts b/packages/docs/src/controllers/move-cursor.controller.ts index 6fa5bdc6ec..0ad442bf90 100644 --- a/packages/docs/src/controllers/move-cursor.controller.ts +++ b/packages/docs/src/controllers/move-cursor.controller.ts @@ -45,7 +45,6 @@ export class MoveCursorController extends Disposable { private _onInputSubscription: Nullable; constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, @@ -99,8 +98,8 @@ export class MoveCursorController extends Disposable { return; } - const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; - + const skeleton = this._renderManagerService.getRenderById(docDataModel.getUnitId()) + ?.with(DocSkeletonManagerService).getSkeleton(); const docObject = this._getDocObject(); if (activeRange == null || skeleton == null || docObject == null) { @@ -203,8 +202,8 @@ export class MoveCursorController extends Disposable { return false; } - const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; - + const skeleton = this._renderManagerService.getRenderById(docDataModel.getUnitId()) + ?.with(DocSkeletonManagerService).getSkeleton(); const docObject = this._getDocObject(); if (activeRange == null || skeleton == null || docObject == null || allRanges == null) { diff --git a/packages/docs/src/controllers/normal-input.controller.ts b/packages/docs/src/controllers/normal-input.controller.ts index 51ad4d939f..5397a01ed0 100644 --- a/packages/docs/src/controllers/normal-input.controller.ts +++ b/packages/docs/src/controllers/normal-input.controller.ts @@ -17,7 +17,6 @@ import type { Nullable } from '@univerjs/core'; import { Disposable, ICommandService, IUniverInstanceService, LifecycleStages, OnLifecycle } from '@univerjs/core'; import { IRenderManagerService, ITextSelectionRenderManager } from '@univerjs/engine-render'; -import { Inject } from '@wendellhu/redi'; import type { Subscription } from 'rxjs'; import { InsertCommand } from '../commands/commands/core-editing.command'; @@ -28,7 +27,6 @@ export class NormalInputController extends Disposable { private _onInputSubscription: Nullable; constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @ITextSelectionRenderManager private readonly _textSelectionRenderManager: ITextSelectionRenderManager, @@ -66,7 +64,8 @@ export class NormalInputController extends Disposable { const e = event as InputEvent; - const skeleton = this._docSkeletonManagerService.getCurrent()?.skeleton; + const skeleton = this._renderManagerService.getRenderById(documentModel.getUnitId()) + ?.with(DocSkeletonManagerService).getSkeleton(); if (e.data == null || skeleton == null) { return; diff --git a/packages/docs/src/doc-plugin.ts b/packages/docs/src/doc-plugin.ts index 63007198f3..cdda5d0e19 100644 --- a/packages/docs/src/doc-plugin.ts +++ b/packages/docs/src/doc-plugin.ts @@ -52,8 +52,6 @@ import { SetTextSelectionsOperation } from './commands/operations/text-selection import { IMEInputController } from './controllers/ime-input.controller'; import { MoveCursorController } from './controllers/move-cursor.controller'; import { NormalInputController } from './controllers/normal-input.controller'; -import { DocSkeletonManagerService } from './services/doc-skeleton-manager.service'; -import { DocViewModelManagerService } from './services/doc-view-model-manager.service'; import { IMEInputManagerService } from './services/ime-input-manager.service'; import { TextSelectionManagerService } from './services/text-selection-manager.service'; import { DocStateChangeManagerService } from './services/doc-state-change-manager.service'; @@ -130,8 +128,6 @@ export class UniverDocsPlugin extends Plugin { ( [ // services - [DocSkeletonManagerService], - [DocViewModelManagerService], [DocStateChangeManagerService], [IMEInputManagerService], [ diff --git a/packages/docs/src/index.ts b/packages/docs/src/index.ts index b11c2329aa..9f5c2a5a90 100644 --- a/packages/docs/src/index.ts +++ b/packages/docs/src/index.ts @@ -15,7 +15,7 @@ */ export type { IDocObjectParam } from './basics/component-tools'; -export { getDocObject, getDocObjectById } from './basics/component-tools'; +export { getDocObject, neoGetDocObject, getDocObjectById } from './basics/component-tools'; export * from './basics/docs-view-key'; export { BreakLineCommand } from './commands/commands/break-line.command'; export { @@ -63,8 +63,7 @@ export { SetTextSelectionsOperation, } from './commands/operations/text-selection.operation'; export { type IUniverDocsConfig, UniverDocsPlugin } from './doc-plugin'; -export { DocSkeletonManagerService, type IDocSkeletonManagerParam } from './services/doc-skeleton-manager.service'; -export { DocViewModelManagerService } from './services/doc-view-model-manager.service'; +export { DocSkeletonManagerService } from './services/doc-skeleton-manager.service'; export { TextSelectionManagerService, serializeTextRange } from './services/text-selection-manager.service'; export { DocStateChangeManagerService, type IDocStateChangeParams } from './services/doc-state-change-manager.service'; export { IMEInputManagerService } from './services/ime-input-manager.service'; diff --git a/packages/docs/src/services/doc-skeleton-manager.service.ts b/packages/docs/src/services/doc-skeleton-manager.service.ts index 0b2ecef98e..951e8026dd 100644 --- a/packages/docs/src/services/doc-skeleton-manager.service.ts +++ b/packages/docs/src/services/doc-skeleton-manager.service.ts @@ -14,133 +14,99 @@ * limitations under the License. */ -import type { Nullable } from '@univerjs/core'; -import { IUniverInstanceService, LocaleService, RxDisposable, UniverInstanceType } from '@univerjs/core'; -import type { DocumentViewModel } from '@univerjs/engine-render'; -import { DocumentSkeleton } from '@univerjs/engine-render'; +import type { DocumentDataModel, Nullable } from '@univerjs/core'; +import { DOCS_NORMAL_EDITOR_UNIT_ID_KEY, IUniverInstanceService, LocaleService, RxDisposable, UniverInstanceType } from '@univerjs/core'; +import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; +import { DocumentSkeleton, DocumentViewModel } from '@univerjs/engine-render'; import { Inject } from '@wendellhu/redi'; import { BehaviorSubject, takeUntil } from 'rxjs'; -import type { IDocumentViewModelManagerParam } from './doc-view-model-manager.service'; -import { DocViewModelManagerService } from './doc-view-model-manager.service'; - -export interface IDocSkeletonManagerParam { - unitId: string; - skeleton: DocumentSkeleton; - dirty: boolean; -} - /** - * This service is for document build and manage doc skeletons. + * This service is for document build and manage doc skeletons. It also manages + * DocumentViewModels. */ -export class DocSkeletonManagerService extends RxDisposable { - private _currentSkeletonUnitId: string = ''; +export class DocSkeletonManagerService extends RxDisposable implements IRenderModule { + private _skeleton: DocumentSkeleton; + private _docViewModel: DocumentViewModel; - private _docSkeletonMap: Map = new Map(); - - private readonly _currentSkeleton$ = new BehaviorSubject>(null); + private readonly _currentSkeleton$ = new BehaviorSubject>(null); readonly currentSkeleton$ = this._currentSkeleton$.asObservable(); // CurrentSkeletonBefore for pre-triggered logic during registration - private readonly _currentSkeletonBefore$ = new BehaviorSubject>(null); - + private readonly _currentSkeletonBefore$ = new BehaviorSubject>(null); readonly currentSkeletonBefore$ = this._currentSkeletonBefore$.asObservable(); constructor( + private readonly _context: IRenderContext, @Inject(LocaleService) private readonly _localeService: LocaleService, - @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService ) { super(); - this._initialize(); - } - private _initialize() { this._init(); - } - override dispose(): void { - this._currentSkeletonBefore$.complete(); - this._currentSkeleton$.complete(); - this._docSkeletonMap.clear(); - } - - private _init() { - this._docViewModelManagerService.currentDocViewModel$ + this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_DOC) .pipe(takeUntil(this.dispose$)) - .subscribe((docViewModel) => { - if (docViewModel == null) { - return; + .subscribe((documentModel) => { + if (documentModel?.getUnitId() === this._context.unitId) { + this._update(documentModel); } - - this._setCurrent(docViewModel); }); + } - this._docViewModelManagerService.getAllModel().forEach((docViewModel) => { - if (docViewModel == null) { - return; - } - - this._setCurrent(docViewModel); - }); - - this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_DOC).pipe(takeUntil(this.dispose$)).subscribe((documentModel) => { - this._docSkeletonMap.delete(documentModel.getUnitId()); + override dispose(): void { + super.dispose(); - this._currentSkeletonUnitId = this._univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_DOC)?.getUnitId() ?? ''; - }); + this._currentSkeletonBefore$.complete(); + this._currentSkeleton$.complete(); } - getCurrent(): Nullable { - return this.getSkeletonByUnitId(this._currentSkeletonUnitId); + private _init() { + const documentDataModel = this._context.unit; + this._update(documentDataModel); } - getAllSkeleton(): Map { - return this._docSkeletonMap; - } + private _update(documentDataModel: DocumentDataModel) { + const unitId = this._context.unitId; - makeDirty(unitId: string, state: boolean = true) { - const param = this.getSkeletonByUnitId(unitId); - if (param == null) { + // No need to build view model, if data model has no body. + if (documentDataModel.getBody() == null) { return; } - param.dirty = state; - } - - getSkeletonByUnitId(unitId: string): Nullable { - return this._docSkeletonMap.get(unitId); - } - - private _setCurrent(docViewModelParam: IDocumentViewModelManagerParam): Nullable { - const { unitId } = docViewModelParam; - - if (!this._docSkeletonMap.has(unitId)) { - const skeleton = this._buildSkeleton(docViewModelParam.docViewModel); + // Always need to reset document data model, because cell editor change doc instance every time. + if (this._docViewModel && unitId === DOCS_NORMAL_EDITOR_UNIT_ID_KEY) { + this._docViewModel.reset(documentDataModel); - skeleton.calculate(); + this._context.unit = documentDataModel; + } else if (!this._docViewModel) { + this._docViewModel = this._buildDocViewModel(documentDataModel); + } - this._docSkeletonMap.set(unitId, { - unitId, - skeleton, - dirty: false, - }); - } else { - const skeletonParam = this.getSkeletonByUnitId(unitId)!; - skeletonParam.skeleton.calculate(); - skeletonParam.dirty = true; + if (!this._skeleton) { + this._skeleton = this._buildSkeleton(this._docViewModel); } - this._currentSkeletonUnitId = unitId; + const skeleton = this._skeleton; + skeleton.calculate(); - this._currentSkeletonBefore$.next(this.getCurrent()); + this._currentSkeletonBefore$.next(skeleton); + this._currentSkeleton$.next(skeleton); + } - this._currentSkeleton$.next(this.getCurrent()); + getSkeleton(): DocumentSkeleton { + return this._skeleton; + } - return this.getCurrent(); + getViewModel(): DocumentViewModel { + return this._docViewModel; } private _buildSkeleton(documentViewModel: DocumentViewModel) { return DocumentSkeleton.create(documentViewModel, this._localeService); } + + private _buildDocViewModel(documentDataModel: DocumentDataModel) { + return new DocumentViewModel(documentDataModel); + } } diff --git a/packages/docs/src/services/doc-view-model-manager.service.ts b/packages/docs/src/services/doc-view-model-manager.service.ts index e06b9b4578..d51799ef89 100644 --- a/packages/docs/src/services/doc-view-model-manager.service.ts +++ b/packages/docs/src/services/doc-view-model-manager.service.ts @@ -16,6 +16,7 @@ import type { DocumentDataModel, Nullable } from '@univerjs/core'; import { DOCS_NORMAL_EDITOR_UNIT_ID_KEY, IUniverInstanceService, RxDisposable, UniverInstanceType } from '@univerjs/core'; +import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; import { DocumentViewModel } from '@univerjs/engine-render'; import { BehaviorSubject, takeUntil } from 'rxjs'; @@ -24,16 +25,22 @@ export interface IDocumentViewModelManagerParam { docViewModel: DocumentViewModel; } +// TODO@wzhudev: move this manager service into render unit + /** - * The view model manager is used to manage Doc view model. has a one-to-one correspondence with the doc skeleton. + * The view model manager is used to manage Doc view model. Each view model has a one-to-one correspondence + * with the doc skeleton. */ -export class DocViewModelManagerService extends RxDisposable { +export class DocViewModelManagerService extends RxDisposable implements IRenderModule { private _docViewModelMap: Map = new Map(); private readonly _currentDocViewModel$ = new BehaviorSubject>(null); readonly currentDocViewModel$ = this._currentDocViewModel$.asObservable(); - constructor(@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService) { + constructor( + private readonly _context: IRenderContext, + @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService + ) { super(); this._initialize(); } diff --git a/packages/engine-render/src/engine.ts b/packages/engine-render/src/engine.ts index 10e1c7cfda..d4e399ee6a 100644 --- a/packages/engine-render/src/engine.ts +++ b/packages/engine-render/src/engine.ts @@ -235,6 +235,7 @@ export class Engine extends ThinEngine { override dispose() { super.dispose(); + const eventPrefix = getPointerPrefix(); const canvasEle = this.getCanvasElement(); canvasEle.removeEventListener(`${eventPrefix}leave`, this._pointerLeaveEvent); diff --git a/packages/engine-render/src/render-manager/render-manager.service.ts b/packages/engine-render/src/render-manager/render-manager.service.ts index 4949eef263..c0a8f0ce3a 100644 --- a/packages/engine-render/src/render-manager/render-manager.service.ts +++ b/packages/engine-render/src/render-manager/render-manager.service.ts @@ -19,7 +19,7 @@ import { Disposable, IUniverInstanceService, toDisposable } from '@univerjs/core import type { DependencyIdentifier, IDisposable } from '@wendellhu/redi'; import { createIdentifier, Inject, Injector } from '@wendellhu/redi'; import type { Observable } from 'rxjs'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import type { BaseObject } from '../base-object'; import type { DocComponent } from '../components/docs/doc-component'; @@ -32,8 +32,8 @@ import { type IRender, type IRenderModuleCtor, RenderUnit } from './render-unit' export type RenderComponentType = SheetComponent | DocComponent | Slide | BaseObject; export interface IRenderManagerService extends IDisposable { + /** @deprecated */ currentRender$: Observable>; - createRender$: Observable>; addRender(unitId: string, renderer: IRender): void; createRender(unitId: string): IRender; removeRender(unitId: string): void; @@ -41,7 +41,16 @@ export interface IRenderManagerService extends IDisposable { getRenderById(unitId: string): Nullable; getRenderAll(): Map; defaultEngine: Engine; - create(unitId: Nullable): void; + + // DEPT@Jocs + // Editor should not be coupled in docs-ui. It should be an common service resident in @univerjs/ui. + // However, currently the refactor is not completed so we have to throw an event and let + // docs-ui to create the editor's renderer. + + /** @deprecated */ + createRender$: Observable; + /** @deprecated this design is very very weird! Remove it. */ + create(unitId: string): void; /** @deprecated There will be multi units to render at the same time, so there is no *current*. */ getCurrent(): Nullable; @@ -49,10 +58,8 @@ export interface IRenderManagerService extends IDisposable { getFirst(): Nullable; has(unitId: string): boolean; - withCurrentTypeOfUnit(type: UniverInstanceType, id: DependencyIdentifier): Nullable; - - registerRenderController(type: UnitType, ctor: IRenderModuleCtor): IDisposable; + registerRenderModule(type: UnitType, ctor: IRenderModuleCtor): IDisposable; } const DEFAULT_SCENE_SIZE = { width: 1500, height: 1000 }; @@ -69,7 +76,8 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe private readonly _currentRender$ = new BehaviorSubject>(this._currentUnitId); readonly currentRender$ = this._currentRender$.asObservable(); - private readonly _createRender$ = new BehaviorSubject>(this._currentUnitId); + private readonly _createRender$ = new Subject(); + /** @deprecated */ readonly createRender$ = this._createRender$.asObservable(); get defaultEngine() { @@ -97,7 +105,7 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe this._currentRender$.complete(); } - registerRenderController(type: UnitType, ctor: IRenderModuleCtor): IDisposable { + registerRenderModule(type: UnitType, ctor: IRenderModuleCtor): IDisposable { if (!this._renderControllers.has(type)) { this._renderControllers.set(type, new Set()); } @@ -119,7 +127,7 @@ export class RenderManagerService extends Disposable implements IRenderManagerSe return Array.from(this._renderControllers.get(type) ?? []); } - create(unitId: Nullable) { + create(unitId: string) { this._createRender$.next(unitId); } diff --git a/packages/facade/package.json b/packages/facade/package.json index 62492aa940..8e66352eba 100644 --- a/packages/facade/package.json +++ b/packages/facade/package.json @@ -62,6 +62,7 @@ "peerDependencies": { "@univerjs/core": "workspace:*", "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", "@univerjs/network": "workspace:*", @@ -75,6 +76,7 @@ "devDependencies": { "@univerjs/core": "workspace:*", "@univerjs/docs": "workspace:*", + "@univerjs/docs-ui": "workspace:*", "@univerjs/engine-formula": "workspace:*", "@univerjs/engine-render": "workspace:*", "@univerjs/network": "workspace:*", diff --git a/packages/facade/src/apis/__tests__/create-test-bed.ts b/packages/facade/src/apis/__tests__/create-test-bed.ts index dcc133e3bd..8a48fb4388 100644 --- a/packages/facade/src/apis/__tests__/create-test-bed.ts +++ b/packages/facade/src/apis/__tests__/create-test-bed.ts @@ -156,8 +156,8 @@ export function createFacadeTestBed(workbookData?: IWorkbookData, dependencies?: injector.add([WorksheetProtectionRuleModel]); const renderManagerService = injector.get(IRenderManagerService); - renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, SheetSkeletonManagerService); - renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, SheetRenderController); + renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, SheetSkeletonManagerService); + renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, SheetRenderController); SheetsConditionalFormattingPlugin.dependencyList.forEach((d) => { injector.add(d); diff --git a/packages/facade/src/apis/docs/__tests__/create-test-bed.ts b/packages/facade/src/apis/docs/__tests__/create-test-bed.ts index b497ae8411..96ffa9ad34 100644 --- a/packages/facade/src/apis/docs/__tests__/create-test-bed.ts +++ b/packages/facade/src/apis/docs/__tests__/create-test-bed.ts @@ -22,15 +22,16 @@ import { LogLevel, Plugin, Univer, + UniverInstanceType, } from '@univerjs/core'; import enUS from '@univerjs/sheets-formula/locale/en-US'; import zhCN from '@univerjs/sheets-formula/locale/zh-CN'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; - -import { DocStateChangeManagerService, DocViewModelManagerService, IMEInputManagerService, TextSelectionManagerService } from '@univerjs/docs'; - +import { DocSkeletonManagerService, DocStateChangeManagerService, IMEInputManagerService, TextSelectionManagerService } from '@univerjs/docs'; import { IRenderManagerService, ITextSelectionRenderManager, RenderManagerService, TextSelectionRenderManager } from '@univerjs/engine-render'; + +import { DocsRenderService } from '@univerjs/docs-ui/services/docs-render.service.js'; import { FUniver } from '../../facade'; function getTestDocumentDataDemo(): IDocumentData { @@ -78,12 +79,15 @@ export function createTestBed(documentConfig?: IDocumentData, dependencies?: Dep override onStarting(injector: Injector): void { injector.add([IRenderManagerService, { useClass: RenderManagerService }]); injector.add([TextSelectionManagerService]); - injector.add([DocViewModelManagerService]); injector.add([DocStateChangeManagerService]); injector.add([IMEInputManagerService]); + injector.add([DocsRenderService]); injector.add([ITextSelectionRenderManager, { useClass: TextSelectionRenderManager }]); dependencies?.forEach((d) => injector.add(d)); + + const renderManagerService = injector.get(IRenderManagerService); + renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_DOC, DocSkeletonManagerService); } } @@ -96,7 +100,7 @@ export function createTestBed(documentConfig?: IDocumentData, dependencies?: Dep univerInstanceService.focusUnit('test'); const logService = injector.get(ILogService); - logService.setLogLevel(LogLevel.SILENT); // change this to `LogLevel.VERBOSE` to debug tests via logs + logService.setLogLevel(LogLevel.SILENT); const univerAPI = FUniver.newAPI(injector); diff --git a/packages/facade/src/apis/docs/__tests__/f-document.spec.ts b/packages/facade/src/apis/docs/__tests__/f-document.spec.ts index 5faa498f35..d95bbb1a1d 100644 --- a/packages/facade/src/apis/docs/__tests__/f-document.spec.ts +++ b/packages/facade/src/apis/docs/__tests__/f-document.spec.ts @@ -34,16 +34,14 @@ describe('Test FDocument', () => { commandService = get(ICommandService); commandService.registerCommand(InsertCommand); - commandService.registerCommand(RichTextEditingMutation as any); + commandService.registerCommand(RichTextEditingMutation); }); it('Document appendText', async () => { - const activeDoc = univerAPI.getActiveDocument(); + const activeDoc = univerAPI.getActiveDocument()!; + expect(await activeDoc.appendText('Univer')).toBeTruthy(); - expect(await activeDoc?.appendText('Univer')).toBeTruthy(); - - const dataStream = activeDoc?.getSnapshot().body?.dataStream; - - expect(dataStream?.substring(0, dataStream.length - 2)).toEqual('Hello,Univer'); + const dataStream = activeDoc.getSnapshot().body!.dataStream; + expect(dataStream.substring(0, dataStream.length - 2)).toEqual('Hello,Univer'); }); }); diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts index b0dd39b641..348c560352 100644 --- a/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts @@ -136,7 +136,7 @@ export class SheetsFilterUIController extends RxDisposable { } private _initRenderControllers(): void { - this.disposeWithMe(this._renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, SheetsFilterRenderController)); + this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, SheetsFilterRenderController)); } private _popupDisposable?: Nullable; diff --git a/packages/sheets-formula/src/controllers/formula-ui.controller.ts b/packages/sheets-formula/src/controllers/formula-ui.controller.ts index 5e9e9e17a8..1fa03f0313 100644 --- a/packages/sheets-formula/src/controllers/formula-ui.controller.ts +++ b/packages/sheets-formula/src/controllers/formula-ui.controller.ts @@ -76,7 +76,7 @@ export class FormulaUIController extends Disposable { this._registerMenus(); this._registerShortcuts(); this._registerComponents(); - this._registerRenderControllers(); + this._registerRenderModules(); } private _registerMenus(): void { @@ -124,7 +124,7 @@ export class FormulaUIController extends Disposable { this._componentManager.register(MORE_FUNCTIONS_COMPONENT, MoreFunctions); } - private _registerRenderControllers(): void { - this.disposeWithMe(this._renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, FormulaEditorShowController)); + private _registerRenderModules(): void { + this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, FormulaEditorShowController)); } } diff --git a/packages/sheets-formula/src/controllers/prompt.controller.ts b/packages/sheets-formula/src/controllers/prompt.controller.ts index 68a6338c79..869e8c5b4e 100644 --- a/packages/sheets-formula/src/controllers/prompt.controller.ts +++ b/packages/sheets-formula/src/controllers/prompt.controller.ts @@ -45,7 +45,7 @@ import { UniverInstanceType, } from '@univerjs/core'; import { - DocViewModelManagerService, + DocSkeletonManagerService, MoveCursorOperation, ReplaceContentCommand, TextSelectionManagerService, @@ -168,7 +168,6 @@ export class PromptController extends Disposable { @Inject(ISelectionRenderService) private readonly _selectionRenderService: ISelectionRenderService, @Inject(IDescriptionService) private readonly _descriptionService: IDescriptionService, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, @IEditorService private readonly _editorService: IEditorService ) { @@ -1340,13 +1339,11 @@ export class PromptController extends Disposable { const documentDataModel = this._univerInstanceService.getCurrentUniverDocInstance(); const editorUnitId = documentDataModel!.getUnitId(); - if (!this._editorService.isEditor(editorUnitId)) { return; } - const docViewModel = this._docViewModelManagerService.getViewModel(editorUnitId); - + const docViewModel = this._renderManagerService.getRenderById(editorUnitId)?.with(DocSkeletonManagerService).getViewModel(); if (docViewModel == null || documentDataModel == null) { return; } diff --git a/packages/sheets-formula/src/formula-ui-plugin.ts b/packages/sheets-formula/src/formula-ui-plugin.ts index 9cc1dbf485..13db46ec00 100644 --- a/packages/sheets-formula/src/formula-ui-plugin.ts +++ b/packages/sheets-formula/src/formula-ui-plugin.ts @@ -77,7 +77,7 @@ export class UniverSheetsFormulaPlugin extends Plugin { FormulaAlertRenderController, ]).forEach((controller) => { - this.disposeWithMe(this._renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, controller)); + this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, controller)); }); } diff --git a/packages/sheets-hyper-link-ui/src/plugin.ts b/packages/sheets-hyper-link-ui/src/plugin.ts index d68b4ea909..e735806307 100644 --- a/packages/sheets-hyper-link-ui/src/plugin.ts +++ b/packages/sheets-hyper-link-ui/src/plugin.ts @@ -69,6 +69,6 @@ export class UniverSheetsHyperLinkUIPlugin extends Plugin { dependencies.forEach((dep) => injector.add(dep)); - this._renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, SheetsHyperLinkRenderController); + this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, SheetsHyperLinkRenderController); } } diff --git a/packages/sheets-ui/src/controllers/editor/__tests__/end-edit.controller.spec.ts b/packages/sheets-ui/src/controllers/editor/__tests__/end-edit.controller.spec.ts index 00295f39e3..c6f886fc79 100644 --- a/packages/sheets-ui/src/controllers/editor/__tests__/end-edit.controller.spec.ts +++ b/packages/sheets-ui/src/controllers/editor/__tests__/end-edit.controller.spec.ts @@ -20,7 +20,7 @@ import { LexerTreeBuilder } from '@univerjs/engine-formula'; import { SpreadsheetSkeleton } from '@univerjs/engine-render'; import type { Injector } from '@wendellhu/redi'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; -import { getCellDataByInput } from '../end-edit.controller'; +import { getCellDataByInput } from '../editing.render-controller'; import { createTestBed } from './create-test-bed'; const richTextDemo: IDocumentData = { diff --git a/packages/sheets-ui/src/controllers/editor/editing.controller.ts b/packages/sheets-ui/src/controllers/editor/editing.controller.ts deleted file mode 100644 index 476ff568c0..0000000000 --- a/packages/sheets-ui/src/controllers/editor/editing.controller.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { Workbook } from '@univerjs/core'; -import { - DOCS_NORMAL_EDITOR_UNIT_ID_KEY, - IUndoRedoService, - IUniverInstanceService, - LifecycleStages, - ObjectMatrix, - OnLifecycle, - RxDisposable, - UniverInstanceType, -} from '@univerjs/core'; -import type { ISheetData } from '@univerjs/engine-formula'; -import { Inject } from '@wendellhu/redi'; -import { takeUntil } from 'rxjs'; - -@OnLifecycle(LifecycleStages.Rendered, EditingController) -export class EditingController extends RxDisposable { - constructor( - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @Inject(IUndoRedoService) private readonly _undoRedoService: IUndoRedoService - ) { - super(); - - this._initialize(); - } - - private _initialize() { - this._initialNormalInput(); - this._listenEditorBlur(); - } - - private _listenEditorBlur() { - this._univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_DOC) - .pipe(takeUntil(this.dispose$)) - .subscribe((docDataModel) => { - if (docDataModel == null) { - return; - } - - const unitId = docDataModel.getUnitId(); - - // Clear undo redo stack of cell editor when lose focus. - if (unitId !== DOCS_NORMAL_EDITOR_UNIT_ID_KEY) { - this._undoRedoService.clearUndoRedo(DOCS_NORMAL_EDITOR_UNIT_ID_KEY); - } - }); - } - - private _initialNormalInput() { - /** - * const formulaEngine = this.getPluginByName('formula')?.getUniverFormulaEngine(); - * =(sum(max(B1:C10,10)*5-100,((1+1)*2+5)/2,10)+count(B1:C10,10*5-100))*5-100 - * =(sum(max(B1:C10,10)*5-100,((1+1)*2+5)/2,10, lambda(x,y, x*y*x)(sum(1,(1+2)*3),2))+lambda(x,y, x*y*x)(sum(1,(1+2)*3),2)+count(B1:C10,10*5-100))*5-100 - * =((1+2)-A1:B2 + 5)/2 + sum(indirect("A5"):B10 + A1:offset("C5", 1, 1), 100) - * =1+(3*4=4)*5+1 - * =(-(1+2)--@A1:B2 + 5)/2 + -sum(indirect("A5"):B10# + B6# + A1:offset("C5", 1, 1) , 100) + {1,2,3;4,5,6;7,8,10} + lambda(x,y,z, x*y*z)(sum(1,(1+2)*3),2,lambda(x,y, @offset(A1:B0,x#*y#))(1,2):C20) & "美国人才" + sum((1+2%)*30%, 1+2)% - * =lambda(x, y, lambda(x,y, x*lambda(x,y, x*y*x)(x,y))(x,y))(sum(1,2,3), 2) - * =let(x,5,y,4,sum(x,y)+x)) - * =REDUCE(1, A1:C2, LAMBDA(a,b,a+b^2)) - * =sum(, A1:B1) - * formulaEngine?.calculate(`=lambda(x,y, x*y*x)(sum(1,(1+2)*3),2)+1-max(100,200)`); - */ - const sheetData: ISheetData = {}; - this._univerInstanceService - .getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)! - .getSheets() - .forEach((sheet) => { - const sheetConfig = sheet.getConfig(); - sheetData[sheet.getSheetId()] = { - cellData: new ObjectMatrix(sheetConfig.cellData), - rowCount: sheetConfig.rowCount, - columnCount: sheetConfig.columnCount, - rowData: sheetConfig.rowData, - columnData: sheetConfig.columnData, - }; - }); - } -} diff --git a/packages/sheets-ui/src/controllers/editor/start-edit.controller.ts b/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts similarity index 52% rename from packages/sheets-ui/src/controllers/editor/start-edit.controller.ts rename to packages/sheets-ui/src/controllers/editor/editing.render-controller.ts index e8098b7ee2..1137ed9b78 100644 --- a/packages/sheets-ui/src/controllers/editor/start-edit.controller.ts +++ b/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts @@ -14,12 +14,16 @@ * limitations under the License. */ -import type { ICommandInfo, IDocumentBody, IPosition, Nullable } from '@univerjs/core'; +import type { ICellData, ICommandInfo, IDocumentBody, IPosition, Nullable, Workbook } from '@univerjs/core'; import { + CellValueType, DEFAULT_EMPTY_DOCUMENT_VALUE, + Direction, Disposable, + DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, EDITOR_ACTIVATED, FOCUSING_EDITOR_BUT_HIDDEN, + FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, FOCUSING_FORMULA_EDITOR, FOCUSING_SHEET, @@ -27,10 +31,10 @@ import { HorizontalAlign, ICommandService, IContextService, + isFormulaString, + IUndoRedoService, IUniverInstanceService, - LifecycleStages, LocaleService, - OnLifecycle, toDisposable, Tools, VerticalAlign, @@ -41,11 +45,12 @@ import { VIEWPORT_KEY as DOC_VIEWPORT_KEY, DOCS_COMPONENT_MAIN_LAYER_INDEX, DocSkeletonManagerService, - DocViewModelManagerService, + MoveCursorOperation, + MoveSelectionOperation, RichTextEditingMutation, TextSelectionManagerService, } from '@univerjs/docs'; -import type { DocumentSkeleton, IDocumentLayoutObject, IEditorInputConfig, Scene } from '@univerjs/engine-render'; +import type { DocumentSkeleton, IDocumentLayoutObject, IEditorInputConfig, IRenderContext, IRenderModule, Scene } from '@univerjs/engine-render'; import { convertTextRotation, DeviceInputEventType, @@ -58,14 +63,19 @@ import { } from '@univerjs/engine-render'; import { IEditorService, KeyCode, SetEditorResizeOperation } from '@univerjs/ui'; import { Inject } from '@wendellhu/redi'; +import { ClearSelectionFormatCommand, SelectionManagerService, SetRangeValuesCommand, SetSelectionsOperation, SetWorksheetActivateCommand } from '@univerjs/sheets'; +import { distinctUntilChanged, filter } from 'rxjs'; +import { LexerTreeBuilder, matchToken } from '@univerjs/engine-formula'; -import { ClearSelectionFormatCommand } from '@univerjs/sheets'; -import { filter } from 'rxjs'; import { getEditorObject } from '../../basics/editor/get-editor-object'; -import { SetCellEditVisibleOperation } from '../../commands/operations/cell-edit.operation'; +import { SetCellEditVisibleArrowOperation, SetCellEditVisibleOperation, SetCellEditVisibleWithF2Operation } from '../../commands/operations/cell-edit.operation'; +import type { IEditorBridgeServiceVisibleParam } from '../../services/editor-bridge.service'; import { IEditorBridgeService } from '../../services/editor-bridge.service'; import { ICellEditorManagerService } from '../../services/editor/cell-editor-manager.service'; import styles from '../../views/sheet-container/index.module.less'; +import { MoveSelectionCommand, MoveSelectionEnterAndTabCommand } from '../../commands/commands/set-selection.command'; +import { MOVE_SELECTION_KEYCODE_LIST } from '../shortcuts/editor.shortcut'; +import { extractStringFromForceString, isForceString } from '../utils/cell-tools'; const HIDDEN_EDITOR_POSITION = -1000; @@ -78,19 +88,30 @@ interface ICanvasOffset { top: number; } -@OnLifecycle(LifecycleStages.Rendered, StartEditController) -export class StartEditController extends Disposable { - private _editorVisiblePrevious = false; +enum CursorChange { + InitialState, + StartEditor, + CursorChange, +} + +export class EditingRenderController extends Disposable implements IRenderModule { + /** + * It is used to distinguish whether the user has actively moved the cursor in the editor, mainly through mouse clicks. + */ + private _cursorChange: CursorChange = CursorChange.InitialState; constructor( - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService, + private readonly _context: IRenderContext, + @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, @IContextService private readonly _contextService: IContextService, + /** @deprecated This controller should not directly use univerInstanceService. Instead, it should call editor service. */ @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService, @ICellEditorManagerService private readonly _cellEditorManagerService: ICellEditorManagerService, @ITextSelectionRenderManager private readonly _textSelectionRenderManager: ITextSelectionRenderManager, + @Inject(SelectionManagerService) private readonly _selectionManagerService: SelectionManagerService, + @Inject(LexerTreeBuilder) private readonly _lexerTreeBuilder: LexerTreeBuilder, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, @ICommandService private readonly _commandService: ICommandService, @Inject(LocaleService) protected readonly _localService: LocaleService, @@ -98,21 +119,26 @@ export class StartEditController extends Disposable { ) { super(); - this._initialize(); - - this._commandExecutedListener(); + this._init(); } - override dispose(): void { - super.dispose(); - } - - private _initialize() { + private _init() { this._initialEditFocusListener(); - this._initialStartEdit(); this._initialKeyboardListener(); this._initialCursorSync(); this._listenEditorFocus(); + this._commandExecutedListener(); + + this._initEditorVisibilityListener(); + this._cursorStateListener(); + } + + private _initEditorVisibilityListener(): void { + this.disposeWithMe(this._editorBridgeService.visible$ + .pipe(distinctUntilChanged((prev, curr) => prev.visible === curr.visible)) + .subscribe((param) => { + param.visible ? this._handleEditorVisible(param) : this._handleEditorInvisible(param); + })); } private _listenEditorFocus() { @@ -128,15 +154,14 @@ export class StartEditController extends Disposable { // fix https://github.com/dream-num/univer/issues/628, need to recalculate the cell editor size after it acquire focus. if (this._editorBridgeService.isVisible()) { const param = this._editorBridgeService.getEditCellState(); - const unitId = this._editorBridgeService.getCurrentEditorId(); + const editorId = this._editorBridgeService.getCurrentEditorId(); - if (param == null || unitId == null || !this._editorService.isSheetEditor(unitId)) { + if (param == null || editorId == null || !this._editorService.isSheetEditor(editorId)) { return; } - const skeleton = this._docSkeletonManagerService.getSkeletonByUnitId(unitId)?.skeleton; - - if (skeleton == null) { + const skeleton = this._getEditorSkeleton(editorId); + if (!skeleton) { return; } @@ -149,6 +174,14 @@ export class StartEditController extends Disposable { ); } + private _getEditorSkeleton(editorId: string) { + return this._renderManagerService.getRenderById(editorId)?.with(DocSkeletonManagerService).getSkeleton(); + } + + private _getEditorViewModel(editorId: string) { + return this._renderManagerService.getRenderById(editorId)?.with(DocSkeletonManagerService).getViewModel(); + } + private _initialCursorSync() { this.disposeWithMe( this._cellEditorManagerService.focus$.pipe(filter((f) => !!f)).subscribe(() => { @@ -458,113 +491,99 @@ export class StartEditController extends Disposable { // You can double-click on the cell or input content by keyboard to put the cell into the edit state. // eslint-disable-next-line max-lines-per-function - private _initialStartEdit() { - // eslint-disable-next-line max-lines-per-function - this.disposeWithMe(this._editorBridgeService.visible$.subscribe((param) => { - const { visible, eventType, keycode } = param; - - if (visible === this._editorVisiblePrevious) { - return; - } - - this._editorVisiblePrevious = visible; - - if (visible === false) { - this._setOpenForCurrent(null, null); - return; - } + private _handleEditorVisible(param: IEditorBridgeServiceVisibleParam) { + const { eventType, keycode } = param; - const editCellState = this._editorBridgeService.getEditCellState(); + // Change `CursorChange` to changed status, when formula bar clicked. + this._cursorChange = + eventType === DeviceInputEventType.PointerDown + ? CursorChange.CursorChange + : CursorChange.StartEditor; - if (editCellState == null) { - return; - } + const editCellState = this._editorBridgeService.getEditCellState(); + if (editCellState == null) { + return; + } - const { - position, - documentLayoutObject, - canvasOffset, - scaleX, - scaleY, - editorUnitId, - unitId, - sheetId, - isInArrayFormulaRange = false, - } = editCellState; + const { + position, + documentLayoutObject, + canvasOffset, + scaleX, + scaleY, + editorUnitId, + unitId, + sheetId, + isInArrayFormulaRange = false, + } = editCellState; - const editorObject = this._getEditorObject(); + const editorObject = this._getEditorObject(); - if (editorObject == null) { - return; - } + if (editorObject == null) { + return; + } - this._setOpenForCurrent(unitId, sheetId); + this._setOpenForCurrent(unitId, sheetId); - const { document, scene } = editorObject; + const { document, scene } = editorObject; - this._contextService.setContextValue(EDITOR_ACTIVATED, true); + this._contextService.setContextValue(EDITOR_ACTIVATED, true); - const { documentModel: documentDataModel } = documentLayoutObject; + const { documentModel: documentDataModel } = documentLayoutObject; + const skeleton = this._getEditorSkeleton(editorUnitId); + if (!skeleton || !documentDataModel) { + return; + } - const docParam = this._docSkeletonManagerService.getSkeletonByUnitId(editorUnitId); + this._fitTextSize(position, canvasOffset, skeleton, documentLayoutObject, scaleX, scaleY); + // move selection + if ( + eventType === DeviceInputEventType.Keyboard || + (eventType === DeviceInputEventType.Dblclick && isInArrayFormulaRange) + ) { + const snapshot = Tools.deepClone(documentDataModel.getSnapshot()); + const documentViewModel = this._getEditorViewModel(editorUnitId); - if (docParam == null || documentDataModel == null) { + if (documentViewModel == null) { return; } - const { skeleton } = docParam; - - this._fitTextSize(position, canvasOffset, skeleton, documentLayoutObject, scaleX, scaleY); - // move selection - if ( - eventType === DeviceInputEventType.Keyboard || - (eventType === DeviceInputEventType.Dblclick && isInArrayFormulaRange) - ) { - const snapshot = Tools.deepClone(documentDataModel.getSnapshot()); - const documentViewModel = this._docViewModelManagerService.getViewModel(editorUnitId); - - if (documentViewModel == null) { - return; - } - - this._resetBodyStyle(snapshot.body!, !!isInArrayFormulaRange); + this._resetBodyStyle(snapshot.body!, !!isInArrayFormulaRange); - documentDataModel.reset(snapshot); - documentViewModel.reset(documentDataModel); + documentDataModel.reset(snapshot); + documentViewModel.reset(documentDataModel); - document.makeDirty(); - - // @JOCS, Why calculate here? - if (keycode === KeyCode.BACKSPACE || eventType === DeviceInputEventType.Dblclick) { - skeleton.calculate(); - this._editorBridgeService.changeEditorDirty(true); - } + document.makeDirty(); - this._textSelectionManagerService.replaceTextRanges([ - { - startOffset: 0, - endOffset: 0, - }, - ]); - } else if (eventType === DeviceInputEventType.Dblclick) { - // TODO: @JOCS, Get the position close to the cursor after clicking on the cell. - const cursor = documentDataModel.getBody()!.dataStream.length - 2 || 0; + // @JOCS, Why calculate here? + if (keycode === KeyCode.BACKSPACE || eventType === DeviceInputEventType.Dblclick) { + skeleton.calculate(); + this._editorBridgeService.changeEditorDirty(true); + } - scene.getViewport(DOC_VIEWPORT_KEY.VIEW_MAIN)?.scrollTo({ - y: Number.POSITIVE_INFINITY, - }); + this._textSelectionManagerService.replaceTextRanges([ + { + startOffset: 0, + endOffset: 0, + }, + ]); + } else if (eventType === DeviceInputEventType.Dblclick) { + // TODO: @JOCS, Get the position close to the cursor after clicking on the cell. + const cursor = documentDataModel.getBody()!.dataStream.length - 2 || 0; + + scene.getViewport(DOC_VIEWPORT_KEY.VIEW_MAIN)?.scrollTo({ + y: Number.POSITIVE_INFINITY, + }); - this._textSelectionManagerService.replaceTextRanges([ - { - startOffset: cursor, - endOffset: cursor, - }, - ]); - } + this._textSelectionManagerService.replaceTextRanges([ + { + startOffset: cursor, + endOffset: cursor, + }, + ]); + } - this._renderManagerService.getRenderById(unitId)?.scene.resetCursor(); - }) - ); + this._renderManagerService.getRenderById(unitId)?.scene.resetCursor(); } private _resetBodyStyle(body: IDocumentBody, removeStyle = false) { @@ -613,9 +632,10 @@ export class StartEditController extends Disposable { this._textSelectionRenderManager.onInputBefore$.subscribe((config) => { const isFocusFormulaEditor = this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR); const isFocusSheets = this._contextService.getContextValue(FOCUSING_SHEET); - const unitId = this._univerInstanceService.getCurrentUniverDocInstance()!.getUnitId(); - if (isFocusSheets && !isFocusFormulaEditor && this._editorService.isSheetEditor(unitId)) { + // TODO@Jocs: should get editor instead of current doc + const unitId = this._univerInstanceService.getCurrentUniverDocInstance()?.getUnitId(); + if (unitId && isFocusSheets && !isFocusFormulaEditor && this._editorService.isSheetEditor(unitId)) { this._showEditorByKeyboard(config); } }) @@ -652,20 +672,18 @@ export class StartEditController extends Disposable { return; } - const unitId = this._editorBridgeService.getCurrentEditorId(); - - if (unitId == null) { + const editorId = this._editorBridgeService.getCurrentEditorId(); + if (editorId == null) { return; } - this._editorBridgeService.changeEditorDirty(true); - - const skeleton = this._docSkeletonManagerService.getSkeletonByUnitId(unitId)?.skeleton; - + const skeleton = this._getEditorSkeleton(editorId); if (skeleton == null) { return; } + this._editorBridgeService.changeEditorDirty(true); + const param = this._editorBridgeService.getEditCellState(); if (param == null) { return; @@ -686,6 +704,36 @@ export class StartEditController extends Disposable { } }) ); + + const closeEditorOperation = [SetCellEditVisibleArrowOperation.id]; + this.disposeWithMe( + this._commandService.onCommandExecuted((command: ICommandInfo) => { + if (closeEditorOperation.includes(command.id)) { + const params = command.params as IEditorBridgeServiceVisibleParam & { isShift: boolean }; + const { keycode, isShift } = params; + + /** + * After the user enters the editor and actively moves the editor selection area with the mouse, + * the up, down, left, and right keys can no longer switch editing cells, + * but move the cursor within the editor instead. + */ + if (keycode != null && + (this._cursorChange === CursorChange.CursorChange || this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR)) + ) { + this._moveInEditor(keycode, isShift); + return; + } + + // TODO@Jocs: After we merging editor related controllers, this seems verbose. + // We can directly call SetRangeValues here. + this._editorBridgeService.changeVisible(params); + } + + if (command.id === SetCellEditVisibleWithF2Operation.id) { + this._cursorChange = CursorChange.CursorChange; + } + }) + ); } private _setOpenForCurrent(unitId: Nullable, sheetId: Nullable) { @@ -703,4 +751,306 @@ export class StartEditController extends Disposable { private _getEditorObject() { return getEditorObject(this._editorBridgeService.getCurrentEditorId(), this._renderManagerService); } + + private async _handleEditorInvisible(param: IEditorBridgeServiceVisibleParam) { + const { keycode } = param; + + this._setOpenForCurrent(null, null); + + this._cursorChange = CursorChange.InitialState; + + const selections = this._selectionManagerService.getSelections(); + const currentSelection = this._selectionManagerService.getCurrent(); + + if (currentSelection == null) { + return; + } + + const { unitId: workbookId, sheetId: worksheetId, pluginName } = currentSelection; + + this._exitInput(param); + + if (keycode === KeyCode.ESC) { + // Reselect the current selections, when exist cell editor by press ESC. + if (selections) { + this._commandService.syncExecuteCommand(SetSelectionsOperation.id, { + unitId: workbookId, + subUnitId: worksheetId, + pluginName, + selections, + }); + } + + return; + } + + const editCellState = this._editorBridgeService.getEditCellState(); + + if (editCellState == null) { + return; + } + + const { unitId, sheetId, row, column, documentLayoutObject } = editCellState; + + // If neither the formula bar editor nor the cell editor has been edited, + // it is considered that the content has not changed and returns directly. + const editorIsDirty = this._editorBridgeService.getEditorDirty(); + if (editorIsDirty === false) { + this._moveCursor(keycode); + + return; + } + + const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); + + const worksheet = workbook?.getSheetBySheetId(sheetId); + + if (worksheet == null) { + return; + } + + const cellData: Nullable = getCellDataByInput( + worksheet.getCellRaw(row, column) || {}, + documentLayoutObject, + this._lexerTreeBuilder + ); + + if (cellData == null) { + this._moveCursor(keycode); + + return; + } + + const context = { + subUnitId: sheetId, + unitId, + workbook: workbook!, + worksheet, + row, + col: column, + }; + + /** + * When closing the editor, switch to the current tab of the editor. + */ + if (workbookId === unitId && sheetId !== worksheetId && this._editorBridgeService.isForceKeepVisible()) { + // SetWorksheetActivateCommand handler uses Promise + await this._commandService.executeCommand(SetWorksheetActivateCommand.id, { + subUnitId: sheetId, + unitId, + }); + } + /** + * When switching tabs while the editor is open, + * the operation to refresh the selection will be blocked and needs to be triggered manually. + */ + this._selectionManagerService.refreshSelection(); + + const cell = this._editorBridgeService.interceptor.fetchThroughInterceptors( + this._editorBridgeService.interceptor.getInterceptPoints().AFTER_CELL_EDIT + )(cellData, context); + + const finalCell = await this._editorBridgeService.interceptor.fetchThroughInterceptors( + this._editorBridgeService.interceptor.getInterceptPoints().AFTER_CELL_EDIT_ASYNC + )(Promise.resolve(cell), context); + + this._commandService.executeCommand(SetRangeValuesCommand.id, { + subUnitId: sheetId, + unitId, + range: { + startRow: row, + startColumn: column, + endRow: row, + endColumn: column, + }, + value: finalCell, + }); + + // moveCursor need to put behind of SetRangeValuesCommand, fix https://github.com/dream-num/univer/issues/1155 + this._moveCursor(keycode); + } + + private _exitInput(param: IEditorBridgeServiceVisibleParam) { + this._contextService.setContextValue(FOCUSING_EDITOR_INPUT_FORMULA, false); + this._contextService.setContextValue(EDITOR_ACTIVATED, false); + this._contextService.setContextValue(FOCUSING_EDITOR_BUT_HIDDEN, false); + this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, false); + + this._cellEditorManagerService.setState({ + show: param.visible, + }); + const editorUnitId = this._editorBridgeService.getCurrentEditorId(); + if (editorUnitId == null || !this._editorService.isSheetEditor(editorUnitId)) { + return; + } + this._undoRedoService.clearUndoRedo(editorUnitId); + this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); + } + + private _moveCursor(keycode?: KeyCode) { + if (keycode == null || !MOVE_SELECTION_KEYCODE_LIST.includes(keycode)) { + return; + } + + let direction = Direction.LEFT; + + switch (keycode) { + case KeyCode.ENTER: + direction = Direction.DOWN; + break; + case KeyCode.TAB: + direction = Direction.RIGHT; + break; + case KeyCode.ARROW_DOWN: + direction = Direction.DOWN; + break; + case KeyCode.ARROW_UP: + direction = Direction.UP; + break; + case KeyCode.ARROW_LEFT: + direction = Direction.LEFT; + break; + case KeyCode.ARROW_RIGHT: + direction = Direction.RIGHT; + break; + } + + if (keycode === KeyCode.ENTER || keycode === KeyCode.TAB) { + this._commandService.executeCommand(MoveSelectionEnterAndTabCommand.id, { + keycode, + direction, + }); + } else { + this._commandService.executeCommand(MoveSelectionCommand.id, { + direction, + }); + } + } + + private _cursorStateListener() { + /** + * The user's operations follow the sequence of opening the editor and then moving the cursor. + * The logic here predicts the user's first cursor movement behavior based on this rule + */ + + const editorObject = this._getEditorObject(); + if (editorObject == null) { + return; + } + + const { document: documentComponent } = editorObject; + + this.disposeWithMe( + toDisposable( + documentComponent.onPointerDownObserver.add(() => { + if (this._cursorChange === CursorChange.StartEditor) { + this._cursorChange = CursorChange.CursorChange; + } + }) + ) + ); + } + + // TODO: @JOCS, is it necessary to move these commands MoveSelectionOperation\MoveCursorOperation to shortcut? and use multi-commands? + private _moveInEditor(keycode: KeyCode, isShift: boolean) { + let direction = Direction.LEFT; + if (keycode === KeyCode.ARROW_DOWN) { + direction = Direction.DOWN; + } else if (keycode === KeyCode.ARROW_UP) { + direction = Direction.UP; + } else if (keycode === KeyCode.ARROW_RIGHT) { + direction = Direction.RIGHT; + } + + if (isShift) { + this._commandService.executeCommand(MoveSelectionOperation.id, { + direction, + }); + } else { + this._commandService.executeCommand(MoveCursorOperation.id, { + direction, + }); + } + } +} + +export function getCellDataByInput( + cellData: ICellData, + documentLayoutObject: IDocumentLayoutObject, + lexerTreeBuilder: LexerTreeBuilder +) { + cellData = Tools.deepClone(cellData); + + const { documentModel } = documentLayoutObject; + if (documentModel == null) { + return null; + } + + const snapshot = documentModel.getSnapshot(); + + const { body } = snapshot; + if (body == null) { + return null; + } + + cellData.t = undefined; + + const data = body.dataStream; + const lastString = data.substring(data.length - 2, data.length); + let newDataStream = lastString === DEFAULT_EMPTY_DOCUMENT_VALUE ? data.substring(0, data.length - 2) : data; + + if (isFormulaString(newDataStream)) { + if (cellData.f === newDataStream) { + return null; + } + const bracketCount = lexerTreeBuilder.checkIfAddBracket(newDataStream); + for (let i = 0; i < bracketCount; i++) { + newDataStream += matchToken.CLOSE_BRACKET; + } + + cellData.f = newDataStream; + cellData.v = null; + cellData.p = null; + } else if (isForceString(newDataStream)) { + const v = extractStringFromForceString(newDataStream); + cellData.v = v; + cellData.f = null; + cellData.si = null; + cellData.p = null; + cellData.t = CellValueType.FORCE_STRING; + } else if (isRichText(body)) { + if (body.dataStream === '\r\n') { + cellData.v = ''; + cellData.f = null; + cellData.si = null; + cellData.p = null; + } else { + cellData.p = snapshot; + cellData.v = null; + cellData.f = null; + cellData.si = null; + } + } else { + // If the data is empty, the data is set to null. + if ((newDataStream === cellData.v || (newDataStream === '' && cellData.v == null)) && cellData.p == null) { + return null; + } + cellData.v = newDataStream; + cellData.f = null; + cellData.si = null; + cellData.p = null; + } + + return cellData; +} + +function isRichText(body: IDocumentBody) { + const { textRuns = [], paragraphs = [] } = body; + + return ( + textRuns.some((textRun) => textRun.ts && !Tools.isEmptyObject(textRun.ts)) || + paragraphs.some((paragraph) => paragraph.bullet) || + paragraphs.length >= 2 + ); } + diff --git a/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts b/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts deleted file mode 100644 index 0da34baf04..0000000000 --- a/packages/sheets-ui/src/controllers/editor/end-edit.controller.ts +++ /dev/null @@ -1,468 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICellData, ICommandInfo, IDocumentBody, Nullable, Observer } from '@univerjs/core'; -import { CellValueType, - DEFAULT_EMPTY_DOCUMENT_VALUE, - Direction, - Disposable, - DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, - EDITOR_ACTIVATED, - FOCUSING_EDITOR_BUT_HIDDEN, - FOCUSING_EDITOR_INPUT_FORMULA, - FOCUSING_FORMULA_EDITOR, - ICommandService, - IContextService, - isFormulaString, - IUndoRedoService, - IUniverInstanceService, - LifecycleStages, - OnLifecycle, - toDisposable, - Tools, -} from '@univerjs/core'; -import { MoveCursorOperation, MoveSelectionOperation } from '@univerjs/docs'; -import { LexerTreeBuilder, matchToken } from '@univerjs/engine-formula'; -import type { IDocumentLayoutObject, IMouseEvent, IPointerEvent } from '@univerjs/engine-render'; -import { DeviceInputEventType, IRenderManagerService } from '@univerjs/engine-render'; -import { - SelectionManagerService, - SetRangeValuesCommand, - SetSelectionsOperation, - SetWorksheetActivateCommand, -} from '@univerjs/sheets'; -import { IEditorService, KeyCode } from '@univerjs/ui'; -import { Inject } from '@wendellhu/redi'; - -import { getEditorObject } from '../../basics/editor/get-editor-object'; -import { MoveSelectionCommand, MoveSelectionEnterAndTabCommand } from '../../commands/commands/set-selection.command'; -import { SetCellEditVisibleArrowOperation, SetCellEditVisibleWithF2Operation } from '../../commands/operations/cell-edit.operation'; -import { ICellEditorManagerService } from '../../services/editor/cell-editor-manager.service'; -import type { IEditorBridgeServiceVisibleParam } from '../../services/editor-bridge.service'; -import { IEditorBridgeService } from '../../services/editor-bridge.service'; -import { MOVE_SELECTION_KEYCODE_LIST } from '../shortcuts/editor.shortcut'; -import { extractStringFromForceString, isForceString } from '../utils/cell-tools'; - -function isRichText(body: IDocumentBody) { - const { textRuns = [], paragraphs = [] } = body; - - return ( - textRuns.some((textRun) => textRun.ts && !Tools.isEmptyObject(textRun.ts)) || - paragraphs.some((paragraph) => paragraph.bullet) || - paragraphs.length >= 2 - ); -} - -enum CursorChange { - InitialState, - StartEditor, - CursorChange, -} - -@OnLifecycle(LifecycleStages.Rendered, EndEditController) -export class EndEditController extends Disposable { - private _cursorChangeObservers: Nullable>; - - private _editorVisiblePrevious = false; - - /** - * It is used to distinguish whether the user has actively moved the cursor in the editor, mainly through mouse clicks. - */ - private _cursorChange: CursorChange = CursorChange.InitialState; - - constructor( - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, - @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, - @ICommandService private readonly _commandService: ICommandService, - @IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService, - @IContextService private readonly _contextService: IContextService, - @ICellEditorManagerService private readonly _cellEditorManagerService: ICellEditorManagerService, - @Inject(LexerTreeBuilder) private readonly _lexerTreeBuilder: LexerTreeBuilder, - @IUndoRedoService private _undoRedoService: IUndoRedoService, - @Inject(SelectionManagerService) private readonly _selectionManagerService: SelectionManagerService, - @IEditorService private _editorService: IEditorService - ) { - super(); - - this._initialize(); - - this._commandExecutedListener(); - } - - override dispose(): void { - const editorObject = this._getEditorObject(); - - if (editorObject == null) { - return; - } - - const { document: documentComponent } = editorObject; - documentComponent.onPointerDownObserver.remove(this._cursorChangeObservers); - - super.dispose(); - } - - private _initialize() { - this._initialExitInput(); - - this._cursorStateListener(); - } - - // eslint-disable-next-line max-lines-per-function - private _initialExitInput() { - this.disposeWithMe( - // eslint-disable-next-line max-lines-per-function - this._editorBridgeService.visible$.subscribe(async (param) => { - const { visible, keycode, eventType } = param; - - if (visible === this._editorVisiblePrevious) { - return; - } - - this._editorVisiblePrevious = visible; - - if (visible === true) { - // Change `CursorChange` to changed status, when formula bar clicked. - this._cursorChange = - eventType === DeviceInputEventType.PointerDown - ? CursorChange.CursorChange - : CursorChange.StartEditor; - return; - } - - this._cursorChange = CursorChange.InitialState; - - const selections = this._selectionManagerService.getSelections(); - const currentSelection = this._selectionManagerService.getCurrent(); - - if (currentSelection == null) { - return; - } - - const { unitId: workbookId, sheetId: worksheetId, pluginName } = currentSelection; - - this._exitInput(param); - - if (keycode === KeyCode.ESC) { - // Reselect the current selections, when exist cell editor by press ESC. - if (selections) { - this._commandService.syncExecuteCommand(SetSelectionsOperation.id, { - unitId: workbookId, - subUnitId: worksheetId, - pluginName, - selections, - }); - } - - return; - } - - const editCellState = this._editorBridgeService.getEditCellState(); - - if (editCellState == null) { - return; - } - - const { unitId, sheetId, row, column, documentLayoutObject } = editCellState; - - // If neither the formula bar editor nor the cell editor has been edited, - // it is considered that the content has not changed and returns directly. - const editorIsDirty = this._editorBridgeService.getEditorDirty(); - if (editorIsDirty === false) { - this._moveCursor(keycode); - - return; - } - - const workbook = this._univerInstanceService.getUniverSheetInstance(unitId); - - const worksheet = workbook?.getSheetBySheetId(sheetId); - - if (worksheet == null) { - return; - } - - const cellData: Nullable = getCellDataByInput( - worksheet.getCellRaw(row, column) || {}, - documentLayoutObject, - this._lexerTreeBuilder - ); - - if (cellData == null) { - this._moveCursor(keycode); - - return; - } - - const context = { - subUnitId: sheetId, - unitId, - workbook: workbook!, - worksheet, - row, - col: column, - }; - - /** - * When closing the editor, switch to the current tab of the editor. - */ - if (workbookId === unitId && sheetId !== worksheetId && this._editorBridgeService.isForceKeepVisible()) { - // SetWorksheetActivateCommand handler uses Promise - await this._commandService.executeCommand(SetWorksheetActivateCommand.id, { - subUnitId: sheetId, - unitId, - }); - } - /** - * When switching tabs while the editor is open, - * the operation to refresh the selection will be blocked and needs to be triggered manually. - */ - this._selectionManagerService.refreshSelection(); - - const cell = this._editorBridgeService.interceptor.fetchThroughInterceptors( - this._editorBridgeService.interceptor.getInterceptPoints().AFTER_CELL_EDIT - )(cellData, context); - - const finalCell = await this._editorBridgeService.interceptor.fetchThroughInterceptors( - this._editorBridgeService.interceptor.getInterceptPoints().AFTER_CELL_EDIT_ASYNC - )(Promise.resolve(cell), context); - - this._commandService.executeCommand(SetRangeValuesCommand.id, { - subUnitId: sheetId, - unitId, - range: { - startRow: row, - startColumn: column, - endRow: row, - endColumn: column, - }, - value: finalCell, - }); - - // moveCursor need to put behind of SetRangeValuesCommand, fix https://github.com/dream-num/univer/issues/1155 - this._moveCursor(keycode); - }) - ); - } - - private _exitInput(param: IEditorBridgeServiceVisibleParam) { - this._contextService.setContextValue(FOCUSING_EDITOR_INPUT_FORMULA, false); - this._contextService.setContextValue(EDITOR_ACTIVATED, false); - this._contextService.setContextValue(FOCUSING_EDITOR_BUT_HIDDEN, false); - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, false); - - this._cellEditorManagerService.setState({ - show: param.visible, - }); - const editorUnitId = this._editorBridgeService.getCurrentEditorId(); - if (editorUnitId == null || !this._editorService.isSheetEditor(editorUnitId)) { - return; - } - this._undoRedoService.clearUndoRedo(editorUnitId); - this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); - } - - private _moveCursor(keycode?: KeyCode) { - if (keycode == null || !MOVE_SELECTION_KEYCODE_LIST.includes(keycode)) { - return; - } - - let direction = Direction.LEFT; - - switch (keycode) { - case KeyCode.ENTER: - direction = Direction.DOWN; - break; - case KeyCode.TAB: - direction = Direction.RIGHT; - break; - case KeyCode.ARROW_DOWN: - direction = Direction.DOWN; - break; - case KeyCode.ARROW_UP: - direction = Direction.UP; - break; - case KeyCode.ARROW_LEFT: - direction = Direction.LEFT; - break; - case KeyCode.ARROW_RIGHT: - direction = Direction.RIGHT; - break; - } - - if (keycode === KeyCode.ENTER || keycode === KeyCode.TAB) { - this._commandService.executeCommand(MoveSelectionEnterAndTabCommand.id, { - keycode, - direction, - }); - } else { - this._commandService.executeCommand(MoveSelectionCommand.id, { - direction, - }); - } - } - - private _cursorStateListener() { - /** - * The user's operations follow the sequence of opening the editor and then moving the cursor. - * The logic here predicts the user's first cursor movement behavior based on this rule - */ - - const editorObject = this._getEditorObject(); - if (editorObject == null) { - return; - } - - const { document: documentComponent } = editorObject; - - this.disposeWithMe( - toDisposable( - documentComponent.onPointerDownObserver.add(() => { - if (this._cursorChange === CursorChange.StartEditor) { - this._cursorChange = CursorChange.CursorChange; - } - }) - ) - ); - } - - private _commandExecutedListener() { - const updateCommandList = [SetCellEditVisibleArrowOperation.id]; - - this.disposeWithMe( - this._commandService.onCommandExecuted((command: ICommandInfo) => { - if (updateCommandList.includes(command.id)) { - const params = command.params as IEditorBridgeServiceVisibleParam & { isShift: boolean }; - const { keycode, isShift } = params; - - /** - * After the user enters the editor and actively moves the editor selection area with the mouse, - * the up, down, left, and right keys can no longer switch editing cells, - * but move the cursor within the editor instead. - */ - if (keycode != null && - (this._cursorChange === CursorChange.CursorChange || this._contextService.getContextValue(FOCUSING_FORMULA_EDITOR)) - ) { - this._moveInEditor(keycode, isShift); - return; - } - - this._editorBridgeService.changeVisible(params); - } - - if (command.id === SetCellEditVisibleWithF2Operation.id) { - this._cursorChange = CursorChange.CursorChange; - } - }) - ); - } - - // TODO: @JOCS, is it necessary to move these commands MoveSelectionOperation\MoveCursorOperation to shortcut? and use multi-commands? - private _moveInEditor(keycode: KeyCode, isShift: boolean) { - let direction = Direction.LEFT; - if (keycode === KeyCode.ARROW_DOWN) { - direction = Direction.DOWN; - } else if (keycode === KeyCode.ARROW_UP) { - direction = Direction.UP; - } else if (keycode === KeyCode.ARROW_RIGHT) { - direction = Direction.RIGHT; - } - - if (isShift) { - this._commandService.executeCommand(MoveSelectionOperation.id, { - direction, - }); - } else { - this._commandService.executeCommand(MoveCursorOperation.id, { - direction, - }); - } - } - - private _getEditorObject() { - return getEditorObject(this._editorBridgeService.getCurrentEditorId(), this._renderManagerService); - } -} - -export function getCellDataByInput( - cellData: ICellData, - documentLayoutObject: IDocumentLayoutObject, - lexerTreeBuilder: LexerTreeBuilder -) { - cellData = Tools.deepClone(cellData); - - const { documentModel } = documentLayoutObject; - if (documentModel == null) { - return null; - } - - const snapshot = documentModel.getSnapshot(); - - const { body } = snapshot; - if (body == null) { - return null; - } - - cellData.t = undefined; - - const data = body.dataStream; - const lastString = data.substring(data.length - 2, data.length); - let newDataStream = lastString === DEFAULT_EMPTY_DOCUMENT_VALUE ? data.substring(0, data.length - 2) : data; - - if (isFormulaString(newDataStream)) { - if (cellData.f === newDataStream) { - return null; - } - const bracketCount = lexerTreeBuilder.checkIfAddBracket(newDataStream); - for (let i = 0; i < bracketCount; i++) { - newDataStream += matchToken.CLOSE_BRACKET; - } - - cellData.f = newDataStream; - cellData.v = null; - cellData.p = null; - } else if (isForceString(newDataStream)) { - const v = extractStringFromForceString(newDataStream); - cellData.v = v; - cellData.f = null; - cellData.si = null; - cellData.p = null; - cellData.t = CellValueType.FORCE_STRING; - } else if (isRichText(body)) { - if (body.dataStream === '\r\n') { - cellData.v = ''; - cellData.f = null; - cellData.si = null; - cellData.p = null; - } else { - cellData.p = snapshot; - cellData.v = null; - cellData.f = null; - cellData.si = null; - } - } else { - // If the data is empty, the data is set to null. - if ((newDataStream === cellData.v || (newDataStream === '' && cellData.v == null)) && cellData.p == null) { - return null; - } - cellData.v = newDataStream; - cellData.f = null; - cellData.si = null; - cellData.p = null; - } - - return cellData; -} diff --git a/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts b/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts index 8bbfcb9f14..7420d7c062 100644 --- a/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts +++ b/packages/sheets-ui/src/controllers/editor/formula-editor.controller.ts @@ -38,11 +38,10 @@ import { CoverContentCommand, VIEWPORT_KEY as DOC_VIEWPORT_KEY, DocSkeletonManagerService, - DocViewModelManagerService, RichTextEditingMutation, TextSelectionManagerService, } from '@univerjs/docs'; -import type { RenderComponentType } from '@univerjs/engine-render'; +import type { DocumentViewModel, RenderComponentType } from '@univerjs/engine-render'; import { DeviceInputEventType, IRenderManagerService, ScrollBar } from '@univerjs/engine-render'; import type { IMoveRangeMutationParams, ISetRangeValuesMutationParams } from '@univerjs/sheets'; import { MoveRangeMutation, SetRangeValuesMutation } from '@univerjs/sheets'; @@ -67,8 +66,6 @@ export class FormulaEditorController extends RxDisposable { @IContextService private readonly _contextService: IContextService, @IFormulaEditorManagerService private readonly _formulaEditorManagerService: IFormulaEditorManagerService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService ) { super(); @@ -200,9 +197,6 @@ export class FormulaEditorController extends RxDisposable { this.disposeWithMe( toDisposable( documentComponent.onPointerDownObserver.add(() => { - this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, true); - this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); - // When clicking on the formula bar, the cell editor also needs to enter the edit state const visibleState = this._editorBridgeService.isVisible(); if (visibleState.visible === false) { @@ -211,6 +205,10 @@ export class FormulaEditorController extends RxDisposable { eventType: DeviceInputEventType.Dblclick, }); } + + // Open the normal editor first, and then we mark formula editor as activated. + this._contextService.setContextValue(FOCUSING_FORMULA_EDITOR, true); + this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); }) ) ); @@ -423,12 +421,16 @@ export class FormulaEditorController extends RxDisposable { actions: JSONXActions ) { const INCLUDE_LIST = [DOCS_NORMAL_EDITOR_UNIT_ID_KEY, DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY]; + const currentRender = this._renderManagerService.getRenderById(unitId); + if (currentRender == null) { + return; + } - const docsSkeletonObject = this._docSkeletonManagerService.getSkeletonByUnitId(unitId); + const skeleton = currentRender.with(DocSkeletonManagerService).getSkeleton(); const docDataModel = this._univerInstanceService.getUniverDocInstance(unitId); - const docViewModel = this._docViewModelManagerService.getViewModel(unitId); + const docViewModel = this._getEditorViewModel(unitId); - if (docDataModel == null || docViewModel == null || docsSkeletonObject == null) { + if (docDataModel == null || docViewModel == null) { return; } @@ -439,14 +441,6 @@ export class FormulaEditorController extends RxDisposable { docViewModel.reset(docDataModel); - const { skeleton } = docsSkeletonObject; - - const currentRender = this._renderManagerService.getRenderById(unitId); - - if (currentRender == null) { - return; - } - skeleton.calculate(); if (INCLUDE_LIST.includes(unitId)) { @@ -462,11 +456,11 @@ export class FormulaEditorController extends RxDisposable { ) { const INCLUDE_LIST = [DOCS_NORMAL_EDITOR_UNIT_ID_KEY, DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY]; - const docsSkeletonObject = this._docSkeletonManagerService.getSkeletonByUnitId(unitId); + const skeleton = this._renderManagerService.getRenderById(unitId)?.with(DocSkeletonManagerService).getSkeleton(); const docDataModel = this._univerInstanceService.getUniverDocInstance(unitId); - const docViewModel = this._docViewModelManagerService.getViewModel(unitId); + const docViewModel = this._getEditorViewModel(unitId); - if (docDataModel == null || docViewModel == null || docsSkeletonObject == null) { + if (docDataModel == null || docViewModel == null || skeleton == null) { return; } @@ -481,8 +475,6 @@ export class FormulaEditorController extends RxDisposable { docViewModel.reset(docDataModel); - const { skeleton } = docsSkeletonObject; - const currentRender = this._renderManagerService.getRenderById(unitId); if (currentRender == null) { @@ -530,10 +522,9 @@ export class FormulaEditorController extends RxDisposable { } private _autoScroll() { - const skeleton = this._docSkeletonManagerService.getSkeletonByUnitId(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY) - ?.skeleton; const position = this._formulaEditorManagerService.getPosition(); + const skeleton = this._renderManagerService.getRenderById(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY)?.with(DocSkeletonManagerService).getSkeleton(); const editorObject = this._renderManagerService.getRenderById(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); const formulaEditorDataModel = this._univerInstanceService.getUniverDocInstance( @@ -576,4 +567,8 @@ export class FormulaEditorController extends RxDisposable { viewportMain?.getScrollBar()?.dispose(); } } + + private _getEditorViewModel(unitId: string): Nullable { + return this._renderManagerService.getRenderById(unitId)?.with(DocSkeletonManagerService).getViewModel(); + } } diff --git a/packages/sheets-ui/src/controllers/render-controllers/editor-bridge.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/editor-bridge.render-controller.ts index d6edbf7d73..a9562d37fb 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/editor-bridge.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/editor-bridge.render-controller.ts @@ -121,10 +121,10 @@ export class EditorBridgeRenderController extends RxDisposable implements IRende }); }); - spreadsheet.onPointerDownObserver.add(this._keepVisibleHideEditor.bind(this)); - spreadsheetColumnHeader.onPointerDownObserver.add(this._keepVisibleHideEditor.bind(this)); - spreadsheetLeftTopPlaceholder.onPointerDownObserver.add(this._keepVisibleHideEditor.bind(this)); - spreadsheetRowHeader.onPointerDownObserver.add(this._keepVisibleHideEditor.bind(this)); + spreadsheet.onPointerDownObserver.add(this._onCanvasPointerDown.bind(this)); + spreadsheetColumnHeader.onPointerDownObserver.add(this._onCanvasPointerDown.bind(this)); + spreadsheetLeftTopPlaceholder.onPointerDownObserver.add(this._onCanvasPointerDown.bind(this)); + spreadsheetRowHeader.onPointerDownObserver.add(this._onCanvasPointerDown.bind(this)); } // Move to another controller @@ -152,11 +152,9 @@ export class EditorBridgeRenderController extends RxDisposable implements IRende // ); // } - /** - * In the activated state of formula editing, - * prohibit closing the editor according to the state to facilitate generating selection reference text. - */ - private _keepVisibleHideEditor() { + private _onCanvasPointerDown() { + // In the activated state of formula editing, + // prohibit closing the editor according to the state to facilitate generating selection reference text. if (this._editorBridgeService.isForceKeepVisible()) { return; } @@ -257,7 +255,7 @@ export class EditorBridgeRenderController extends RxDisposable implements IRende } if (command.id === SetWorksheetActiveOperation.id) { - this._keepVisibleHideEditor(); + this._onCanvasPointerDown(); } else if (updateCommandList.includes(command.id)) { this._hideEditor(); } diff --git a/packages/sheets-ui/src/controllers/render-controllers/sheet-render.controller.ts b/packages/sheets-ui/src/controllers/render-controllers/sheet-render.controller.ts deleted file mode 100644 index 7896465a3c..0000000000 --- a/packages/sheets-ui/src/controllers/render-controllers/sheet-render.controller.ts +++ /dev/null @@ -1,494 +0,0 @@ -/** - * Copyright 2023-present DreamNum Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { ICommandInfo, IRange, Workbook, Worksheet } from '@univerjs/core'; -import { CommandType, ICommandService, Rectangle, RxDisposable } from '@univerjs/core'; -import type { IRenderContext, IRenderModule, IViewportInfos, IWheelEvent, Scene } from '@univerjs/engine-render'; -import { - Layer, - PointerInput, - Rect, - RENDER_CLASS_TYPE, - ScrollBar, - SHEET_VIEWPORT_KEY, - Spreadsheet, - SpreadsheetColumnHeader, - SpreadsheetRowHeader, - Viewport, -} from '@univerjs/engine-render'; -import { Inject } from '@wendellhu/redi'; -import { COMMAND_LISTENER_SKELETON_CHANGE, COMMAND_LISTENER_VALUE_CHANGE, MoveRangeMutation, SetRangeValuesMutation, SetWorksheetActiveOperation } from '@univerjs/sheets'; -import { SetScrollRelativeCommand } from '../../commands/commands/set-scroll.command'; - -import { - SHEET_COMPONENT_HEADER_LAYER_INDEX, - SHEET_COMPONENT_MAIN_LAYER_INDEX, - SHEET_VIEW_KEY, -} from '../../common/keys'; -import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service'; -import { SheetsRenderService } from '../../services/sheets-render.service'; - -interface ISetWorksheetMutationParams { - unitId: string; - subUnitId: string; -} - -export class SheetRenderController extends RxDisposable implements IRenderModule { - constructor( - private readonly _context: IRenderContext, - @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, - @Inject(SheetsRenderService) private readonly _sheetRenderService: SheetsRenderService, - @ICommandService private readonly _commandService: ICommandService - ) { - super(); - - this._addNewRender(this._context.unit); - } - - private _addNewRender(workbook: Workbook) { - const { scene, engine } = this._context; - - scene.addLayer(new Layer(scene, [], 0), new Layer(scene, [], 2)); - - this._addComponent(workbook); - this._initRerenderScheduler(); - this._initCommandListener(); - - const worksheet = this._context.unit.getActiveSheet(); - if (!worksheet) { - throw new Error('No active sheet found'); - } - - const sheetId = worksheet.getSheetId(); - this._sheetSkeletonManagerService.setCurrent({ sheetId }); - const should = workbook.getShouldRenderLoopImmediately(); - if (should) { - engine.runRenderLoop(() => scene.render()); - } - } - - private _addComponent(workbook: Workbook) { - const { scene, components } = this._context; - - const worksheet = workbook.getActiveSheet(); - if (!worksheet) { - throw new Error('No active sheet found'); - } - - const spreadsheet = new Spreadsheet(SHEET_VIEW_KEY.MAIN); - - this._addViewport(worksheet); - - const spreadsheetRowHeader = new SpreadsheetRowHeader(SHEET_VIEW_KEY.ROW); - const spreadsheetColumnHeader = new SpreadsheetColumnHeader(SHEET_VIEW_KEY.COLUMN); - const SpreadsheetLeftTopPlaceholder = new Rect(SHEET_VIEW_KEY.LEFT_TOP, { - zIndex: 2, - left: -1, - top: -1, - fill: 'rgb(248, 249, 250)', - stroke: 'rgb(217, 217, 217)', - strokeWidth: 1, - }); - - this._context.mainComponent = spreadsheet; - components.set(SHEET_VIEW_KEY.MAIN, spreadsheet); - components.set(SHEET_VIEW_KEY.ROW, spreadsheetRowHeader); - components.set(SHEET_VIEW_KEY.COLUMN, spreadsheetColumnHeader); - components.set(SHEET_VIEW_KEY.LEFT_TOP, SpreadsheetLeftTopPlaceholder); - - scene.addObjects([spreadsheet], SHEET_COMPONENT_MAIN_LAYER_INDEX); - scene.addObjects( - [spreadsheetRowHeader, spreadsheetColumnHeader, SpreadsheetLeftTopPlaceholder], - SHEET_COMPONENT_HEADER_LAYER_INDEX - ); - - scene.enableLayerCache(SHEET_COMPONENT_MAIN_LAYER_INDEX, SHEET_COMPONENT_HEADER_LAYER_INDEX); - } - - // eslint-disable-next-line max-lines-per-function - private _initViewports(scene: Scene, rowHeader: { width: number }, columnHeader: { height: number }) { - const bufferEdgeX = 100; - const bufferEdgeY = 100; - // window.sc = scene; - const viewMain = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN, scene, { - left: rowHeader.width, - top: columnHeader.height, - bottom: 0, - right: 0, - isWheelPreventDefaultX: true, - isRelativeX: true, - isRelativeY: true, - allowCache: true, - bufferEdgeX, - bufferEdgeY, - }); - const viewMainLeftTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT_TOP, scene, { - isWheelPreventDefaultX: true, - active: false, - isRelativeX: false, - isRelativeY: false, - allowCache: true, - bufferEdgeX: 0, - bufferEdgeY: 0, - }); - - const viewMainLeft = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT, scene, { - isWheelPreventDefaultX: true, - active: false, - isRelativeX: false, - isRelativeY: true, - allowCache: true, - bufferEdgeX: 0, - bufferEdgeY, - }); - - const viewMainTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_TOP, scene, { - isWheelPreventDefaultX: true, - active: false, - isRelativeX: true, - isRelativeY: false, - allowCache: true, - bufferEdgeX, - bufferEdgeY: 0, - }); - - const viewRowTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_ROW_TOP, scene, { - active: false, - isWheelPreventDefaultX: true, - isRelativeX: false, - isRelativeY: false, - }); - - const viewRowBottom = new Viewport(SHEET_VIEWPORT_KEY.VIEW_ROW_BOTTOM, scene, { - left: 0, - top: columnHeader.height, - bottom: 0, - width: rowHeader.width, - isWheelPreventDefaultX: true, - isRelativeX: false, - isRelativeY: true, - }); - - const viewColumnLeft = new Viewport(SHEET_VIEWPORT_KEY.VIEW_COLUMN_LEFT, scene, { - active: false, - isWheelPreventDefaultX: true, - isRelativeX: false, - isRelativeY: false, - }); - - const viewColumnRight = new Viewport(SHEET_VIEWPORT_KEY.VIEW_COLUMN_RIGHT, scene, { - left: rowHeader.width, - top: 0, - height: columnHeader.height, - right: 0, - isWheelPreventDefaultX: true, - isRelativeX: true, - isRelativeY: false, - }); - - const viewLeftTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_LEFT_TOP, scene, { - left: 0, - top: 0, - width: rowHeader.width, - height: columnHeader.height, - isWheelPreventDefaultX: true, - isRelativeX: false, - isRelativeY: false, - }); - - return { - viewMain, - viewLeftTop, - viewMainLeftTop, - viewMainLeft, - viewMainTop, - viewColumnLeft, - viewRowTop, - viewRowBottom, - viewColumnRight, - }; - } - - /** - * - * initViewport & wheel event - * +-----------------+--------------------+-------------------+ - * | VIEW_LEFT_TOP | VIEW_COLUMN_LEFT | VIEW_COLUMN_RIGHT | - * +-----------------+--------------------+-------------------+ - * | VIEW_ROW_TOP | VIEW_MAIN_LEFT_TOP | VIEW_MAIN_TOP | - * +-----------------+--------------------+-------------------+ - * | VIEW_ROW_BOTTOM | VIEW_MAIN_LEFT | VIEW_MAIN | - * +-----------------+--------------------+-------------------+ - */ - - private _addViewport(worksheet: Worksheet) { - const scene = this._context.scene; - if (scene == null) { - return; - } - const { rowHeader, columnHeader } = worksheet.getConfig(); - const { viewMain } = this._initViewports(scene, rowHeader, columnHeader); - - this._initMouseWheel(scene, viewMain); - const _scrollBar = new ScrollBar(viewMain); - - scene.attachControl(); - - return viewMain; - } - - private _initRerenderScheduler(): void { - this.disposeWithMe(this._sheetSkeletonManagerService.currentSkeleton$.subscribe((param) => { - if (!param) return null; - - const { skeleton: spreadsheetSkeleton, sheetId } = param; - const workbook = this._context.unit; - const worksheet = workbook?.getSheetBySheetId(sheetId); - if (workbook == null || worksheet == null) return; - - const { mainComponent, components } = this._context; - const spreadsheet = mainComponent as Spreadsheet; - const spreadsheetRowHeader = components.get(SHEET_VIEW_KEY.ROW) as SpreadsheetRowHeader; - const spreadsheetColumnHeader = components.get(SHEET_VIEW_KEY.COLUMN) as SpreadsheetColumnHeader; - const spreadsheetLeftTopPlaceholder = components.get(SHEET_VIEW_KEY.LEFT_TOP) as Rect; - const { rowHeaderWidth, columnHeaderHeight } = spreadsheetSkeleton; - - spreadsheet?.updateSkeleton(spreadsheetSkeleton); - spreadsheetRowHeader?.updateSkeleton(spreadsheetSkeleton); - spreadsheetColumnHeader?.updateSkeleton(spreadsheetSkeleton); - spreadsheetLeftTopPlaceholder?.transformByState({ - width: rowHeaderWidth, - height: columnHeaderHeight, - }); - })); - } - - private _initCommandListener(): void { - this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { - const workbook = this._context.unit; - const unitId = workbook.getUnitId(); - if (COMMAND_LISTENER_SKELETON_CHANGE.includes(command.id) || this._sheetRenderService.checkMutationShouldTriggerRerender(command.id)) { - const worksheet = workbook.getActiveSheet(); - if (!worksheet) return; - - const sheetId = worksheet.getSheetId(); - const params = command.params; - const { unitId, subUnitId } = params as ISetWorksheetMutationParams; - - if ((unitId !== workbook.getUnitId() && subUnitId !== worksheet.getSheetId())) { - return; - } - - if (command.id !== SetWorksheetActiveOperation.id) { - this._sheetSkeletonManagerService.makeDirty({ - sheetId, - commandId: command.id, - }, true); - } - - // Change the skeleton to render when the sheet is changed. - // Should also check the init sheet. - // setCurrent ---> currentSkeletonBefore$ ---> zoom.controller.subscribe ---> scene._setTransForm ---> viewports markDirty - // setCurrent ---> currentSkeleton$ ---> scroll.controller.subscribe ---> scene?.transformByState ---> scene._setTransFor - this._sheetSkeletonManagerService.setCurrent({ - sheetId, - commandId: command.id, - }); - } else if (COMMAND_LISTENER_VALUE_CHANGE.includes(command.id)) { - this._sheetSkeletonManagerService.reCalculate(); - } - - if (command.type === CommandType.MUTATION) { - this._markUnitDirty(unitId, command); - } - })); - } - - private _markUnitDirty(unitId: string, command: ICommandInfo) { - const { mainComponent: spreadsheet, scene } = this._context; - // 现在 spreadsheet.markDirty 会调用 vport.markDirty - // 因为其他 controller 中存在 mainComponent?.makeDirty() 的调用, 不止是 sheet-render.controller 在标脏 - if (spreadsheet) { - spreadsheet.makeDirty(); // refresh spreadsheet - } - - scene.makeDirty(); - if (!command.params) return; - - const cmdParams = command.params as Record; - const viewports = this._spreadsheetViewports(scene); - if (command.id === SetRangeValuesMutation.id && cmdParams.cellValue) { - const dirtyRange: IRange = this._cellValueToRange(cmdParams.cellValue); - const dirtyBounds = this._rangeToBounds([dirtyRange]); - this._markViewportDirty(viewports, dirtyBounds); - (spreadsheet as unknown as Spreadsheet).setDirtyArea(dirtyBounds); - } - - if (command.id === MoveRangeMutation.id && cmdParams.from && cmdParams.to) { - const fromRange = this._cellValueToRange(cmdParams.from.value); - const toRange = this._cellValueToRange(cmdParams.to.value); - const dirtyBounds = this._rangeToBounds([fromRange, toRange]); - this._markViewportDirty(viewports, dirtyBounds); - (spreadsheet as unknown as Spreadsheet).setDirtyArea(dirtyBounds); - } - } - - /** - * cellValue data structure: - * {[row]: { [col]: value}} - * @param cellValue - * @returns - */ - private _cellValueToRange(cellValue: Record>) { - const rows = Object.keys(cellValue).map(Number); - const columns = []; - - for (const [_row, columnObj] of Object.entries(cellValue)) { - for (const column in columnObj) { - columns.push(Number(column)); - } - } - - const startRow = Math.min(...rows); - const endRow = Math.max(...rows); - const startColumn = Math.min(...columns); - const endColumn = Math.max(...columns); - - return { - startRow, - endRow, - startColumn, - endColumn, - } as IRange; - } - - private _rangeToBounds(ranges: IRange[]) { - const skeleton = this._sheetSkeletonManagerService.getCurrent()!.skeleton; - const { rowHeightAccumulation, columnWidthAccumulation, rowHeaderWidth, columnHeaderHeight } = skeleton; - - // rowHeightAccumulation 已经表示的是行底部的高度 - const dirtyBounds: IViewportInfos[] = []; - for (const r of ranges) { - const { startRow, endRow, startColumn, endColumn } = r; - const top = startRow === 0 ? 0 : rowHeightAccumulation[startRow - 1] + columnHeaderHeight; - const bottom = rowHeightAccumulation[endRow] + columnHeaderHeight; - const left = startColumn === 0 ? 0 : columnWidthAccumulation[startColumn - 1] + rowHeaderWidth; - const right = columnWidthAccumulation[endColumn] + rowHeaderWidth; - dirtyBounds.push({ top, left, bottom, right, width: right - left, height: bottom - top }); - } - return dirtyBounds; - } - - private _markViewportDirty(viewports: Viewport[], dirtyBounds: IViewportInfos[]) { - const activeViewports = viewports.filter((vp) => vp.isActive && vp.cacheBound); - for (const vp of activeViewports) { - for (const b of dirtyBounds) { - if (Rectangle.hasIntersectionBetweenTwoRect(vp.cacheBound!, b)) { - vp.markDirty(true); - } - } - } - } - - private _spreadsheetViewports(scene: Scene) { - return scene.getViewports().filter((v) => ['viewMain', 'viewMainLeftTop', 'viewMainTop', 'viewMainLeft'].includes(v.viewportKey)); - } - - // mouse scroll - private _initMouseWheel(scene: Scene, viewMain: Viewport) { - this.disposeWithMe( - scene.onMouseWheelObserver.add((evt: IWheelEvent, state) => { - if (evt.ctrlKey) { - return; - } - - let offsetX = 0; - let offsetY = 0; - - const isLimitedStore = viewMain.limitedScroll(); - if (evt.inputIndex === PointerInput.MouseWheelX) { - const deltaFactor = Math.abs(evt.deltaX); - // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; - const scrollNum = deltaFactor; - // 展示更多右侧内容,evt.deltaX > 0 - // 展示更多左侧内容, evt.deltaX < 0 - if (evt.deltaX > 0) { - offsetX = scrollNum; - } else { - offsetX = -scrollNum; - } - this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); - - // 临界点时执行浏览器行为 - if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { - if (!isLimitedStore?.isLimitedX) { - state.stopPropagation(); - } - } else if (viewMain.isWheelPreventDefaultX) { - evt.preventDefault(); - } else if (!isLimitedStore?.isLimitedX) { - evt.preventDefault(); - } - } - if (evt.inputIndex === PointerInput.MouseWheelY) { - const deltaFactor = Math.abs(evt.deltaY); - // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; - let scrollNum = deltaFactor; - if (evt.shiftKey) { - scrollNum *= 3; - if (evt.deltaY > 0) { - offsetX = scrollNum; - } else { - offsetX = -scrollNum; - } - this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); - - // 临界点时执行浏览器行为 - if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { - if (!isLimitedStore?.isLimitedX) { - state.stopPropagation(); - } - } else if (viewMain.isWheelPreventDefaultX) { - evt.preventDefault(); - } else if (!isLimitedStore?.isLimitedX) { - evt.preventDefault(); - } - } else { - if (evt.deltaY > 0) { - offsetY = scrollNum; - } else { - offsetY = -scrollNum; - } - this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetY }); - - // 临界点时执行浏览器行为 - if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { - if (!isLimitedStore?.isLimitedY) { - state.stopPropagation(); - } - } else if (viewMain.isWheelPreventDefaultY) { - evt.preventDefault(); - } else if (!isLimitedStore?.isLimitedY) { - evt.preventDefault(); - } - } - } - - this._context.scene.makeDirty(true); - }) - ); - } -} diff --git a/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts b/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts index cf732b1e13..13e876a2f5 100644 --- a/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts +++ b/packages/sheets-ui/src/controllers/render-controllers/sheet.render-controller.ts @@ -14,3 +14,483 @@ * limitations under the License. */ +import type { ICommandInfo, IRange, Workbook, Worksheet } from '@univerjs/core'; +import { CommandType, ICommandService, Rectangle, RxDisposable } from '@univerjs/core'; +import type { IRenderContext, IRenderModule, IViewportInfos, IWheelEvent, Scene } from '@univerjs/engine-render'; +import { + Layer, + PointerInput, + Rect, + RENDER_CLASS_TYPE, + ScrollBar, + SHEET_VIEWPORT_KEY, + Spreadsheet, + SpreadsheetColumnHeader, + SpreadsheetRowHeader, + Viewport, +} from '@univerjs/engine-render'; +import { Inject } from '@wendellhu/redi'; +import { COMMAND_LISTENER_SKELETON_CHANGE, COMMAND_LISTENER_VALUE_CHANGE, MoveRangeMutation, SetRangeValuesMutation, SetWorksheetActiveOperation } from '@univerjs/sheets'; +import { SetScrollRelativeCommand } from '../../commands/commands/set-scroll.command'; + +import { + SHEET_COMPONENT_HEADER_LAYER_INDEX, + SHEET_COMPONENT_MAIN_LAYER_INDEX, + SHEET_VIEW_KEY, +} from '../../common/keys'; +import { SheetSkeletonManagerService } from '../../services/sheet-skeleton-manager.service'; +import { SheetsRenderService } from '../../services/sheets-render.service'; + +interface ISetWorksheetMutationParams { + unitId: string; + subUnitId: string; +} + +export class SheetRenderController extends RxDisposable implements IRenderModule { + constructor( + private readonly _context: IRenderContext, + @Inject(SheetSkeletonManagerService) private readonly _sheetSkeletonManagerService: SheetSkeletonManagerService, + @Inject(SheetsRenderService) private readonly _sheetRenderService: SheetsRenderService, + @ICommandService private readonly _commandService: ICommandService + ) { + super(); + + this._addNewRender(); + } + + private _addNewRender() { + const { scene, engine, unit: workbook } = this._context; + + scene.addLayer(new Layer(scene, [], 0), new Layer(scene, [], 2)); + + this._addComponent(workbook); + this._initRerenderScheduler(); + this._initCommandListener(); + + const worksheet = this._context.unit.getActiveSheet(); + if (!worksheet) { + throw new Error('No active sheet found'); + } + + const sheetId = worksheet.getSheetId(); + this._sheetSkeletonManagerService.setCurrent({ sheetId }); + const should = workbook.getShouldRenderLoopImmediately(); + if (should) { + engine.runRenderLoop(() => scene.render()); + } + } + + private _addComponent(workbook: Workbook) { + const { scene, components } = this._context; + + const worksheet = workbook.getActiveSheet(); + if (!worksheet) { + throw new Error('No active sheet found'); + } + + const spreadsheet = new Spreadsheet(SHEET_VIEW_KEY.MAIN); + + this._addViewport(worksheet); + + const spreadsheetRowHeader = new SpreadsheetRowHeader(SHEET_VIEW_KEY.ROW); + const spreadsheetColumnHeader = new SpreadsheetColumnHeader(SHEET_VIEW_KEY.COLUMN); + const SpreadsheetLeftTopPlaceholder = new Rect(SHEET_VIEW_KEY.LEFT_TOP, { + zIndex: 2, + left: -1, + top: -1, + fill: 'rgb(248, 249, 250)', + stroke: 'rgb(217, 217, 217)', + strokeWidth: 1, + }); + + this._context.mainComponent = spreadsheet; + components.set(SHEET_VIEW_KEY.MAIN, spreadsheet); + components.set(SHEET_VIEW_KEY.ROW, spreadsheetRowHeader); + components.set(SHEET_VIEW_KEY.COLUMN, spreadsheetColumnHeader); + components.set(SHEET_VIEW_KEY.LEFT_TOP, SpreadsheetLeftTopPlaceholder); + + scene.addObjects([spreadsheet], SHEET_COMPONENT_MAIN_LAYER_INDEX); + scene.addObjects( + [spreadsheetRowHeader, spreadsheetColumnHeader, SpreadsheetLeftTopPlaceholder], + SHEET_COMPONENT_HEADER_LAYER_INDEX + ); + + scene.enableLayerCache(SHEET_COMPONENT_MAIN_LAYER_INDEX, SHEET_COMPONENT_HEADER_LAYER_INDEX); + } + + // eslint-disable-next-line max-lines-per-function + private _initViewports(scene: Scene, rowHeader: { width: number }, columnHeader: { height: number }) { + const bufferEdgeX = 100; + const bufferEdgeY = 100; + // window.sc = scene; + const viewMain = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN, scene, { + left: rowHeader.width, + top: columnHeader.height, + bottom: 0, + right: 0, + isWheelPreventDefaultX: true, + isRelativeX: true, + isRelativeY: true, + allowCache: true, + bufferEdgeX, + bufferEdgeY, + }); + const viewMainLeftTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT_TOP, scene, { + isWheelPreventDefaultX: true, + active: false, + isRelativeX: false, + isRelativeY: false, + allowCache: true, + bufferEdgeX: 0, + bufferEdgeY: 0, + }); + + const viewMainLeft = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_LEFT, scene, { + isWheelPreventDefaultX: true, + active: false, + isRelativeX: false, + isRelativeY: true, + allowCache: true, + bufferEdgeX: 0, + bufferEdgeY, + }); + + const viewMainTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_MAIN_TOP, scene, { + isWheelPreventDefaultX: true, + active: false, + isRelativeX: true, + isRelativeY: false, + allowCache: true, + bufferEdgeX, + bufferEdgeY: 0, + }); + + const viewRowTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_ROW_TOP, scene, { + active: false, + isWheelPreventDefaultX: true, + isRelativeX: false, + isRelativeY: false, + }); + + const viewRowBottom = new Viewport(SHEET_VIEWPORT_KEY.VIEW_ROW_BOTTOM, scene, { + left: 0, + top: columnHeader.height, + bottom: 0, + width: rowHeader.width, + isWheelPreventDefaultX: true, + isRelativeX: false, + isRelativeY: true, + }); + + const viewColumnLeft = new Viewport(SHEET_VIEWPORT_KEY.VIEW_COLUMN_LEFT, scene, { + active: false, + isWheelPreventDefaultX: true, + isRelativeX: false, + isRelativeY: false, + }); + + const viewColumnRight = new Viewport(SHEET_VIEWPORT_KEY.VIEW_COLUMN_RIGHT, scene, { + left: rowHeader.width, + top: 0, + height: columnHeader.height, + right: 0, + isWheelPreventDefaultX: true, + isRelativeX: true, + isRelativeY: false, + }); + + const viewLeftTop = new Viewport(SHEET_VIEWPORT_KEY.VIEW_LEFT_TOP, scene, { + left: 0, + top: 0, + width: rowHeader.width, + height: columnHeader.height, + isWheelPreventDefaultX: true, + isRelativeX: false, + isRelativeY: false, + }); + + return { + viewMain, + viewLeftTop, + viewMainLeftTop, + viewMainLeft, + viewMainTop, + viewColumnLeft, + viewRowTop, + viewRowBottom, + viewColumnRight, + }; + } + + /** + * + * initViewport & wheel event + * +-----------------+--------------------+-------------------+ + * | VIEW_LEFT_TOP | VIEW_COLUMN_LEFT | VIEW_COLUMN_RIGHT | + * +-----------------+--------------------+-------------------+ + * | VIEW_ROW_TOP | VIEW_MAIN_LEFT_TOP | VIEW_MAIN_TOP | + * +-----------------+--------------------+-------------------+ + * | VIEW_ROW_BOTTOM | VIEW_MAIN_LEFT | VIEW_MAIN | + * +-----------------+--------------------+-------------------+ + */ + + private _addViewport(worksheet: Worksheet) { + const scene = this._context.scene; + if (scene == null) { + return; + } + const { rowHeader, columnHeader } = worksheet.getConfig(); + const { viewMain } = this._initViewports(scene, rowHeader, columnHeader); + + this._initMouseWheel(scene, viewMain); + const _scrollBar = new ScrollBar(viewMain); + + scene.attachControl(); + + return viewMain; + } + + private _initRerenderScheduler(): void { + this.disposeWithMe(this._sheetSkeletonManagerService.currentSkeleton$.subscribe((param) => { + if (!param) return null; + + const { skeleton: spreadsheetSkeleton, sheetId } = param; + const workbook = this._context.unit; + const worksheet = workbook?.getSheetBySheetId(sheetId); + if (workbook == null || worksheet == null) return; + + const { mainComponent, components } = this._context; + const spreadsheet = mainComponent as Spreadsheet; + const spreadsheetRowHeader = components.get(SHEET_VIEW_KEY.ROW) as SpreadsheetRowHeader; + const spreadsheetColumnHeader = components.get(SHEET_VIEW_KEY.COLUMN) as SpreadsheetColumnHeader; + const spreadsheetLeftTopPlaceholder = components.get(SHEET_VIEW_KEY.LEFT_TOP) as Rect; + const { rowHeaderWidth, columnHeaderHeight } = spreadsheetSkeleton; + + spreadsheet?.updateSkeleton(spreadsheetSkeleton); + spreadsheetRowHeader?.updateSkeleton(spreadsheetSkeleton); + spreadsheetColumnHeader?.updateSkeleton(spreadsheetSkeleton); + spreadsheetLeftTopPlaceholder?.transformByState({ + width: rowHeaderWidth, + height: columnHeaderHeight, + }); + })); + } + + private _initCommandListener(): void { + this.disposeWithMe(this._commandService.onCommandExecuted((command: ICommandInfo) => { + const { unit: workbook, unitId: workbookId } = this._context; + const { id: commandId } = command; + + if (COMMAND_LISTENER_SKELETON_CHANGE.includes(commandId) || this._sheetRenderService.checkMutationShouldTriggerRerender(commandId)) { + const worksheet = workbook.getActiveSheet(); + if (!worksheet) return; + + const workbookId = this._context.unitId; + const worksheetId = worksheet.getSheetId(); + const params = command.params; + const { unitId, subUnitId } = params as ISetWorksheetMutationParams; + + if (unitId !== workbookId || subUnitId !== worksheetId) { + return; + } + + if (commandId !== SetWorksheetActiveOperation.id) { + this._sheetSkeletonManagerService.makeDirty({ + sheetId: worksheetId, + commandId, + }, true); + } + + // Change the skeleton to render when the sheet is changed. + // Should also check the init sheet. + // setCurrent ---> currentSkeletonBefore$ ---> zoom.controller.subscribe ---> scene._setTransForm ---> viewports markDirty + // setCurrent ---> currentSkeleton$ ---> scroll.controller.subscribe ---> scene?.transformByState ---> scene._setTransFor + this._sheetSkeletonManagerService.setCurrent({ + sheetId: worksheetId, + commandId, + }); + } else if (COMMAND_LISTENER_VALUE_CHANGE.includes(commandId)) { + this._sheetSkeletonManagerService.reCalculate(); + } + + if (command.type === CommandType.MUTATION) { + this._markUnitDirty(workbookId, command); + } + })); + } + + private _markUnitDirty(unitId: string, command: ICommandInfo) { + const { mainComponent: spreadsheet, scene } = this._context; + // 现在 spreadsheet.markDirty 会调用 vport.markDirty + // 因为其他 controller 中存在 mainComponent?.makeDirty() 的调用, 不止是 sheet-render.controller 在标脏 + if (spreadsheet) { + spreadsheet.makeDirty(); // refresh spreadsheet + } + + scene.makeDirty(); + if (!command.params) return; + + const cmdParams = command.params as Record; + const viewports = this._spreadsheetViewports(scene); + if (command.id === SetRangeValuesMutation.id && cmdParams.cellValue) { + const dirtyRange: IRange = this._cellValueToRange(cmdParams.cellValue); + const dirtyBounds = this._rangeToBounds([dirtyRange]); + this._markViewportDirty(viewports, dirtyBounds); + (spreadsheet as unknown as Spreadsheet).setDirtyArea(dirtyBounds); + } + + if (command.id === MoveRangeMutation.id && cmdParams.from && cmdParams.to) { + const fromRange = this._cellValueToRange(cmdParams.from.value); + const toRange = this._cellValueToRange(cmdParams.to.value); + const dirtyBounds = this._rangeToBounds([fromRange, toRange]); + this._markViewportDirty(viewports, dirtyBounds); + (spreadsheet as unknown as Spreadsheet).setDirtyArea(dirtyBounds); + } + } + + /** + * cellValue data structure: + * {[row]: { [col]: value}} + * @param cellValue + * @returns + */ + private _cellValueToRange(cellValue: Record>) { + const rows = Object.keys(cellValue).map(Number); + const columns = []; + + for (const [_row, columnObj] of Object.entries(cellValue)) { + for (const column in columnObj) { + columns.push(Number(column)); + } + } + + const startRow = Math.min(...rows); + const endRow = Math.max(...rows); + const startColumn = Math.min(...columns); + const endColumn = Math.max(...columns); + + return { + startRow, + endRow, + startColumn, + endColumn, + } as IRange; + } + + private _rangeToBounds(ranges: IRange[]) { + const skeleton = this._sheetSkeletonManagerService.getCurrent()!.skeleton; + const { rowHeightAccumulation, columnWidthAccumulation, rowHeaderWidth, columnHeaderHeight } = skeleton; + + // rowHeightAccumulation 已经表示的是行底部的高度 + const dirtyBounds: IViewportInfos[] = []; + for (const r of ranges) { + const { startRow, endRow, startColumn, endColumn } = r; + const top = startRow === 0 ? 0 : rowHeightAccumulation[startRow - 1] + columnHeaderHeight; + const bottom = rowHeightAccumulation[endRow] + columnHeaderHeight; + const left = startColumn === 0 ? 0 : columnWidthAccumulation[startColumn - 1] + rowHeaderWidth; + const right = columnWidthAccumulation[endColumn] + rowHeaderWidth; + dirtyBounds.push({ top, left, bottom, right, width: right - left, height: bottom - top }); + } + return dirtyBounds; + } + + private _markViewportDirty(viewports: Viewport[], dirtyBounds: IViewportInfos[]) { + const activeViewports = viewports.filter((vp) => vp.isActive && vp.cacheBound); + for (const vp of activeViewports) { + for (const b of dirtyBounds) { + if (Rectangle.hasIntersectionBetweenTwoRect(vp.cacheBound!, b)) { + vp.markDirty(true); + } + } + } + } + + private _spreadsheetViewports(scene: Scene) { + return scene.getViewports().filter((v) => ['viewMain', 'viewMainLeftTop', 'viewMainTop', 'viewMainLeft'].includes(v.viewportKey)); + } + + // mouse scroll + private _initMouseWheel(scene: Scene, viewMain: Viewport) { + this.disposeWithMe( + scene.onMouseWheelObserver.add((evt: IWheelEvent, state) => { + if (evt.ctrlKey) { + return; + } + + let offsetX = 0; + let offsetY = 0; + + const isLimitedStore = viewMain.limitedScroll(); + if (evt.inputIndex === PointerInput.MouseWheelX) { + const deltaFactor = Math.abs(evt.deltaX); + // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; + const scrollNum = deltaFactor; + // 展示更多右侧内容,evt.deltaX > 0 + // 展示更多左侧内容, evt.deltaX < 0 + if (evt.deltaX > 0) { + offsetX = scrollNum; + } else { + offsetX = -scrollNum; + } + this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); + + // 临界点时执行浏览器行为 + if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { + if (!isLimitedStore?.isLimitedX) { + state.stopPropagation(); + } + } else if (viewMain.isWheelPreventDefaultX) { + evt.preventDefault(); + } else if (!isLimitedStore?.isLimitedX) { + evt.preventDefault(); + } + } + if (evt.inputIndex === PointerInput.MouseWheelY) { + const deltaFactor = Math.abs(evt.deltaY); + // let magicNumber = deltaFactor < 40 ? 2 : deltaFactor < 80 ? 3 : 4; + let scrollNum = deltaFactor; + if (evt.shiftKey) { + scrollNum *= 3; + if (evt.deltaY > 0) { + offsetX = scrollNum; + } else { + offsetX = -scrollNum; + } + this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetX }); + + // 临界点时执行浏览器行为 + if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { + if (!isLimitedStore?.isLimitedX) { + state.stopPropagation(); + } + } else if (viewMain.isWheelPreventDefaultX) { + evt.preventDefault(); + } else if (!isLimitedStore?.isLimitedX) { + evt.preventDefault(); + } + } else { + if (evt.deltaY > 0) { + offsetY = scrollNum; + } else { + offsetY = -scrollNum; + } + this._commandService.executeCommand(SetScrollRelativeCommand.id, { offsetY }); + + // 临界点时执行浏览器行为 + if (scene.getParent().classType === RENDER_CLASS_TYPE.SCENE_VIEWER) { + if (!isLimitedStore?.isLimitedY) { + state.stopPropagation(); + } + } else if (viewMain.isWheelPreventDefaultY) { + evt.preventDefault(); + } else if (!isLimitedStore?.isLimitedY) { + evt.preventDefault(); + } + } + } + + this._context.scene.makeDirty(true); + }) + ); + } +} diff --git a/packages/sheets-ui/src/index.ts b/packages/sheets-ui/src/index.ts index c3aa49b43f..525d668133 100644 --- a/packages/sheets-ui/src/index.ts +++ b/packages/sheets-ui/src/index.ts @@ -77,7 +77,7 @@ export { SelectionShape } from './services/selection/selection-shape'; export type { ISheetSkeletonManagerParam } from './services/sheet-skeleton-manager.service'; export { SheetSkeletonManagerService } from './services/sheet-skeleton-manager.service'; export { UniverSheetsUIPlugin } from './sheets-ui-plugin'; -export { SheetRenderController } from './controllers/render-controllers/sheet-render.controller'; +export { SheetRenderController } from './controllers/render-controllers/sheet.render-controller'; export { HoverManagerService } from './services/hover-manager.service'; export { DragManagerService } from './services/drag-manager.service'; export { CellAlertManagerService, CellAlertType, type ICellAlert } from './services/cell-alert-manager.service'; diff --git a/packages/sheets-ui/src/services/sheets-render.service.ts b/packages/sheets-ui/src/services/sheets-render.service.ts index 3e1cf129a5..047273405f 100644 --- a/packages/sheets-ui/src/services/sheets-render.service.ts +++ b/packages/sheets-ui/src/services/sheets-render.service.ts @@ -37,7 +37,7 @@ export class SheetsRenderService extends RxDisposable { constructor( @IContextService private readonly _contextService: IContextService, - @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, + @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService ) { super(); @@ -71,13 +71,13 @@ export class SheetsRenderService extends RxDisposable { } private _initWorkbookListener(): void { - this._univerInstanceService.getTypeOfUnitAdded$(UniverInstanceType.UNIVER_SHEET) + this._instanceSrv.getTypeOfUnitAdded$(UniverInstanceType.UNIVER_SHEET) .pipe(takeUntil(this.dispose$)) .subscribe((workbook) => this._createRenderer(workbook)); - this._univerInstanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET) + this._instanceSrv.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET) .forEach((workbook) => this._createRenderer(workbook)); - this._univerInstanceService.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_SHEET) + this._instanceSrv.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_SHEET) .pipe(takeUntil(this.dispose$)) .subscribe((workbook) => this._disposeRenderer(workbook)); } @@ -85,12 +85,13 @@ export class SheetsRenderService extends RxDisposable { private _createRenderer(workbook: Workbook): void { const unitId = workbook.getUnitId(); this._renderManagerService.createRender(unitId); - this._renderManagerService.setCurrent(unitId); // NOTE@wzhudev: maybe not in uni mode + + // NOTE@wzhudev: maybe not in univer mode + this._renderManagerService.setCurrent(unitId); } private _disposeRenderer(workbook: Workbook): void { const unitId = workbook.getUnitId(); - this._renderManagerService.removeRender(unitId); } diff --git a/packages/sheets-ui/src/sheets-ui-plugin.ts b/packages/sheets-ui/src/sheets-ui-plugin.ts index 1d4c143ea7..50858c1bc8 100644 --- a/packages/sheets-ui/src/sheets-ui-plugin.ts +++ b/packages/sheets-ui/src/sheets-ui-plugin.ts @@ -15,7 +15,7 @@ */ import type { Workbook } from '@univerjs/core'; -import { DependentOn, IUniverInstanceService, LocaleService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; +import { DependentOn, IUniverInstanceService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; import { filter } from 'rxjs/operators'; @@ -26,10 +26,8 @@ import { UniverUIPlugin } from '@univerjs/ui'; import { ActiveWorksheetController } from './controllers/active-worksheet/active-worksheet.controller'; import { AutoHeightController } from './controllers/auto-height.controller'; import { SheetClipboardController } from './controllers/clipboard/clipboard.controller'; -import { EditingController } from './controllers/editor/editing.controller'; -import { EndEditController } from './controllers/editor/end-edit.controller'; import { FormulaEditorController } from './controllers/editor/formula-editor.controller'; -import { StartEditController } from './controllers/editor/start-edit.controller'; +import { EditingRenderController } from './controllers/editor/editing.render-controller'; import { FormatPainterRenderController } from './controllers/render-controllers/format-painter.render-controller'; import { HeaderFreezeRenderController } from './controllers/render-controllers/freeze.render-controller'; import { HeaderMenuRenderController } from './controllers/render-controllers/header-menu.render-controller'; @@ -58,7 +56,7 @@ import { ISheetBarService, SheetBarService } from './services/sheet-bar/sheet-ba import { SheetSkeletonManagerService } from './services/sheet-skeleton-manager.service'; import { ShortcutExperienceService } from './services/shortcut-experience.service'; import { IStatusBarService, StatusBarService } from './services/status-bar.service'; -import { SheetRenderController } from './controllers/render-controllers/sheet-render.controller'; +import { SheetRenderController } from './controllers/render-controllers/sheet.render-controller'; import { HoverRenderController } from './controllers/hover-render.controller'; import { HoverManagerService } from './services/hover-manager.service'; import { CellAlertManagerService } from './services/cell-alert-manager.service'; @@ -94,7 +92,6 @@ export class UniverSheetsUIPlugin extends Plugin { constructor( private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector, - @Inject(LocaleService) private readonly _localeService: LocaleService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService ) { @@ -131,7 +128,6 @@ export class UniverSheetsUIPlugin extends Plugin { // controllers [ActiveWorksheetController], [AutoHeightController], - [EndEditController], [FormulaEditorController], [HeaderFreezeRenderController], [SheetClipboardController], @@ -142,9 +138,7 @@ export class UniverSheetsUIPlugin extends Plugin { useFactory: () => this._injector.createInstance(SheetUIController, this._config), }, ], - [StartEditController], [StatusBarController], - [EditingController], [AutoFillController], [FormatPainterController], @@ -175,21 +169,21 @@ export class UniverSheetsUIPlugin extends Plugin { } override onRendered(): void { - this._registerRenderControllers(); + this._registerRenderModules(); } private _registerRenderBasics(): void { ([ SheetSkeletonManagerService, SheetRenderController, - ]).forEach((controller) => { - this.disposeWithMe(this._renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, controller)); + ]).forEach((m) => { + this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m)); }); } // We have to let render basics get bootstrapped before. Because some render controllers relies on // a correct skeleton when they get loaded. - private _registerRenderControllers(): void { + private _registerRenderModules(): void { ([ // https://github.com/dream-num/univer-pro/issues/669 // HeaderMoveRenderController(HMRC) must be initialized before SelectionRenderController(SRC). @@ -214,13 +208,17 @@ export class UniverSheetsUIPlugin extends Plugin { ForceStringRenderController, CellCustomRenderController, SheetContextMenuRenderController, + + // editor EditorBridgeRenderController, + EditingRenderController, + // permission SheetPermissionInterceptorCanvasRenderController, SheetPermissionInterceptorFormulaRenderController, SheetPermissionRenderController, - ]).forEach((controller) => { - this.disposeWithMe(this._renderManagerService.registerRenderController(UniverInstanceType.UNIVER_SHEET, controller)); + ]).forEach((m) => { + this.disposeWithMe(this._renderManagerService.registerRenderModule(UniverInstanceType.UNIVER_SHEET, m)); }); } @@ -228,8 +226,6 @@ export class UniverSheetsUIPlugin extends Plugin { const univerInstanceService = this._univerInstanceService; univerInstanceService.getCurrentTypeOfUnit$(UniverInstanceType.UNIVER_SHEET) .pipe(filter((v) => !!v)) - .subscribe((workbook) => { - univerInstanceService.focusUnit(workbook!.getUnitId()); - }); + .subscribe((workbook) => univerInstanceService.focusUnit(workbook!.getUnitId())); } } diff --git a/packages/sheets-zen-editor/src/controllers/zen-editor.controller.ts b/packages/sheets-zen-editor/src/controllers/zen-editor.controller.ts index 299b8492f5..91289b7a12 100644 --- a/packages/sheets-zen-editor/src/controllers/zen-editor.controller.ts +++ b/packages/sheets-zen-editor/src/controllers/zen-editor.controller.ts @@ -31,7 +31,6 @@ import type { IDocObjectParam, IRichTextEditingMutationParams } from '@univerjs/ import { VIEWPORT_KEY as DOC_VIEWPORT_KEY, DocSkeletonManagerService, - DocViewModelManagerService, getDocObject, RichTextEditingMutation, TextSelectionManagerService, @@ -60,9 +59,7 @@ export class ZenEditorController extends RxDisposable { @IZenZoneService private readonly _zenZoneService: IZenZoneService, @IEditorBridgeService private readonly _editorBridgeService: IEditorBridgeService, @IUndoRedoService private readonly _undoRedoService: IUndoRedoService, - @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @Inject(DocSkeletonManagerService) private readonly _docSkeletonManagerService: DocSkeletonManagerService, - @Inject(DocViewModelManagerService) private readonly _docViewModelManagerService: DocViewModelManagerService + @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService ) { super(); @@ -127,7 +124,7 @@ export class ZenEditorController extends RxDisposable { const { engine } = editorObject; - const skeleton = this._docSkeletonManagerService.getSkeletonByUnitId(DOCS_ZEN_EDITOR_UNIT_ID_KEY)?.skeleton; + const skeleton = this._renderManagerService.getRenderById(DOCS_ZEN_EDITOR_UNIT_ID_KEY)?.with(DocSkeletonManagerService).getSkeleton(); // Update page size when container resized. // zenEditorDataModel.updateDocumentDataPageSize(width); @@ -221,11 +218,12 @@ export class ZenEditorController extends RxDisposable { DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, ]; - const docsSkeletonObject = this._docSkeletonManagerService.getSkeletonByUnitId(unitId); + const docSkeletonManagerService = this._renderManagerService.getRenderById(unitId)?.with(DocSkeletonManagerService); + const skeleton = docSkeletonManagerService?.getSkeleton(); const docDataModel = this._univerInstanceService.getUniverDocInstance(unitId); - const docViewModel = this._docViewModelManagerService.getViewModel(unitId); + const docViewModel = docSkeletonManagerService?.getViewModel(); - if (docDataModel == null || docViewModel == null || docsSkeletonObject == null) { + if (docDataModel == null || docViewModel == null || skeleton == null) { return; } @@ -245,10 +243,7 @@ export class ZenEditorController extends RxDisposable { docViewModel.reset(docDataModel); - const { skeleton } = docsSkeletonObject; - const currentRender = this._getDocObject(); - if (currentRender == null) { return; } diff --git a/packages/ui/src/controllers/ui/ui-desktop.controller.tsx b/packages/ui/src/controllers/ui/ui-desktop.controller.tsx index 24a82b78fe..25761b7161 100644 --- a/packages/ui/src/controllers/ui/ui-desktop.controller.tsx +++ b/packages/ui/src/controllers/ui/ui-desktop.controller.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Disposable, IUniverInstanceService, LifecycleService, LifecycleStages, toDisposable, UniverInstanceType } from '@univerjs/core'; +import { Disposable, isInternalEditorID, IUniverInstanceService, LifecycleService, LifecycleStages, toDisposable } from '@univerjs/core'; import { IRenderManagerService } from '@univerjs/engine-render'; import type { IDisposable } from '@wendellhu/redi'; import { Inject, Injector, Optional } from '@wendellhu/redi'; @@ -53,14 +53,12 @@ export class DesktopUIController extends Disposable { this.disposeWithMe(this._layoutService.registerCanvasElement(canvasElement as HTMLCanvasElement)); } - // TODO: this is subject to change in the future + // TODO: this is subject to change in the future for Uni-mode this._renderManagerService.currentRender$.subscribe((renderId) => { if (renderId) { const render = this._renderManagerService.getRenderById(renderId)!; if (!render.unitId) return; - - const unitType = this._instanceService.getUnitType(render.unitId); - if (unitType !== UniverInstanceType.UNIVER_SHEET) return; + if (isInternalEditorID(render.unitId)) return; render.engine.setContainer(canvasElement); } diff --git a/packages/ui/src/services/editor/editor.service.ts b/packages/ui/src/services/editor/editor.service.ts index 1da1bfbf76..b93ce46355 100644 --- a/packages/ui/src/services/editor/editor.service.ts +++ b/packages/ui/src/services/editor/editor.service.ts @@ -17,7 +17,7 @@ import type { DocumentDataModel, IDocumentBody, IDocumentData, IDocumentStyle, IPosition, Nullable, Workbook } from '@univerjs/core'; import { DEFAULT_EMPTY_DOCUMENT_VALUE, DEFAULT_STYLES, Disposable, EDITOR_ACTIVATED, FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE, HorizontalAlign, IContextService, IUniverInstanceService, toDisposable, UniverInstanceType, VerticalAlign } from '@univerjs/core'; import type { IDisposable } from '@wendellhu/redi'; -import { createIdentifier, Inject, Injector } from '@wendellhu/redi'; +import { createIdentifier, Inject } from '@wendellhu/redi'; import type { Observable } from 'rxjs'; import { Subject } from 'rxjs'; import type { IRender, ISuccinctTextRangeParam, Scene } from '@univerjs/engine-render'; @@ -353,7 +353,6 @@ export class EditorService extends Disposable implements IEditorService, IDispos private _spreadsheetFocusState: boolean = false; constructor( - @Inject(Injector) private readonly _injector: Injector, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @Inject(LexerTreeBuilder) private readonly _lexerTreeBuilder: LexerTreeBuilder, @@ -617,10 +616,11 @@ export class EditorService extends Disposable implements IEditorService, IDispos register(config: IEditorConfigParam, container: HTMLDivElement): IDisposable { const { initialSnapshot, editorUnitId, canvasStyle = {} } = config; - const documentDataModel = this._univerInstanceService.createUnit(UniverInstanceType.UNIVER_DOC, initialSnapshot || this._getBlank(editorUnitId)); + const documentDataModel = this._univerInstanceService.createUnit( + UniverInstanceType.UNIVER_DOC, initialSnapshot || this._getBlank(editorUnitId) + ); let render = this._renderManagerService.getRenderById(editorUnitId); - if (render == null) { this._renderManagerService.create(editorUnitId); render = this._renderManagerService.getRenderById(editorUnitId)!; @@ -633,6 +633,7 @@ export class EditorService extends Disposable implements IEditorService, IDispos this._editors.set(editorUnitId, editor); // Delete scroll bar + // FIXME@Jocs: should add a configuration when creating a renderer, not delete it. (render.mainComponent?.getScene() as Scene)?.getViewports()?.[0].getScrollBar()?.dispose(); if (!editor.isSheetEditor()) { @@ -754,6 +755,4 @@ export class EditorService extends Disposable implements IEditorService, IDispos } } -export const IEditorService = createIdentifier( - 'univer.editor.service' -); +export const IEditorService = createIdentifier('univer.editor.service'); diff --git a/packages/ui/src/ui-plugin.ts b/packages/ui/src/ui-plugin.ts index f87bc2c928..6fafa06539 100644 --- a/packages/ui/src/ui-plugin.ts +++ b/packages/ui/src/ui-plugin.ts @@ -144,6 +144,6 @@ export class UniverUIPlugin extends Plugin { private _initUI(): void { // We need to run this async to let other modules do their `onReady` jobs first. - Promise.resolve().then(() => this._injector.get(IUIController).bootstrapWorkbench(this._config)); + setTimeout(() => this._injector.get(IUIController).bootstrapWorkbench(this._config), 16); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6bb5eee5d6..7454dfb8b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -951,6 +951,9 @@ importers: '@univerjs/docs': specifier: workspace:* version: link:../docs + '@univerjs/docs-ui': + specifier: workspace:* + version: link:../docs-ui '@univerjs/engine-formula': specifier: workspace:* version: link:../engine-formula