diff --git a/examples/src/main.tsx b/examples/src/main.tsx index 198bf5d14a4..04cbe1c8b7d 100644 --- a/examples/src/main.tsx +++ b/examples/src/main.tsx @@ -53,7 +53,7 @@ function Examples() { title: '📚 Docs Uniscript', href: './docs-uniscript/', }, { - title: '🌌 Universe', + title: '🌌 Uni Mode', href: './uni/', }, { title: '📱 Mobile', diff --git a/packages-experimental/uni-formula-ui/package.json b/packages-experimental/uni-formula-ui/package.json index 3b0e34e8e1f..bc811038ae0 100644 --- a/packages-experimental/uni-formula-ui/package.json +++ b/packages-experimental/uni-formula-ui/package.json @@ -62,8 +62,11 @@ "@univerjs/core": "workspace:*", "@univerjs/docs": "workspace:*", "@univerjs/docs-ui": "workspace:*", + "@univerjs/engine-render": "workspace:*", "@univerjs/rpc": "workspace:*", "@univerjs/sheets-formula": "workspace:*", + "@univerjs/slides": "workspace:*", + "@univerjs/slides-ui": "workspace:*", "@univerjs/ui": "workspace:*", "@univerjs/uni-formula": "workspace:*", "clsx": ">=2.0.0", @@ -75,10 +78,13 @@ "@univerjs/design": "workspace:*", "@univerjs/docs": "workspace:*", "@univerjs/docs-ui": "workspace:*", + "@univerjs/engine-render": "workspace:*", "@univerjs/icons": "^0.1.72", "@univerjs/rpc": "workspace:*", "@univerjs/shared": "workspace:*", "@univerjs/sheets-formula": "workspace:*", + "@univerjs/slides": "workspace:*", + "@univerjs/slides-ui": "workspace:*", "@univerjs/ui": "workspace:*", "@univerjs/uni-formula": "workspace:*", "clsx": "^2.1.1", diff --git a/packages-experimental/uni-formula-ui/src/commands/command.ts b/packages-experimental/uni-formula-ui/src/commands/commands/doc.command.ts similarity index 99% rename from packages-experimental/uni-formula-ui/src/commands/command.ts rename to packages-experimental/uni-formula-ui/src/commands/commands/doc.command.ts index 4f9746e6e19..b9614323142 100644 --- a/packages-experimental/uni-formula-ui/src/commands/command.ts +++ b/packages-experimental/uni-formula-ui/src/commands/commands/doc.command.ts @@ -22,8 +22,9 @@ import { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniForm export interface IAddDocUniFormulaCommandParams { unitId: string; - f: string; startIndex: number; + + f: string; } export const AddDocUniFormulaCommand: ICommand = { diff --git a/packages-experimental/uni-formula-ui/src/commands/commands/slide.command.ts b/packages-experimental/uni-formula-ui/src/commands/commands/slide.command.ts new file mode 100644 index 00000000000..d8ebb2cb161 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/commands/commands/slide.command.ts @@ -0,0 +1,75 @@ +/** + * 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 { ICommand, IDocumentBody } from '@univerjs/core'; +import { CommandType, CustomRangeType, generateRandomId, ICommandService, LocaleService, makeCustomRangeStream } from '@univerjs/core'; +import { SLIDE_EDITOR_ID } from '@univerjs/slides-ui'; +import { makeSelection, replaceSelectionFactory } from '@univerjs/docs'; +import { SlideUIFormulaCacheService } from '../../services/slide-ui-formula-cache.service'; + +export interface IAddSlideUniFormulaCommandParams { + unitId: string; + pageId: string; + elementId: string; + startIndex: number; + + f: string; +} + +export const AddSlideUniFormulaCommand: ICommand = { + type: CommandType.COMMAND, + id: 'slide.command.add-slide-uni-formula', + async handler(accessor, params: IAddSlideUniFormulaCommandParams) { + const { startIndex } = params; + + const commandService = accessor.get(ICommandService); + const slideUiFormulaCacheService = accessor.get(SlideUIFormulaCacheService); + const localeService = accessor.get(LocaleService); + const placeholder = localeService.t('uni-formula.command.stream-placeholder'); + + // TODO: use placeholder here? + const rangeId = generateRandomId(); + const dataStream = makeCustomRangeStream(placeholder); + const body: IDocumentBody = { + dataStream, + customRanges: [{ + startIndex: 0, + endIndex: dataStream.length - 1, + rangeId, + rangeType: CustomRangeType.UNI_FORMULA, + wholeEntity: true, + }], + }; + + const insertCustomRangeMutation = replaceSelectionFactory(accessor, { + unitId: SLIDE_EDITOR_ID, + body, + selection: makeSelection(startIndex, startIndex + 1), + }); + + // NOTE: For slides, the process to update a element's content is pretty different from docs. + // Since the text editor in slides is temporary, we don't need to update the content of the element when user + // has not confirmed the change. So we don't need to add a mutation to update resources here. + // We will do that when user confirms the change. + + if (insertCustomRangeMutation) { + slideUiFormulaCacheService.writeCache(rangeId, params); + return commandService.executeCommand(insertCustomRangeMutation.id, insertCustomRangeMutation.params, { onlyLocal: true }); + } + + return false; + }, +}; diff --git a/packages-experimental/uni-formula-ui/src/commands/operation.ts b/packages-experimental/uni-formula-ui/src/commands/operation.ts deleted file mode 100644 index aa91b6c4f72..00000000000 --- a/packages-experimental/uni-formula-ui/src/commands/operation.ts +++ /dev/null @@ -1,60 +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 { ICommand, IOperation } from '@univerjs/core'; -import { CommandType } from '@univerjs/core'; -import { DocFormulaPopupService } from '../services/formula-popup.service'; - -export interface IShowFormulaPopupOperationParams { - unitId: string; - startIndex: number; - - type?: 'new' | 'existing'; - rangeId?: string; -} - -export const ShowFormulaPopupOperation: IOperation = { - id: 'doc.operation.show-formula-popup', - type: CommandType.OPERATION, - handler(accessor, params: IShowFormulaPopupOperationParams) { - const { type = 'new', startIndex, unitId, rangeId } = params; - const docFormulaPopupService = accessor.get(DocFormulaPopupService); - - if (type === 'existing' && !rangeId) { - return false; - } - - return docFormulaPopupService.showPopup(unitId, startIndex, type, rangeId); - }, -}; - -export const CloseFormulaPopupOperation: IOperation = { - id: 'doc.operation.close-formula-popup', - type: CommandType.OPERATION, - handler(accessor) { - const docFormulaPopupService = accessor.get(DocFormulaPopupService); - return docFormulaPopupService.closePopup(true); - }, -}; - -export const ConfirmFormulaPopupCommand: ICommand = { - id: 'doc.operation.confirm-formula-popup', - type: CommandType.COMMAND, - handler(accessor) { - const docFormulaPopupService = accessor.get(DocFormulaPopupService); - return docFormulaPopupService.confirmPopup(); - }, -}; diff --git a/packages-experimental/uni-formula-ui/src/commands/operations/operation.ts b/packages-experimental/uni-formula-ui/src/commands/operations/operation.ts new file mode 100644 index 00000000000..836e3ffc1bf --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/commands/operations/operation.ts @@ -0,0 +1,78 @@ +/** + * 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 { ICommand, IOperation } from '@univerjs/core'; +import { CommandType } from '@univerjs/core'; +import { UniFormulaPopupService } from '../../services/formula-popup.service'; + +export interface IDocPopupPosition { + rangeId?: string; +} + +export interface ISlidePopupPosition extends IDocPopupPosition { + pageId: string; + elementId: string; +} + +export type IPopupPosition = IDocPopupPosition | ISlidePopupPosition; + +export function isSlidePosition(position?: IPopupPosition): position is ISlidePopupPosition { + return !!position && 'pageId' in position; +} + +export interface IShowDocFormulaPopupOperationParams { + unitId: string; + startIndex: number; + type?: 'new' | 'existing'; + position: IDocPopupPosition; +} + +export interface IShowSlideFormulaPopupOPerationParams extends IShowDocFormulaPopupOperationParams { + position: ISlidePopupPosition; +} + +export type IShowFormulaPopupOperationParams = IShowDocFormulaPopupOperationParams | IShowSlideFormulaPopupOPerationParams; + +export const ShowFormulaPopupOperation: IOperation = { + id: 'uni-formula.operation.show-formula-popup', + type: CommandType.OPERATION, + handler(accessor, params: IShowFormulaPopupOperationParams) { + const { type = 'new', startIndex, unitId, position } = params; + const { rangeId } = position; + const formulaPopupService = accessor.get(UniFormulaPopupService); + + if (type === 'existing' && !rangeId) return false; + return formulaPopupService.showDocPopup(unitId, startIndex, type, position); + }, +}; + +export const CloseFormulaPopupOperation: IOperation = { + id: 'uni-formula.operation.close-formula-popup', + type: CommandType.OPERATION, + handler(accessor) { + const docFormulaPopupService = accessor.get(UniFormulaPopupService); + return docFormulaPopupService.closePopup(true); + }, +}; + +export const ConfirmFormulaPopupCommand: ICommand = { + id: 'uni-formula.operation.confirm-formula-popup', + type: CommandType.COMMAND, + handler(accessor) { + const docFormulaPopupService = accessor.get(UniFormulaPopupService); + return docFormulaPopupService.confirmPopup(); + }, +}; diff --git a/packages-experimental/uni-formula-ui/src/controllers/formula-input.controller.ts b/packages-experimental/uni-formula-ui/src/controllers/doc-formula-input.controller.ts similarity index 74% rename from packages-experimental/uni-formula-ui/src/controllers/formula-input.controller.ts rename to packages-experimental/uni-formula-ui/src/controllers/doc-formula-input.controller.ts index 0244f5fb01a..f7872b182b4 100644 --- a/packages-experimental/uni-formula-ui/src/controllers/formula-input.controller.ts +++ b/packages-experimental/uni-formula-ui/src/controllers/doc-formula-input.controller.ts @@ -17,53 +17,45 @@ import { CustomRangeType, Disposable, ICommandService, ILogService, Inject, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; import type { IInsertCommandParams } from '@univerjs/docs'; import { DeleteLeftCommand, InsertCommand, MoveCursorOperation, TextSelectionManagerService } from '@univerjs/docs'; -import { ComponentManager, IEditorService } from '@univerjs/ui'; +import { IEditorService } from '@univerjs/ui'; import { DocHoverManagerService } from '@univerjs/docs-ui'; -import { AddDocUniFormulaCommand, RemoveDocUniFormulaCommand, UpdateDocUniFormulaCommand } from '../commands/command'; -import type { IShowFormulaPopupOperationParams } from '../commands/operation'; -import { CloseFormulaPopupOperation, ConfirmFormulaPopupCommand, ShowFormulaPopupOperation } from '../commands/operation'; -import { DocFormulaPopup, DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY } from '../views/components/DocFormulaPopup'; -import { DocFormulaPopupService } from '../services/formula-popup.service'; +import { AddDocUniFormulaCommand, RemoveDocUniFormulaCommand, UpdateDocUniFormulaCommand } from '../commands/commands/doc.command'; +import type { IShowFormulaPopupOperationParams } from '../commands/operations/operation'; +import { CloseFormulaPopupOperation, ShowFormulaPopupOperation } from '../commands/operations/operation'; +import { UNI_FORMULA_EDITOR_ID } from '../views/components/DocFormulaPopup'; +import { UniFormulaPopupService } from '../services/formula-popup.service'; const FORMULA_INPUT_TRIGGER_CHAR = '='; -@OnLifecycle(LifecycleStages.Steady, DocUniFormulaController) -export class DocUniFormulaController extends Disposable { +@OnLifecycle(LifecycleStages.Steady, DocUniFormulaInputController) +export class DocUniFormulaInputController extends Disposable { constructor( @ICommandService private readonly _commandService: ICommandService, @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, @IEditorService private readonly _editorService: IEditorService, @ILogService private readonly _logService: ILogService, - @Inject(DocHoverManagerService) private readonly _docHoverManagerService: DocHoverManagerService, - @Inject(DocFormulaPopupService) private readonly _docFormulaPopupService: DocFormulaPopupService, - @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, - @Inject(ComponentManager) private readonly _componentManager: ComponentManager + @Inject(DocHoverManagerService) private readonly _docHoverManagerSrv: DocHoverManagerService, + @Inject(UniFormulaPopupService) private readonly _formulaPopupSrv: UniFormulaPopupService, + @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService ) { super(); this._initKeyboardListeners(); - this._initComponents(); this._initCommands(); this._initHoverListener(); } private _initCommands(): void { [ - ShowFormulaPopupOperation, - CloseFormulaPopupOperation, - ConfirmFormulaPopupCommand, AddDocUniFormulaCommand, RemoveDocUniFormulaCommand, UpdateDocUniFormulaCommand, ].forEach((command) => this._commandService.registerCommand(command)); } - private _initComponents(): void { - this.disposeWithMe(this._componentManager.register(DocFormulaPopup.componentKey, DocFormulaPopup)); - } - private _initKeyboardListeners(): void { + // TODO@wzhudev: only need to listen when a doc unit is focused. // The formula input trigger works not exactly the same as Mention. this.disposeWithMe(this._commandService.onCommandExecuted((commandInfo) => { const currentEditor = this._editorService.getFocusEditor(); @@ -72,7 +64,7 @@ export class DocUniFormulaController extends Disposable { const { id } = commandInfo; if ( - currentEditor?.editorUnitId === DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY || + currentEditor?.editorUnitId === UNI_FORMULA_EDITOR_ID || focusedUnit?.type !== UniverInstanceType.UNIVER_DOC ) { return; @@ -85,8 +77,9 @@ export class DocUniFormulaController extends Disposable { this._showPopup({ startIndex: activeRange.startOffset! - 1, unitId: focusedUnit.getUnitId(), + position: {}, }); - } else if (this._docFormulaPopupService.popupInfo) { + } else if (this._formulaPopupSrv.popupInfo) { this._closePopup(); } } @@ -98,13 +91,13 @@ export class DocUniFormulaController extends Disposable { } private _initHoverListener(): void { - this.disposeWithMe(this._docHoverManagerService.activeCustomRanges$.subscribe((customRanges) => { + this.disposeWithMe(this._docHoverManagerSrv.activeCustomRanges$.subscribe((customRanges) => { const focusedUnit = this._instanceSrv.getFocusedUnit(); if ( !focusedUnit || - this._docFormulaPopupService.popupInfo?.type === 'new' || - this._docFormulaPopupService.popupLocked + this._formulaPopupSrv.popupInfo?.type === 'new' || + this._formulaPopupSrv.popupLocked ) { return; } @@ -116,7 +109,7 @@ export class DocUniFormulaController extends Disposable { this._showPopup({ startIndex, unitId: focusedUnit.getUnitId(), - rangeId, + position: { rangeId }, type: 'existing', }); } else { @@ -126,7 +119,7 @@ export class DocUniFormulaController extends Disposable { } })); - this.disposeWithMe(this._docFormulaPopupService.popupHovered$.subscribe((hovered) => { + this.disposeWithMe(this._formulaPopupSrv.popupHovered$.subscribe((hovered) => { if (hovered) { this._removeTimer(); } @@ -151,7 +144,7 @@ export class DocUniFormulaController extends Disposable { private _closePopupTimer: number | null = null; private _closePopup(timeout: number = 0): void { - if (!this._docFormulaPopupService.popupInfo) { + if (!this._formulaPopupSrv.popupInfo) { return; } diff --git a/packages-experimental/uni-formula-ui/src/controllers/slide-formula-input.controller.ts b/packages-experimental/uni-formula-ui/src/controllers/slide-formula-input.controller.ts new file mode 100644 index 00000000000..53c98560791 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/controllers/slide-formula-input.controller.ts @@ -0,0 +1,115 @@ +/** + * 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 { Disposable, ICommandService, Inject, Injector, IUniverInstanceService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import { IEditorService } from '@univerjs/ui'; +import type { IInsertCommandParams } from '@univerjs/docs'; +import { InsertCommand, TextSelectionManagerService } from '@univerjs/docs'; +import { ISlideEditorBridgeService } from '@univerjs/slides-ui'; +import { AddSlideUniFormulaCommand } from '../commands/commands/slide.command'; +import { UNI_FORMULA_EDITOR_ID } from '../views/components/DocFormulaPopup'; +import { UniFormulaPopupService } from '../services/formula-popup.service'; +import type { IShowFormulaPopupOperationParams, ISlidePopupPosition } from '../commands/operations/operation'; +import { CloseFormulaPopupOperation, ShowFormulaPopupOperation } from '../commands/operations/operation'; + +const FORMULA_INPUT_TRIGGER_CHAR = '='; + +@OnLifecycle(LifecycleStages.Steady, SlideUniFormulaInputController) +export class SlideUniFormulaInputController extends Disposable { + constructor( + @Inject(Injector) private readonly _injector: Injector, + @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, + @ICommandService private readonly _commandSrv: ICommandService, + @IEditorService private readonly _editorSrv: IEditorService, + @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, + @Inject(UniFormulaPopupService) private readonly _formulaPopupSrv: UniFormulaPopupService + ) { + super(); + + this._initCommands(); + this._initKeyboardListeners(); + } + + private _initCommands() { + [ + AddSlideUniFormulaCommand, + ].forEach((cmd) => this._commandSrv.registerCommand(cmd)); + } + + private _initKeyboardListeners(): void { + this.disposeWithMe(this._commandSrv.onCommandExecuted((commandInfo) => { + const currentEditor = this._editorSrv.getFocusEditor(); + const focusedUnit = this._instanceSrv.getFocusedUnit(); + + const { id } = commandInfo; + + if ( + currentEditor?.editorUnitId === UNI_FORMULA_EDITOR_ID || + focusedUnit?.type !== UniverInstanceType.UNIVER_SLIDE + ) { + return; + } + + if (id === InsertCommand.id) { + const params = commandInfo.params as IInsertCommandParams; + const activeRange = this._textSelectionManagerService.getActiveTextRange(); + if (params.body.dataStream === FORMULA_INPUT_TRIGGER_CHAR && activeRange) { + // NOTE: we can avoid manually get the SlideEditorBridgeService when we split + // slide formula editor plugin into a separate package. + const editorBridgeService = this._injector.get(ISlideEditorBridgeService); + const editorRect = editorBridgeService.getEditorRect(); + const { pageId, richTextObj } = editorRect; + const { oKey } = richTextObj; + this._showPopup({ + startIndex: activeRange.startOffset! - 1, + unitId: focusedUnit.getUnitId(), + position: { + pageId, + elementId: oKey, + } as ISlidePopupPosition, + }); + } else if (this._formulaPopupSrv.popupInfo) { + this._closePopup(); + } + } + })); + } + + private _removeTimer(): void { + if (this._closePopupTimer !== null) { + window.clearTimeout(this._closePopupTimer); + this._closePopupTimer = null; + } + } + + private _showPopup(params: IShowFormulaPopupOperationParams): void { + this._removeTimer(); + this._commandSrv.executeCommand(ShowFormulaPopupOperation.id, params); + } + + private _closePopupTimer: number | null = null; + private _closePopup(timeout: number = 0): void { + if (!this._formulaPopupSrv.popupInfo) { + return; + } + + if (timeout === 0) { + this._commandSrv.executeCommand(CloseFormulaPopupOperation.id); + } else { + this._closePopupTimer = window.setTimeout(() => this._closePopup(0), timeout); + } + } +} diff --git a/packages-experimental/uni-formula-ui/src/controllers/uni-formula-ui.controller.ts b/packages-experimental/uni-formula-ui/src/controllers/uni-formula-ui.controller.ts new file mode 100644 index 00000000000..e4534887235 --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/controllers/uni-formula-ui.controller.ts @@ -0,0 +1,38 @@ +/** + * 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 { Disposable, ICommandService, Inject, LifecycleStages, OnLifecycle } from '@univerjs/core'; +import { ComponentManager } from '@univerjs/ui'; +import { UniFormulaPopup } from '../views/components/DocFormulaPopup'; +import { CloseFormulaPopupOperation, ConfirmFormulaPopupCommand, ShowFormulaPopupOperation } from '../commands/operations/operation'; + +@OnLifecycle(LifecycleStages.Steady, UniFormulaUniController) +export class UniFormulaUniController extends Disposable { + constructor( + @ICommandService private readonly _commandSrv: ICommandService, + @Inject(ComponentManager) private readonly _componentManager: ComponentManager + ) { + super(); + + [ + ShowFormulaPopupOperation, + CloseFormulaPopupOperation, + ConfirmFormulaPopupCommand, + ].forEach((command) => this._commandSrv.registerCommand(command)); + + this.disposeWithMe(this._componentManager.register(UniFormulaPopup.componentKey, UniFormulaPopup)); + } +} diff --git a/packages-experimental/uni-formula-ui/src/locale/en-US.ts b/packages-experimental/uni-formula-ui/src/locale/en-US.ts index 3f0e266a592..ef449432bec 100644 --- a/packages-experimental/uni-formula-ui/src/locale/en-US.ts +++ b/packages-experimental/uni-formula-ui/src/locale/en-US.ts @@ -30,7 +30,7 @@ const locale: typeof zhCN = { }, }, command: { - 'stream-placeholder': 'Doc Formula', + 'stream-placeholder': 'Uni Formula', }, }, }; diff --git a/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts b/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts index 6856c0ffe1b..aa610228dda 100644 --- a/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts +++ b/packages-experimental/uni-formula-ui/src/locale/ru-RU.ts @@ -30,7 +30,7 @@ const locale: typeof zhCN = { }, }, command: { - 'stream-placeholder': 'Doc Formula', + 'stream-placeholder': 'Uni Formula', }, }, }; diff --git a/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts b/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts index 6856c0ffe1b..aa610228dda 100644 --- a/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts +++ b/packages-experimental/uni-formula-ui/src/locale/vi-VN.ts @@ -30,7 +30,7 @@ const locale: typeof zhCN = { }, }, command: { - 'stream-placeholder': 'Doc Formula', + 'stream-placeholder': 'Uni Formula', }, }, }; diff --git a/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts b/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts index 20e223fab76..994267d8190 100644 --- a/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts +++ b/packages-experimental/uni-formula-ui/src/locale/zh-CN.ts @@ -28,7 +28,7 @@ const locale = { }, }, command: { - 'stream-placeholder': '文档公式', + 'stream-placeholder': '无界公式', }, }, }; diff --git a/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts b/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts index 6856c0ffe1b..aa610228dda 100644 --- a/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts +++ b/packages-experimental/uni-formula-ui/src/locale/zh-TW.ts @@ -30,7 +30,7 @@ const locale: typeof zhCN = { }, }, command: { - 'stream-placeholder': 'Doc Formula', + 'stream-placeholder': 'Uni Formula', }, }, }; diff --git a/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts b/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts index 2bf76393291..7ab1d5e7698 100644 --- a/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts +++ b/packages-experimental/uni-formula-ui/src/services/formula-popup.service.ts @@ -23,29 +23,28 @@ import type { IShortcutItem } from '@univerjs/ui'; import { IShortcutService, KeyCode } from '@univerjs/ui'; import { FORMULA_PROMPT_ACTIVATED } from '@univerjs/sheets-formula'; import { IUniFormulaService } from '@univerjs/uni-formula'; -import type { IAddDocUniFormulaCommandParams } from '../commands/command'; -import { AddDocUniFormulaCommand } from '../commands/command'; -import { ConfirmFormulaPopupCommand } from '../commands/operation'; +import type { IAddDocUniFormulaCommandParams } from '../commands/commands/doc.command'; +import { AddDocUniFormulaCommand } from '../commands/commands/doc.command'; +import type { IPopupPosition } from '../commands/operations/operation'; +import { ConfirmFormulaPopupCommand, isSlidePosition } from '../commands/operations/operation'; +import type { IAddSlideUniFormulaCommandParams } from '../commands/commands/slide.command'; +import { AddSlideUniFormulaCommand } from '../commands/commands/slide.command'; export const DOC_FORMULA_POPUP_KEY = 'DOC_FORMULA_POPUP' as const; -export interface IDocFormulaPopupInfo { +export interface IUniFormulaPopupInfo { unitId: string; - - /** If the popup is for inserting a formula or inspecting an existing formula. */ type: 'new' | 'existing'; - f: Nullable; - disposable: IDisposable; - startIndex: number; + position?: IPopupPosition; } -export class DocFormulaPopupService extends Disposable { - private readonly _popupInfo$ = new BehaviorSubject>(null); +export class UniFormulaPopupService extends Disposable { + private readonly _popupInfo$ = new BehaviorSubject>(null); readonly popupInfo$ = this._popupInfo$.asObservable(); - get popupInfo(): Nullable { return this._popupInfo$.getValue(); } + get popupInfo(): Nullable { return this._popupInfo$.getValue(); } private _popupLocked = false; get popupLocked(): boolean { return this._popupLocked; } @@ -94,22 +93,24 @@ export class DocFormulaPopupService extends Disposable { this._popupHovered$.next(hovered); } - showPopup(unitId: string, startIndex: number, type: 'new'): boolean; - showPopup(unitId: string, startIndex: number, type: 'existing', rangeId: string): boolean; - showPopup(unitId: string, startIndex: number, type: 'new' | 'existing', rangeId?: string): boolean; - showPopup(unitId: string, startIndex: number, type: 'new' | 'existing', rangeId?: string): boolean { + showDocPopup(unitId: string, startIndex: number, type: 'new'): boolean; + showDocPopup(unitId: string, startIndex: number, type: 'existing', position: IPopupPosition): boolean; + showDocPopup(unitId: string, startIndex: number, type: 'new' | 'existing', position?: IPopupPosition): boolean; + showDocPopup(unitId: string, startIndex: number, type: 'new' | 'existing', position?: IPopupPosition): boolean { this.closePopup(); - const f = (rangeId && type === 'existing') - ? this._uniFormulaService.getFormulaWithRangeId(unitId, rangeId)?.f ?? '=' + // Open existing doc formula. + const f = (position && position.rangeId && type === 'existing') + ? this._uniFormulaService.getDocFormula(unitId, position.rangeId)?.f ?? '=' : '='; + const disposable = this._docCanvasPopupManagerService.attachPopupToRange(makeSelection(startIndex), { componentKey: DOC_FORMULA_POPUP_KEY, onClickOutside: () => this.closePopup(), // user may update ref range selections direction: 'top', }); - this._popupInfo$.next({ unitId, disposable, type, f, startIndex }); + this._popupInfo$.next({ unitId, disposable, type, f, startIndex, position }); return true; } @@ -135,7 +136,18 @@ export class DocFormulaPopupService extends Disposable { this.unlockPopup(); this.closePopup(); - // write this formula string to doc + // Write to slide. + if (isSlidePosition(info.position)) { + return this._commandService.executeCommand(AddSlideUniFormulaCommand.id, { + unitId: info.unitId, + f, + startIndex: info.startIndex, + pageId: info.position.pageId, + elementId: info.position.elementId, + }); + } + + // Write to doc. return this._commandService.executeCommand(AddDocUniFormulaCommand.id, { unitId: info.unitId, f, diff --git a/packages-experimental/uni-formula-ui/src/services/slide-ui-formula-cache.service.ts b/packages-experimental/uni-formula-ui/src/services/slide-ui-formula-cache.service.ts new file mode 100644 index 00000000000..7c182763c5e --- /dev/null +++ b/packages-experimental/uni-formula-ui/src/services/slide-ui-formula-cache.service.ts @@ -0,0 +1,76 @@ +/** + * 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 { CustomRangeType, Disposable } from '@univerjs/core'; +import { IUniFormulaService } from '@univerjs/uni-formula'; +import { ISlideEditorBridgeService } from '@univerjs/slides-ui'; +import type { RichText } from '@univerjs/engine-render'; +import type { IAddSlideUniFormulaCommandParams } from '../commands/commands/slide.command'; +import type { UniFormulaService } from './uni-formula.service'; + +export class SlideUIFormulaCacheService extends Disposable { + private readonly _caches: Map = new Map(); + + constructor( + @ISlideEditorBridgeService private readonly _editorBridgeService: ISlideEditorBridgeService, + @IUniFormulaService private readonly _uniFormulaService: UniFormulaService + ) { + super(); + + this._editorBridgeService.endEditing$.subscribe((richText) => this._checkApplyCache(richText)); + } + + writeCache(rangeId: string, params: IAddSlideUniFormulaCommandParams) { + if (this._caches.size && this._caches.values().next().value.unitId !== params.unitId) { + this.clearCache(); + } + + this._caches.set(rangeId, params); + } + + private _checkApplyCache(richText: RichText) { + const document = richText.documentData; + const customRanges = document.body?.customRanges; + if (!customRanges || customRanges.length === 0) { + this.clearCache(); + return; + }; + + // Check if there are custom ranges in the rich text. If there are, we would write apply the cache + // to the uni formula service + customRanges.forEach((range) => { + if (range.rangeType === CustomRangeType.UNI_FORMULA) { + const cache = this._caches.get(range.rangeId); + if (cache) { + this._applyCache(range.rangeId, cache); + } else { + throw new Error('[SlideUIFormulaCacheService]: cache not found!'); + } + } + }); + + this.clearCache(); + } + + private _applyCache(rangeId: string, cache: IAddSlideUniFormulaCommandParams) { + const { unitId, pageId, elementId, f } = cache; + this._uniFormulaService.registerSlideFormula(unitId, pageId, elementId, rangeId, f); + } + + clearCache() { + this._caches.clear(); + } +} diff --git a/packages-experimental/uni-formula-ui/src/services/uni-formula.service.ts b/packages-experimental/uni-formula-ui/src/services/uni-formula.service.ts index 6c118314b7d..5df42c451db 100644 --- a/packages-experimental/uni-formula-ui/src/services/uni-formula.service.ts +++ b/packages-experimental/uni-formula-ui/src/services/uni-formula.service.ts @@ -21,7 +21,7 @@ import type { IDisposable, IDocumentBody, Nullable, -} from '@univerjs/core'; + SlideDataModel } from '@univerjs/core'; import { CommandType, CustomRangeType, @@ -39,23 +39,89 @@ import { import { makeSelection, replaceSelectionFactory } from '@univerjs/docs'; import { DataSyncPrimaryController } from '@univerjs/rpc'; import { RegisterOtherFormulaService } from '@univerjs/sheets-formula'; -import type { IDocFormulaCache } from '@univerjs/uni-formula'; +import { CanvasView } from '@univerjs/slides'; +import type { IDocFormulaCache, ISlideFormulaCache } from '@univerjs/uni-formula'; import { DumbUniFormulaService, IUniFormulaService } from '@univerjs/uni-formula'; import { take } from 'rxjs'; +import { RichText } from '@univerjs/engine-render'; +import { type IDocPopupPosition, type ISlidePopupPosition, isSlidePosition } from '../commands/operations/operation'; + +const PSEUDO_SUBUNIT = 'PSEUDO_SUBUNIT'; + +export interface IUpdateSlideUniFormulaCacheCommandParams { + unitId: string; + positions: ISlidePopupPosition[]; + cache: ISlideFormulaCache[]; +} -const DOC_PSEUDO_SUBUNIT = 'DOC_PSEUDO_SUBUNIT'; -/** - * Update calculating result in a batch. - */ export interface IUpdateDocUniFormulaCacheCommandParams { /** The doc in which formula results changed. */ unitId: string; - /** Range ids. */ - ids: string[]; + positions: ISlidePopupPosition[] | IDocPopupPosition[]; /** Calculation results. */ cache: IDocFormulaCache[]; } +export const UpdateSlideUniFormulaCacheCommand: ICommand = { + type: CommandType.COMMAND, + id: 'uni-formula.mutation.update-slide-uni-formula-cache', + handler(accessor, params: IUpdateSlideUniFormulaCacheCommandParams) { + const { unitId, positions, cache } = params; + + const uniFormulaService = accessor.get(IUniFormulaService); + const instanceService = accessor.get(IUniverInstanceService); + const slideCanvasView = accessor.get(CanvasView); + + const slide = instanceService.getUnit(unitId, UniverInstanceType.UNIVER_SLIDE); + if (!slide) return true; + + return positions.every((position, index) => { + // TODO@wzhudev: we should get the slide's rendering modules to update the formula results. + // Note that this is very hacky because Univer Slide hasn't provide any mutations to + // modify its content. This is just for POC of uni formula. + const scene = slideCanvasView.getRenderUnitByPageId(position.pageId).scene; + if (!scene) return false; + + const element = scene.getObject(position.elementId); + if (!element || !(element instanceof RichText)) return false; + + const documentModel = element.documentModel; + const originBody = documentModel.getBody()!; + const range = originBody.customRanges?.find((r) => r.rangeId === position.rangeId); + if (!range) return false; + + const dataStream = makeCustomRangeStream(`${cache[index].v ?? ''}`); + const body: IDocumentBody = { + dataStream, + customRanges: [{ + startIndex: 0, + endIndex: dataStream.length - 1, + rangeId: position.rangeId!, + rangeType: CustomRangeType.UNI_FORMULA, + wholeEntity: true, + }], + }; + + const redoMutation = replaceSelectionFactory(accessor, { + unitId, + originBody, + body, + selection: makeSelection(range.startIndex, range.endIndex), + }); + + if (!redoMutation) return false; + + // This is pretty annoying... + element.documentModel.apply(redoMutation.params.actions); + element.refreshDocumentByDocData(); // trigger re-render + + uniFormulaService.updateSlideFormulaResults(unitId, position.pageId, position.elementId, position.rangeId!, cache[index]); + + return true; + }); + }, +}; + /** * This command is internal. It should not be exposed to third-party developers. * @@ -63,25 +129,26 @@ export interface IUpdateDocUniFormulaCacheCommandParams { */ export const UpdateDocUniFormulaCacheCommand: ICommand = { type: CommandType.COMMAND, - id: 'doc.mutation.update-doc-uni-formula-cache', + id: 'uni-formula.mutation.update-doc-uni-formula-cache', handler(accessor, params: IUpdateDocUniFormulaCacheCommandParams) { - const { unitId, ids, cache } = params; + const { unitId, positions, cache } = params; const uniFormulaService = accessor.get(IUniFormulaService); const instanceService = accessor.get(IUniverInstanceService); const commandService = accessor.get(ICommandService); - const data = instanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); + const doc = instanceService.getUnit(unitId, UniverInstanceType.UNIVER_DOC); + // The document may have not loaded on this client. We are safe to ignore cache updating. + if (!doc) return true; - /** The document may have not loaded on this client. We are safe to ignore cache updating. */ - if (!data) return true; - const body = data.getBody(); + const body = doc.getBody(); function getRange(rangeId: string) { return body?.customRanges?.find((r) => r.rangeId === rangeId); } - const saveCacheResult = uniFormulaService.updateFormulaResults(unitId, ids, cache); + const ids = positions.map((position) => position.rangeId!); + const saveCacheResult = uniFormulaService.updateDocFormulaResults(unitId, ids, cache); if (!saveCacheResult) return false; return ids.every((id, index) => { @@ -136,7 +203,10 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm ) { super(resourceManagerService, commandSrv, instanceSrv); - commandSrv.registerCommand(UpdateDocUniFormulaCacheCommand); + [ + UpdateSlideUniFormulaCacheCommand, + UpdateDocUniFormulaCacheCommand, + ].forEach((command) => commandSrv.registerCommand(command)); // Only able to perform formula calculation after a sheet is loaded. // FIXME: the formula engine is not unit-agnostic. @@ -164,7 +234,7 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm const pseudoId = getPseudoUnitKey(unitId); this._checkSyncingUnit(pseudoId); - const id = this._registerOtherFormulaSrv.registerFormula(pseudoId, DOC_PSEUDO_SUBUNIT, f); + const id = this._registerOtherFormulaSrv.registerFormula(pseudoId, PSEUDO_SUBUNIT, f); this._docFormulas.set(key, { unitId, rangeId, f, formulaId: id, v, t }); this._formulaIdToKey.set(id, key); @@ -176,6 +246,36 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm return toDisposable(() => this.unregisterDocFormula(unitId, rangeId)); } + override registerSlideFormula( + unitId: string, + pageId: string, + elementId: string, + rangeId: string, + f: string, + v: ICellData['v'], + t: ICellData['t'] + ): IDisposable { + const key = getSlideFormulaKey(unitId, pageId, elementId, rangeId); + if (this._slideFormulas.has(key)) { + throw new Error(`[UniFormulaService]: cannot register formula ${key} when it is already registered!`); + } + + if (this._canPerformFormulaCalculation) { + const pseudoId = getPseudoUnitKey(unitId); + this._checkSyncingUnit(pseudoId); + + const id = this._registerOtherFormulaSrv.registerFormula(pseudoId, PSEUDO_SUBUNIT, f); + this._slideFormulas.set(key, { unitId, pageId, elementId, rangeId, f, formulaId: id, v, t }); + this._formulaIdToKey.set(id, key); + + this._checkResultSubscription(); + } else { + this._slideFormulas.set(key, { unitId, pageId, elementId, rangeId, f, formulaId: '', v, t }); + } + + return toDisposable(() => this.unregisterSlideFormula(unitId, pageId, elementId, rangeId)); + } + override unregisterDocFormula(unitId: string, rangeId: string): void { const key = getDocFormulaKey(unitId, rangeId); const item = this._docFormulas.get(key); @@ -186,13 +286,30 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm this._dataSyncDisposables.get(pseudoId)?.dec(); if (this._canPerformFormulaCalculation) { - this._registerOtherFormulaSrv.deleteFormula(pseudoId, DOC_PSEUDO_SUBUNIT, [item.formulaId]); + this._registerOtherFormulaSrv.deleteFormula(pseudoId, PSEUDO_SUBUNIT, [item.formulaId]); this._formulaIdToKey.delete(item.formulaId); } this._docFormulas.delete(key); } + override unregisterSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): void { + const key = getSlideFormulaKey(unitId, pageId, elementId, formulaId); + const item = this._slideFormulas.get(key); + if (!item) return; + + const pseudoId = getPseudoUnitKey(unitId); + this._checkDisposingResultSubscription(); + this._dataSyncDisposables.get(pseudoId)?.dec(); + + if (this._canPerformFormulaCalculation) { + this._registerOtherFormulaSrv.deleteFormula(pseudoId, PSEUDO_SUBUNIT, [item.formulaId]); + this._formulaIdToKey.delete(item.formulaId); + } + + this._slideFormulas.delete(key); + } + private _initFormulaRegistration(): void { // When the doc bootstraps, there could be no sheets modules loaded. So we need to check if there // are registered formulas but not added to the formula system. @@ -202,7 +319,7 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm const pseudoId = getPseudoUnitKey(unitId); this._checkSyncingUnit(pseudoId); - const id = this._registerOtherFormulaSrv.registerFormula(pseudoId, DOC_PSEUDO_SUBUNIT, f); + const id = this._registerOtherFormulaSrv.registerFormula(pseudoId, PSEUDO_SUBUNIT, f); value.formulaId = id; this._formulaIdToKey.set(id, key); } @@ -227,37 +344,59 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm this._resultSubscription = toDisposable(this._registerOtherFormulaSrv.formulaResult$.subscribe((resultMap) => { for (const resultOfUnit in resultMap) { - const results = resultMap[resultOfUnit][DOC_PSEUDO_SUBUNIT]; + const results = resultMap[resultOfUnit][PSEUDO_SUBUNIT]; if (results) { const mutationParam = results.map((result) => { const formulaId = result.formulaId; const key = this._formulaIdToKey.get(formulaId); if (!key) return null; - const item = this._docFormulas.get(key); - if (!item) return null; - - const r = result.result?.[0][0]; - if (item.v === r?.v && item.t === r?.t) return null; - - return { id: item.rangeId, unitId: item.unitId, cache: r }; + const docItem = this._docFormulas.get(key); + if (docItem) { + const r = result.result?.[0][0]; + if (docItem.v === r?.v && docItem.t === r?.t) return null; + + return { position: { rangeId: docItem.rangeId }, unitId: docItem.unitId, cache: r }; + }; + + const slideItem = this._slideFormulas.get(key); + if (slideItem) { + const r = result.result?.[0][0]; + if (slideItem.v === r?.v && slideItem.t === r?.t) return null; + + return { + unitId: slideItem.unitId, + position: { + elementId: slideItem.elementId, + rangeId: slideItem.rangeId, + pageId: slideItem.pageId, + }, + cache: r, + }; + } + + return null; }).reduce((previous, curr) => { if (!curr || !curr.cache) return previous; if (!previous.unitId) previous.unitId = curr.unitId; - previous.ids.push(curr.id); + previous.positions.push(curr.position); previous.cache.push(curr.cache); return previous; }, { unitId: '', - ids: [] as string[], + positions: [] as (ISlidePopupPosition | IDocPopupPosition)[], cache: [] as Pick[], }); - if (mutationParam.ids.length === 0) return; + if (mutationParam.positions.length === 0) return; - this._commandSrv.executeCommand(UpdateDocUniFormulaCacheCommand.id, mutationParam as IUpdateDocUniFormulaCacheCommandParams); + if (isSlidePosition(mutationParam.positions[0])) { + this._commandSrv.executeCommand(UpdateSlideUniFormulaCacheCommand.id, mutationParam as IUpdateSlideUniFormulaCacheCommandParams); + } else { + this._commandSrv.executeCommand(UpdateDocUniFormulaCacheCommand.id, mutationParam as IUpdateDocUniFormulaCacheCommandParams); + } } } })); @@ -273,12 +412,6 @@ export class UniFormulaService extends DumbUniFormulaService implements IUniForm this._resultSubscription = null; } } - - private _checkFormulaUsable(): void { - if (!this._canPerformFormulaCalculation && this._instanceSrv.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET).length) { - this._canPerformFormulaCalculation = true; - } - } } function getPseudoUnitKey(unitId: string): string { @@ -288,3 +421,7 @@ function getPseudoUnitKey(unitId: string): string { function getDocFormulaKey(unitId: string, formulaId: string): string { return `pseudo-${unitId}-${formulaId}`; } + +function getSlideFormulaKey(unitId: string, pageId: string, elementId: string, rangeId: string): string { + return `pseudo-${unitId}-${pageId}-${elementId}-${rangeId}`; +} diff --git a/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts b/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts index 9ed4172d839..882fbc2a66e 100644 --- a/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts +++ b/packages-experimental/uni-formula-ui/src/uni-formula-ui.plugin.ts @@ -19,9 +19,12 @@ import { DependentOn, Inject, Injector, Plugin, UniverInstanceType } from '@univ import { IUniFormulaService, UniverDocUniFormulaPlugin } from '@univerjs/uni-formula'; import { DOC_FORMULA_UI_PLUGIN_NAME } from './const'; -import { DocFormulaPopupService } from './services/formula-popup.service'; -import { DocUniFormulaController } from './controllers/formula-input.controller'; +import { UniFormulaPopupService } from './services/formula-popup.service'; +import { DocUniFormulaInputController } from './controllers/doc-formula-input.controller'; import { UniFormulaService } from './services/uni-formula.service'; +import { UniFormulaUniController } from './controllers/uni-formula-ui.controller'; +import { SlideUniFormulaInputController } from './controllers/slide-formula-input.controller'; +import { SlideUIFormulaCacheService } from './services/slide-ui-formula-cache.service'; @DependentOn(UniverDocUniFormulaPlugin) export class UniverDocUniFormulaUIPlugin extends Plugin { @@ -37,8 +40,11 @@ export class UniverDocUniFormulaUIPlugin extends Plugin { override onStarting(injector: Injector): void { ([ - [DocUniFormulaController], - [DocFormulaPopupService], + [UniFormulaUniController], + [DocUniFormulaInputController], + [SlideUniFormulaInputController], + [SlideUIFormulaCacheService], + [UniFormulaPopupService], [IUniFormulaService, { useClass: UniFormulaService }], ] as Dependency[]).forEach((d) => injector.add(d)); } diff --git a/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx b/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx index 89ade0d894c..b8047f0c1fa 100644 --- a/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx +++ b/packages-experimental/uni-formula-ui/src/views/components/DocFormulaPopup.tsx @@ -20,16 +20,17 @@ import { TextEditor, useObservable } from '@univerjs/ui'; import type { IDocumentData, Nullable } from '@univerjs/core'; import { BooleanNumber, createInternalEditorID, DEFAULT_EMPTY_DOCUMENT_VALUE, DocumentFlavor, HorizontalAlign, ICommandService, LocaleService, useDependency, VerticalAlign, WrapStrategy } from '@univerjs/core'; import { CheckMarkSingle, CloseSingle } from '@univerjs/icons'; -import type { IDocFormulaPopupInfo } from '../../services/formula-popup.service'; -import { DOC_FORMULA_POPUP_KEY, DocFormulaPopupService } from '../../services/formula-popup.service'; +import type { IUniFormulaPopupInfo } from '../../services/formula-popup.service'; +import { DOC_FORMULA_POPUP_KEY, UniFormulaPopupService } from '../../services/formula-popup.service'; -import { CloseFormulaPopupOperation, ConfirmFormulaPopupCommand } from '../../commands/operation'; +import { CloseFormulaPopupOperation, ConfirmFormulaPopupCommand } from '../../commands/operations/operation'; import styles from './index.module.less'; -export const DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY = createInternalEditorID('UNI_FORMULA'); +export const UNI_FORMULA_EDITOR_ID = createInternalEditorID('UNI_FORMULA'); + function makeSnapshot(f: string): IDocumentData { return { - id: DOCS_UNI_FORMULA_EDITOR_UNIT_ID_KEY, + id: UNI_FORMULA_EDITOR_ID, body: { dataStream: `${f}${DEFAULT_EMPTY_DOCUMENT_VALUE}`, textRuns: [], @@ -62,8 +63,8 @@ function makeSnapshot(f: string): IDocumentData { }; } -export function DocFormulaPopup() { - const docFormulaPopupService = useDependency(DocFormulaPopupService); +export function UniFormulaPopup() { + const docFormulaPopupService = useDependency(UniFormulaPopupService); const popupInfo = useObservable(docFormulaPopupService.popupInfo$); if (!popupInfo) { @@ -73,14 +74,14 @@ export function DocFormulaPopup() { return ; } -DocFormulaPopup.componentKey = DOC_FORMULA_POPUP_KEY; +UniFormulaPopup.componentKey = DOC_FORMULA_POPUP_KEY; -function DocFormula(props: { popupInfo: IDocFormulaPopupInfo }) { +function DocFormula(props: { popupInfo: IUniFormulaPopupInfo }) { const { popupInfo } = props; const { f } = popupInfo; const localeService = useDependency(LocaleService); - const formulaPopupService = useDependency(DocFormulaPopupService); + const formulaPopupService = useDependency(UniFormulaPopupService); const commandService = useDependency(ICommandService); const [formulaString, setFormulaString] = useState>(f); @@ -122,7 +123,7 @@ function DocFormula(props: { popupInfo: IDocFormulaPopupInfo }) { return (
onHovered(true)} onMouseLeave={() => onHovered(false)}> = { @@ -41,8 +39,7 @@ export const AddDocUniFormulaMutation: IMutation = { type: CommandType.MUTATION, @@ -51,7 +48,7 @@ export const UpdateDocUniFormulaMutation: IMutation = { + type: CommandType.MUTATION, + id: 'slide.mutation.add-slide-uni-formula', + handler(accessor, params: IAddSlideUniFormulaMutationParams) { + const { unitId, pageId, elementId, f, rangeId: id } = params; + const uniFormulaService = accessor.get(IUniFormulaService); + + uniFormulaService.registerSlideFormula(unitId, pageId, elementId, id, f); + + return true; + }, +}; + +export interface IUpdateSlideUniFormulaMutationParams extends IAddSlideUniFormulaMutationParams {} + +export const UpdateSlideUniFormulaMutation: IMutation = { + type: CommandType.MUTATION, + id: 'slide.mutation.update-slide-uni-formula', + handler(accessor, params: IUpdateSlideUniFormulaMutationParams) { + const { unitId, pageId, elementId, f, rangeId: id } = params; + const uniFormulaService = accessor.get(IUniFormulaService); + + if (!uniFormulaService.hasSlideFormula(unitId, pageId, elementId, id)) return false; + + uniFormulaService.unregisterSlideFormula(unitId, pageId, elementId, id); + uniFormulaService.registerSlideFormula(unitId, pageId, elementId, id, f); + return true; + }, +}; diff --git a/packages-experimental/uni-formula/src/const.ts b/packages-experimental/uni-formula/src/const.ts index f64cf70de99..656c6db4c57 100644 --- a/packages-experimental/uni-formula/src/const.ts +++ b/packages-experimental/uni-formula/src/const.ts @@ -14,4 +14,6 @@ * limitations under the License. */ -export const DOC_FORMULA_PLUGIN_NAME = 'DOC_FORMULA_PLUGIN'; +export const UNI_FORMULA_PLUGIN_NAME = 'UI_FORMULA_PLUGIN'; + +export const DOC_UNI_FORMULA_RESOURCE_NAME = 'DOC_FORMULA_PLUGIN'; diff --git a/packages-experimental/uni-formula/src/controller/uni-formula.controller.ts b/packages-experimental/uni-formula/src/controller/uni-formula.controller.ts index bf8d85bb137..73a07df2556 100644 --- a/packages-experimental/uni-formula/src/controller/uni-formula.controller.ts +++ b/packages-experimental/uni-formula/src/controller/uni-formula.controller.ts @@ -15,7 +15,7 @@ */ import { ICommandService } from '@univerjs/core'; -import { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniFormulaMutation } from '../commands/mutation'; +import { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniFormulaMutation } from '../commands/mutations/doc-formula.mutation'; export class UniFormulaController { constructor( diff --git a/packages-experimental/uni-formula/src/index.ts b/packages-experimental/uni-formula/src/index.ts index 544e9043e36..d121af161f5 100644 --- a/packages-experimental/uni-formula/src/index.ts +++ b/packages-experimental/uni-formula/src/index.ts @@ -16,8 +16,9 @@ export { UniverDocUniFormulaPlugin } from './uni-formula.plugin'; export { IUniFormulaService, DumbUniFormulaService } from './services/uni-formula.service'; -export { DOC_FORMULA_PLUGIN_NAME } from './const'; -export { type IDocFormulaCache, type IDocFormulaData, type IDocFormulaReference } from './models/doc-formula'; +export { UNI_FORMULA_PLUGIN_NAME as DOC_FORMULA_PLUGIN_NAME } from './const'; +export type { IDocFormulaCache, IDocFormulaData, IDocFormulaReference } from './models/doc-formula'; +export type { ISlideFormulaCache, ISlideFormulaData, ISlideFormulaReference } from './models/slide-formula'; // #region - all commands @@ -28,6 +29,6 @@ export { AddDocUniFormulaMutation, RemoveDocUniFormulaMutation, UpdateDocUniFormulaMutation, -} from './commands/mutation'; +} from './commands/mutations/doc-formula.mutation'; // #endregion diff --git a/packages-experimental/uni-formula/src/models/slide-formula.ts b/packages-experimental/uni-formula/src/models/slide-formula.ts new file mode 100644 index 00000000000..e3d9d9050b5 --- /dev/null +++ b/packages-experimental/uni-formula/src/models/slide-formula.ts @@ -0,0 +1,50 @@ +/** + * 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 } from '@univerjs/core'; + +export interface ISlideFormulaData { + rangeId: string; + + f: string; + + v?: ICellData['v']; + + t?: ICellData['t']; +} + +export interface ISlideFormulaReference extends ISlideFormulaData { + unitId: string; + + pageId: string; + + elementId: string; + + formulaId: string; +} + +export interface ISlideFormulaCache extends Pick { } + +export function slideToJson(formulas: ISlideFormulaReference[]): string { + return JSON.stringify(formulas.map((f) => ({ + rangeId: f.rangeId, + pageId: f.pageId, + elementId: f.elementId, + f: f.f, + v: f.v, + t: f.t, + }))); +} diff --git a/packages-experimental/uni-formula/src/services/uni-formula.service.ts b/packages-experimental/uni-formula/src/services/uni-formula.service.ts index 0c5e3b06618..71b5fcb3e79 100644 --- a/packages-experimental/uni-formula/src/services/uni-formula.service.ts +++ b/packages-experimental/uni-formula/src/services/uni-formula.service.ts @@ -26,46 +26,65 @@ import { } from '@univerjs/core'; import { type IDocFormulaCache, type IDocFormulaData, type IDocFormulaReference, toJson } from '../models/doc-formula'; -import { DOC_FORMULA_PLUGIN_NAME } from '../const'; +import { DOC_UNI_FORMULA_RESOURCE_NAME } from '../const'; +import type { ISlideFormulaReference } from '../models/slide-formula'; export interface IUniFormulaService { - getFormulaWithRangeId(unitId: string, rangeId: string): Nullable; - registerDocFormula(unitId: string, rangeId: string, f: string, v: ICellData['v'], t: ICellData['t']): IDisposable; + updateDocFormulaResults(unitId: string, formulaIds: string[], v: IDocFormulaCache[]): boolean; + updateSlideFormulaResults(unitId: string, pageId: string, elementId: string, formulaId: string, v: IDocFormulaCache): boolean; + + // #region doc + hasDocFormula(unitId: string, formulaId: string): boolean; + getDocFormula(unitId: string, rangeId: string): Nullable; + registerDocFormula(unitId: string, rangeId: string, f: string, v: ICellData['v'], t: ICellData['t']): IDisposable; unregisterDocFormula(unitId: string, rangeId: string): void; - hasFocFormula(unitId: string, formulaId: string): boolean; - updateFormulaResults(unitId: string, formulaIds: string[], v: IDocFormulaCache[]): boolean; + + // #endregion + + // #region slide + + hasSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): boolean; + getSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): Nullable; + registerSlideFormula(unitId: string, pageId: string, elementId: string, f: string, v: ICellData['v'], t: ICellData['t']): IDisposable; + unregisterSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): void; + + // #endregion } export const IUniFormulaService = createIdentifier('uni-formula.uni-formula.service'); +// NOTE@wzhudev: we implement formula for doc and slide here for convenience, but we should separate them in the future. + export class DumbUniFormulaService extends Disposable implements IUniFormulaService { - /** This data maps doc formula key to the formula id in the formula system. */ protected readonly _docFormulas = new Map(); + protected readonly _slideFormulas = new Map(); constructor( - @IResourceManagerService resourceManagerService: IResourceManagerService, + @IResourceManagerService resourceManagerSrv: IResourceManagerService, @ICommandService protected readonly _commandSrv: ICommandService, @IUniverInstanceService protected readonly _instanceSrv: IUniverInstanceService ) { super(); - this._initDocFormulaResources(resourceManagerService); + this._initDocFormulaResources(resourceManagerSrv); this._instanceSrv.getTypeOfUnitDisposed$(UniverInstanceType.UNIVER_DOC).subscribe((doc) => { this._unregisterDoc(doc.getUnitId()); }); } - hasFocFormula(unitId: string, formulaId: string): boolean { + // #region docs + + hasDocFormula(unitId: string, formulaId: string): boolean { return this._docFormulas.has(getDocFormulaKey(unitId, formulaId)); } - getFormulaWithRangeId(unitId: string, rangeId: string): Nullable { + getDocFormula(unitId: string, rangeId: string): Nullable { return this._docFormulas.get(getDocFormulaKey(unitId, rangeId)) ?? null; } - updateFormulaResults(unitId: string, formulaIds: string[], v: IDocFormulaCache[]): boolean { + updateDocFormulaResults(unitId: string, formulaIds: string[], v: IDocFormulaCache[]): boolean { formulaIds.forEach((id, index) => { const formulaData = this._docFormulas.get(getDocFormulaKey(unitId, id)); if (!formulaData) return true; @@ -78,16 +97,6 @@ export class DumbUniFormulaService extends Disposable implements IUniFormulaServ return true; } - /** - * Remove all formulas under a doc. - */ - private _unregisterDoc(unitId: string): void { - const existingFormulas = Array.from(this._docFormulas.entries()); - existingFormulas.forEach(([_, value]) => { - if (value.unitId === unitId) this.unregisterDocFormula(unitId, value.rangeId); - }); - } - /** * Register a doc formula into the formula system. */ @@ -110,9 +119,18 @@ export class DumbUniFormulaService extends Disposable implements IUniFormulaServ } } + updateSlideFormulaResults(unitId: string, pageId: string, elementId: string, formulaId: string, v: IDocFormulaCache): boolean { + const formulaData = this._slideFormulas.get(getSlideFormulaKey(unitId, pageId, elementId, formulaId)); + if (!formulaData) return true; + + formulaData.v = v.v; + formulaData.t = v.t; + return true; + } + private _initDocFormulaResources(resourceManagerService: IResourceManagerService): void { resourceManagerService.registerPluginResource({ - pluginName: DOC_FORMULA_PLUGIN_NAME, + pluginName: DOC_UNI_FORMULA_RESOURCE_NAME, businesses: [UniverInstanceType.UNIVER_DOC], toJson: (unitId: string) => { const formulas = this._getAllFormulasOfUnit(unitId); @@ -131,16 +149,72 @@ export class DumbUniFormulaService extends Disposable implements IUniFormulaServ }); } + /** + * Remove all formulas under a doc. + */ + private _unregisterDoc(unitId: string): void { + const existingFormulas = Array.from(this._docFormulas.entries()); + existingFormulas.forEach(([_, value]) => { + if (value.unitId === unitId) this.unregisterDocFormula(unitId, value.rangeId); + }); + } + + // #endregion + + // #region slides + + registerSlideFormula( + unitId: string, + pageId: string, + elementId: string, + rangeId: string, + f: string, + v: ICellData['v'], + t: ICellData['t'] + ): IDisposable { + const key = getSlideFormulaKey(unitId, pageId, elementId, f); + if (this._slideFormulas.has(key)) { + throw new Error(`[UniFormulaService]: cannot register formula ${key} when it is already registered!`); + } + + this._slideFormulas.set(key, { unitId, pageId, elementId, rangeId, formulaId: '', f, v, t }); + + return toDisposable(() => this.unregisterDocFormula(unitId, rangeId)); + } + + hasSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): boolean { + return this._slideFormulas.has(getSlideFormulaKey(unitId, pageId, elementId, formulaId)); + } + + getSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): Nullable { + return this._slideFormulas.get(getSlideFormulaKey(unitId, pageId, elementId, formulaId)) ?? null; + } + + unregisterSlideFormula(unitId: string, pageId: string, elementId: string, formulaId: string): void { + const key = getSlideFormulaKey(unitId, pageId, elementId, formulaId); + const item = this._slideFormulas.get(key); + if (item) { + this._slideFormulas.delete(key); + } + } + + // #endregion + private _getAllFormulasOfUnit(unitId: string) { const formulas = Array.from(this._docFormulas.entries()).filter((v) => v[1].unitId === unitId); return formulas; } } -export function getPseudoUnitKey(unitId: string): string { +export function getPseudoDocUnitKey(unitId: string): string { return `pseudo-${unitId}`; } export function getDocFormulaKey(unitId: string, formulaId: string): string { return `pseudo-${unitId}-${formulaId}`; } + +function getSlideFormulaKey(unitId: string, pageId: string, elementId: string, formulaId: string): string { + return `pseudo-${unitId}-${pageId}-${elementId}-${formulaId}`; +} + diff --git a/packages-experimental/uni-formula/src/uni-formula.plugin.ts b/packages-experimental/uni-formula/src/uni-formula.plugin.ts index f14cff93174..8e1e338f0ad 100644 --- a/packages-experimental/uni-formula/src/uni-formula.plugin.ts +++ b/packages-experimental/uni-formula/src/uni-formula.plugin.ts @@ -17,11 +17,11 @@ import type { Dependency } from '@univerjs/core'; import { Inject, Injector, Plugin, UniverInstanceType } from '@univerjs/core'; import { DumbUniFormulaService, IUniFormulaService } from './services/uni-formula.service'; -import { DOC_FORMULA_PLUGIN_NAME } from './const'; +import { UNI_FORMULA_PLUGIN_NAME } from './const'; import { UniFormulaController } from './controller/uni-formula.controller'; export class UniverDocUniFormulaPlugin extends Plugin { - static override pluginName: string = DOC_FORMULA_PLUGIN_NAME; + static override pluginName: string = UNI_FORMULA_PLUGIN_NAME; // This plugin should load only when sheet related modules are loaded. static override type: UniverInstanceType = UniverInstanceType.UNIVER_UNKNOWN; diff --git a/packages/docs/src/basics/replace.ts b/packages/docs/src/basics/replace.ts index 154b67ef431..d7bc2274e71 100644 --- a/packages/docs/src/basics/replace.ts +++ b/packages/docs/src/basics/replace.ts @@ -113,20 +113,29 @@ export function getRetainAndDeleteAndExcludeLineBreak( export interface IReplaceSelectionFactoryParams { unitId: string; selection?: ITextRange; + + originBody?: IDocumentBody; + + /** Body to be inserted at the given position. */ body: IDocumentBody; // Do not contain `\r\n` at the end. + textRanges?: ITextRangeWithStyle[]; } export function replaceSelectionFactory(accessor: IAccessor, params: IReplaceSelectionFactoryParams) { - const { unitId, body: insertBody } = params; + const { unitId, originBody, body: insertBody } = params; const univerInstanceService = accessor.get(IUniverInstanceService); - const docDataModel = univerInstanceService.getUnit(unitId); - const textSelectionManagerService = accessor.get(TextSelectionManagerService); - if (!docDataModel) { - return false; + + let body: IDocumentBody | undefined; + if (!params.originBody) { + const docDataModel = univerInstanceService.getUnit(unitId); + body = docDataModel?.getBody(); + } else { + body = originBody; } + if (!body) return false; - const body = docDataModel.getBody(); + const textSelectionManagerService = accessor.get(TextSelectionManagerService); const selection = params.selection ?? textSelectionManagerService.getActiveTextRangeWithStyle(); if (!selection || !body) { return false; diff --git a/packages/engine-render/src/shape/rich-text.ts b/packages/engine-render/src/shape/rich-text.ts index 0d99bd67e5e..7ae1b6cf743 100644 --- a/packages/engine-render/src/shape/rich-text.ts +++ b/packages/engine-render/src/shape/rich-text.ts @@ -59,6 +59,8 @@ export class RichText extends BaseObject { private _documents!: Documents; + documentModel!: DocumentDataModel; + /** * fontFamily */ @@ -145,7 +147,7 @@ export class RichText extends BaseObject { this._documentData = this._convertToDocumentData(props.text || ''); } - const docModel = new DocumentDataModel(this._documentData); + const docModel = this.documentModel = new DocumentDataModel(this._documentData); const docViewModel = new DocumentViewModel(docModel); this._documentSkeleton = DocumentSkeleton.create(docViewModel, this._localeService); @@ -356,7 +358,7 @@ export class RichText extends BaseObject { * now it is invoked when transformByState(change editor size) & end of editing */ refreshDocumentByDocData() { - const docModel = new DocumentDataModel(this._documentData); + const docModel = this.documentModel = new DocumentDataModel(this._documentData); const docViewModel = new DocumentViewModel(docModel); this._documentSkeleton = DocumentSkeleton.create(docViewModel, this._localeService); diff --git a/packages/slides-ui/src/controllers/slide-editing.render-controller.ts b/packages/slides-ui/src/controllers/slide-editing.render-controller.ts index f5029dddf9b..ac8b4106ad9 100644 --- a/packages/slides-ui/src/controllers/slide-editing.render-controller.ts +++ b/packages/slides-ui/src/controllers/slide-editing.render-controller.ts @@ -27,13 +27,10 @@ import { Direction, Disposable, DisposableCollection, - DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY, EDITOR_ACTIVATED, FOCUSING_EDITOR_BUT_HIDDEN, - FOCUSING_EDITOR_INPUT_FORMULA, FOCUSING_EDITOR_STANDALONE, FOCUSING_UNIVER_EDITOR_STANDALONE_SINGLE_MODE, - FORMULA_EDITOR_ACTIVATED, HorizontalAlign, ICommandService, IContextService, @@ -719,9 +716,17 @@ export class SlideEditingRenderController extends Disposable implements IRenderM const editedMutations = [RichTextEditingMutation.id]; d.add(this._commandService.onCommandExecuted((command: ICommandInfo) => { + // Only should do something when it is the current editor. + // FIXME: listen to command execution is pretty expensive. We should + // have multi editor instances and only handle event from a single editor. + if (this._editorService.getFocusId() !== this._renderContext.unitId) { + return; + } + if (moveCursorOP.includes(command.id)) { this._moveCursorCmdHandler(command); } + if (editedMutations.includes(command.id)) { if (this._editorBridgeService.isVisible()) { this._editingChangedHandler(); @@ -806,10 +811,7 @@ export class SlideEditingRenderController extends Disposable implements IRenderM } 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(FORMULA_EDITOR_ACTIVATED, false); this._cellEditorManagerService.setState({ show: param.visible, @@ -819,7 +821,6 @@ export class SlideEditingRenderController extends Disposable implements IRenderM return; } this._undoRedoService.clearUndoRedo(editorUnitId); - this._undoRedoService.clearUndoRedo(DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY); } private _moveCursor(keycode?: KeyCode) { diff --git a/packages/slides-ui/src/controllers/slide-editor-bridge.render-controller.ts b/packages/slides-ui/src/controllers/slide-editor-bridge.render-controller.ts index f3a3f10f4e1..e28f3c69eee 100644 --- a/packages/slides-ui/src/controllers/slide-editor-bridge.render-controller.ts +++ b/packages/slides-ui/src/controllers/slide-editor-bridge.render-controller.ts @@ -55,7 +55,7 @@ export class SlideEditorBridgeRenderController extends RxDisposable implements I @IContextService private readonly _contextService: IContextService, @IUniverInstanceService private readonly _instanceSrv: IUniverInstanceService, @ICommandService private readonly _commandService: ICommandService, - @Inject(ISlideEditorBridgeService) private readonly _editorBridgeService: ISlideEditorBridgeService, + @ISlideEditorBridgeService private readonly _editorBridgeService: ISlideEditorBridgeService, @Inject(TextSelectionManagerService) private readonly _textSelectionManagerService: TextSelectionManagerService, @ITextSelectionRenderManager private readonly _textSelectionRenderManager: ITextSelectionRenderManager, @Inject(CanvasView) private readonly _canvasView: CanvasView @@ -78,7 +78,7 @@ export class SlideEditorBridgeRenderController extends RxDisposable implements I // })); } - private _setEditorRect(targetObject: RichText) { + private _setEditorRect(pageId: string, targetObject: RichText) { this._curRichText = targetObject as RichText; const { scene, engine } = this._renderContext; const unitId = this._renderContext.unitId; @@ -87,7 +87,7 @@ export class SlideEditorBridgeRenderController extends RxDisposable implements I scene, engine, unitId, - pageId: '', + pageId, // FIXME: wtf this is an empty string? richTextObj: targetObject, }; @@ -133,7 +133,7 @@ export class SlideEditorBridgeRenderController extends RxDisposable implements I if (object.objectType !== ObjectType.RICH_TEXT) { this.pickOtherObjects(); } else { - this.startEditing(object as RichText); + this.startEditing(page.id, object as RichText); } })); @@ -161,6 +161,9 @@ export class SlideEditorBridgeRenderController extends RxDisposable implements I if (!slideData) return false; curRichText.refreshDocumentByDocData(); curRichText.resizeToContentSize(); + + this._editorBridgeService.endEditing$.next(curRichText); + this._curRichText = null; } @@ -171,10 +174,10 @@ export class SlideEditorBridgeRenderController extends RxDisposable implements I * TODO @lumixraku need scale param * @param target */ - startEditing(target: RichText) { + startEditing(pageId: string, target: RichText) { // this.setSlideTextEditor$.next({ content, rect }); - this._setEditorRect(target); + this._setEditorRect(pageId, target); this.setEditorVisible(true); } diff --git a/packages/slides-ui/src/index.ts b/packages/slides-ui/src/index.ts index bdd0a3331f7..154980ad128 100644 --- a/packages/slides-ui/src/index.ts +++ b/packages/slides-ui/src/index.ts @@ -18,6 +18,7 @@ export { UniverSlidesUIPlugin } from './slides-ui-plugin'; export { SlidesUIController } from './controllers/slide-ui.controller'; export { SlideSideBar } from './components/slide-bar/SlideBar'; +export { ISlideEditorBridgeService } from './services/slide-editor-bridge.service'; export { SlideCanvasPopMangerService } from './services/slide-popup-manager.service'; export type { IUniverSlidesDrawingConfig } from './controllers/slide-ui.controller'; @@ -34,3 +35,4 @@ export { SlideAddTextOperation } from './commands/operations/insert-text.operati // #endregion export { SlideEditorContainer } from './views/editor-container/EditorContainer'; +export { SLIDE_EDITOR_ID } from './const'; diff --git a/packages/slides-ui/src/services/slide-editor-bridge.service.ts b/packages/slides-ui/src/services/slide-editor-bridge.service.ts index aadfe657d3e..66e96c1ec2d 100644 --- a/packages/slides-ui/src/services/slide-editor-bridge.service.ts +++ b/packages/slides-ui/src/services/slide-editor-bridge.service.ts @@ -32,7 +32,7 @@ import { SLIDE_KEY } from '@univerjs/slides'; import type { KeyCode } from '@univerjs/ui'; import { IEditorService } from '@univerjs/ui'; import type { Observable } from 'rxjs'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { SLIDE_EDITOR_ID } from '../const'; // TODO same as @univerjs/slides/views/render/adaptors/index.js @@ -77,6 +77,13 @@ export interface ISetEditorInfo { export interface ISlideEditorBridgeService { currentEditRectState$: Observable>; visible$: Observable; + + /** + * @deprecated This is a temp solution only for demo purposes. We should have mutations to directly write + * content to slides. + */ + endEditing$: Subject; + // interceptor: InterceptorManager<{ // BEFORE_CELL_EDIT: typeof BEFORE_CELL_EDIT; // AFTER_CELL_EDIT: typeof AFTER_CELL_EDIT; @@ -119,8 +126,12 @@ export class SlideEditorBridgeService extends Disposable implements ISlideEditor private readonly _visible$ = new BehaviorSubject(this._visibleParam); readonly visible$ = this._visible$.asObservable(); + private readonly _afterVisible$ = new BehaviorSubject(this._visibleParam); readonly afterVisible$ = this._afterVisible$.asObservable(); + + readonly endEditing$ = new Subject(); + private _currentEditRectInfo: ISetEditorInfo; constructor( diff --git a/packages/slides-ui/src/services/slide-editor-manager.service.ts b/packages/slides-ui/src/services/slide-editor-manager.service.ts index 23709327e32..c804dbbae3d 100644 --- a/packages/slides-ui/src/services/slide-editor-manager.service.ts +++ b/packages/slides-ui/src/services/slide-editor-manager.service.ts @@ -48,11 +48,9 @@ export class SlideEditorManagerService implements ISlideEditorManagerService, ID private _rect: Nullable = null; private readonly _state$ = new BehaviorSubject>(null); - readonly state$ = this._state$.asObservable(); private readonly _rect$ = new BehaviorSubject>(null); - readonly rect$ = this._rect$.asObservable(); private _focus: boolean = false; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ba89da8e42a..92e30547786 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -499,6 +499,9 @@ importers: '@univerjs/docs-ui': specifier: workspace:* version: link:../../packages/docs-ui + '@univerjs/engine-render': + specifier: workspace:* + version: link:../../packages/engine-render '@univerjs/icons': specifier: ^0.1.72 version: 0.1.72(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -511,6 +514,12 @@ importers: '@univerjs/sheets-formula': specifier: workspace:* version: link:../../packages/sheets-formula + '@univerjs/slides': + specifier: workspace:* + version: link:../../packages/slides + '@univerjs/slides-ui': + specifier: workspace:* + version: link:../../packages/slides-ui '@univerjs/ui': specifier: workspace:* version: link:../../packages/ui