diff --git a/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png b/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png index 51d2e200b2..fa2f4f3097 100644 Binary files a/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png and b/e2e/visual-comparison/docs/docs-visual-comparison.spec.ts-snapshots/default-doc-ci-chromium-linux.png differ diff --git a/e2e/visual-comparison/sheets/sheets-switching-render-unit.spec.ts-snapshots/switching-render-unit-ci-chromium-linux.png b/e2e/visual-comparison/sheets/sheets-switching-render-unit.spec.ts-snapshots/switching-render-unit-ci-chromium-linux.png index 6161a85cf8..dba2a509c8 100644 Binary files a/e2e/visual-comparison/sheets/sheets-switching-render-unit.spec.ts-snapshots/switching-render-unit-ci-chromium-linux.png and b/e2e/visual-comparison/sheets/sheets-switching-render-unit.spec.ts-snapshots/switching-render-unit-ci-chromium-linux.png differ diff --git a/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/default-sheet-fullpage-ci-chromium-linux.png b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/default-sheet-fullpage-ci-chromium-linux.png index 20b428022a..f688156613 100644 Binary files a/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/default-sheet-fullpage-ci-chromium-linux.png and b/e2e/visual-comparison/sheets/sheets-visual-comparison.spec.ts-snapshots/default-sheet-fullpage-ci-chromium-linux.png differ diff --git a/packages-experimental/debugger/src/controllers/debugger.controller.ts b/packages-experimental/debugger/src/controllers/debugger.controller.ts index 6849b452ba..5fc5dc65a2 100644 --- a/packages-experimental/debugger/src/controllers/debugger.controller.ts +++ b/packages-experimental/debugger/src/controllers/debugger.controller.ts @@ -85,7 +85,6 @@ export class DebuggerController extends Disposable { private _initCustomComponents(): void { const componentManager = this._componentManager; - // this.disposeWithMe(componentManager.register(TEST_EDITOR_CONTAINER_COMPONENT, TestEditorContainer)); this.disposeWithMe(componentManager.register('VueI18nIcon', VueI18nIcon, { framework: 'vue3', })); diff --git a/packages-experimental/debugger/src/controllers/menu.schema.ts b/packages-experimental/debugger/src/controllers/menu.schema.ts index 52ba059f22..d5ab4215c5 100644 --- a/packages-experimental/debugger/src/controllers/menu.schema.ts +++ b/packages-experimental/debugger/src/controllers/menu.schema.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { type MenuSchemaType, RibbonOthersGroup } from '@univerjs/ui'; +import type { MenuSchemaType } from '@univerjs/ui'; +import { RibbonOthersGroup } from '@univerjs/ui'; import { CreateFloatDomCommand } from '../commands/commands/float-dom.command'; import { CreateEmptySheetCommand, DisposeCurrentUnitCommand, DisposeUniverCommand, LoadSheetSnapshotCommand } from '../commands/commands/unit.command'; import { ShowCellContentOperation } from '../commands/operations/cell.operation'; diff --git a/packages-experimental/debugger/src/controllers/menu.ts b/packages-experimental/debugger/src/controllers/menu.ts index f7848e7e5e..750d7fe12c 100644 --- a/packages-experimental/debugger/src/controllers/menu.ts +++ b/packages-experimental/debugger/src/controllers/menu.ts @@ -37,7 +37,7 @@ import { SidebarOperation } from '../commands/operations/sidebar.operation'; import { ThemeOperation } from '../commands/operations/theme.operation'; import { UNIVER_WATERMARK_MENU } from './watermark.menu.controller'; -export function LocaleMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { +export function LocaleMenuItemFactory(): IMenuSelectorItem { return { id: LocaleOperation.id, icon: 'VueI18nIcon', @@ -72,7 +72,7 @@ export function LocaleMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { }; } -export function DarkModeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { +export function DarkModeMenuItemFactory(): IMenuSelectorItem { return { id: DarkModeOperation.id, title: '🌓', @@ -95,7 +95,7 @@ export function DarkModeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem }; } -export function ThemeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { +export function ThemeMenuItemFactory(): IMenuSelectorItem { return { id: ThemeOperation.id, title: 'Theme', @@ -114,7 +114,7 @@ export function ThemeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { }; } -export function NotificationMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { +export function NotificationMenuItemFactory(): IMenuSelectorItem { return { id: NotificationOperation.id, title: 'Notification', @@ -141,7 +141,7 @@ export function NotificationMenuItemFactory(accessor: IAccessor): IMenuSelectorI }; } -export function DialogMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { +export function DialogMenuItemFactory(): IMenuSelectorItem { return { id: DialogOperation.id, title: 'Dialog', @@ -160,7 +160,7 @@ export function DialogMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { }; } -export function ConfirmMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { +export function ConfirmMenuItemFactory(): IMenuSelectorItem { return { id: ConfirmOperation.id, title: 'Confirm', @@ -322,7 +322,7 @@ export function CreateFloatDOMMenuItemFactory(): IMenuButtonItem { }; } -export function ShowCellContentMenuItemFactory(accessor: IAccessor): IMenuButtonItem { +export function ShowCellContentMenuItemFactory(): IMenuButtonItem { return { id: ShowCellContentOperation.id, type: MenuItemType.BUTTON, @@ -354,7 +354,7 @@ export function ChangeUserMenuItemFactory(): IMenuSelectorItem { }; } -export function WatermarkMenuItemFactory(accessor: IAccessor): IMenuButtonItem { +export function WatermarkMenuItemFactory(): IMenuButtonItem { return { id: OpenWatermarkPanelOperation.id, title: 'univer-watermark.title', diff --git a/packages-experimental/debugger/src/controllers/multi-units/multi-units.controller.ts b/packages-experimental/debugger/src/controllers/multi-units/multi-units.controller.ts new file mode 100644 index 0000000000..7d8c11a94a --- /dev/null +++ b/packages-experimental/debugger/src/controllers/multi-units/multi-units.controller.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Disposable, ICommandService } from '@univerjs/core'; +import { IMenuManagerService } from '@univerjs/ui'; +import { menuSchema, SwitchUnitOperation } from './multi-units.menu'; + +/** + * This controller helps to debug multi units inside a single Univer + * instance. + */ +export class MultiUnitsController extends Disposable { + constructor( + @ICommandService private readonly _commandService: ICommandService, + @IMenuManagerService private readonly _menuManagerService: IMenuManagerService + ) { + super(); + + this._initializeCommand(); + this._initializeMenu(); + } + + private _initializeCommand(): void { + this.disposeWithMe(this._commandService.registerCommand(SwitchUnitOperation)); + } + + private _initializeMenu(): void { + this._menuManagerService.mergeMenu(menuSchema); + } +} diff --git a/packages-experimental/debugger/src/controllers/multi-units/multi-units.menu.ts b/packages-experimental/debugger/src/controllers/multi-units/multi-units.menu.ts new file mode 100644 index 0000000000..ae5895bb40 --- /dev/null +++ b/packages-experimental/debugger/src/controllers/multi-units/multi-units.menu.ts @@ -0,0 +1,62 @@ +/** + * Copyright 2023-present DreamNum Co., Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { IAccessor, IOperation, Workbook } from '@univerjs/core'; +import type { IMenuSelectorItem, IValueOption, MenuSchemaType } from '@univerjs/ui'; +import { CommandType, IUniverInstanceService, UniverInstanceType } from '@univerjs/core'; +import { MenuItemType, RibbonStartGroup } from '@univerjs/ui'; +import { merge, Observable, shareReplay } from 'rxjs'; + +export const SwitchUnitOperation: IOperation<{ value: string }> = { + id: 'debugger.operation.switch-unit', + type: CommandType.OPERATION, + handler: (accessor, params) => { + const univerInstanceService = accessor.get(IUniverInstanceService); + if (!univerInstanceService.getUnit(params.value)) return false; + univerInstanceService.setCurrentUnitForType(params.value); + return true; + }, +}; + +function SwitchUnitMenuFactory(accessor: IAccessor): IMenuSelectorItem { + const instanceService = accessor.get(IUniverInstanceService); + const selections$ = new Observable>((observer) => { + function emit() { + const sheets = instanceService.getAllUnitsForType(UniverInstanceType.UNIVER_SHEET); + const options = sheets.map((sheet) => ({ label: sheet.getName() || sheet.getUnitId(), value: sheet.getUnitId() })); + observer.next(options); + } + + merge(instanceService.unitAdded$, instanceService.unitDisposed$).subscribe(() => emit()); + emit(); + }).pipe(shareReplay(1)); + + return { + id: SwitchUnitOperation.id, + title: 'Switch Unit', // TODO: i18n, + type: MenuItemType.SELECTOR, + selections: selections$, + }; +} + +export const menuSchema: MenuSchemaType = { + [RibbonStartGroup.FORMAT]: { + [SwitchUnitOperation.id]: { + order: -1, + menuItemFactory: SwitchUnitMenuFactory, + }, + }, +}; diff --git a/packages-experimental/debugger/src/plugin.ts b/packages-experimental/debugger/src/plugin.ts index 5a1ec9ba73..b11c2f8732 100644 --- a/packages-experimental/debugger/src/plugin.ts +++ b/packages-experimental/debugger/src/plugin.ts @@ -14,13 +14,13 @@ * limitations under the License. */ -import type { Dependency } from '@univerjs/core'; import type { IUniverDebuggerConfig } from './controllers/config.schema'; -import { IConfigService, Inject, Injector, merge, Plugin } from '@univerjs/core'; +import { IConfigService, Inject, Injector, merge, Plugin, registerDependencies, touchDependencies } from '@univerjs/core'; import { DEBUGGER_PLUGIN_CONFIG_KEY, defaultPluginConfig } from './controllers/config.schema'; import { DarkModeController } from './controllers/dark-mode.controller'; import { DebuggerController } from './controllers/debugger.controller'; import { E2EController } from './controllers/e2e/e2e.controller'; +import { MultiUnitsController } from './controllers/multi-units/multi-units.controller'; import { PerformanceMonitorController } from './controllers/performance-monitor.controller'; import { UniverWatermarkMenuController } from './controllers/watermark.menu.controller'; @@ -49,25 +49,33 @@ export class UniverDebuggerPlugin extends Plugin { } override onStarting(): void { - ([ + registerDependencies(this._injector, [ [PerformanceMonitorController], [DarkModeController], [DebuggerController], + [MultiUnitsController], [E2EController], [UniverWatermarkMenuController], - ] as Dependency[]).forEach((d) => this._injector.add(d)); + ]); - this._injector.get(E2EController); + touchDependencies(this._injector, [ + [E2EController], + ]); } override onReady(): void { - this._injector.get(DebuggerController); + touchDependencies(this._injector, [ + [DebuggerController], + [MultiUnitsController], + ]); } override onRendered(): void { - this._injector.get(DarkModeController); - this._injector.get(PerformanceMonitorController); - this._injector.get(UniverWatermarkMenuController); + touchDependencies(this._injector, [ + [DarkModeController], + [PerformanceMonitorController], + [UniverWatermarkMenuController], + ]); } getDebuggerController() { diff --git a/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts b/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts index 3e9215ca5a..fb4590ade4 100644 --- a/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts +++ b/packages/sheets-ui/src/controllers/editor/editing.render-controller.ts @@ -19,7 +19,7 @@ import type { DocumentDataModel, ICellData, ICommandInfo, IDisposable, IDocumentBody, IDocumentData, IDocumentStyle, IMutationInfo, IStyleData, Nullable, Styles, Workbook } from '@univerjs/core'; import type { IRichTextEditingMutationParams } from '@univerjs/docs'; import type { IRenderContext, IRenderModule } from '@univerjs/engine-render'; -import type { MutationsAffectRange, WorkbookSelectionModel } from '@univerjs/sheets'; +import type { ISetRangeValuesCommandParams, MutationsAffectRange, WorkbookSelectionModel } from '@univerjs/sheets'; import type { IEditorBridgeServiceVisibleParam } from '../../services/editor-bridge.service'; import { @@ -64,7 +64,7 @@ import { } from '@univerjs/engine-render'; import { adjustRangeOnMutation, COMMAND_LISTENER_SKELETON_CHANGE, InsertColMutation, InsertRowMutation, MoveColsMutation, MoveRowsMutation, REF_SELECTIONS_ENABLED, RemoveColMutation, RemoveRowMutation, SetRangeValuesCommand, SetSelectionsOperation, SetWorksheetActivateCommand, SetWorksheetActiveOperation, SheetInterceptorService, SheetsSelectionsService } from '@univerjs/sheets'; -import { KeyCode } from '@univerjs/ui'; +import { KeyCode, MetaKeys } from '@univerjs/ui'; import { distinctUntilChanged, filter } from 'rxjs'; import { getEditorObject } from '../../basics/editor/get-editor-object'; import { MoveSelectionCommand, MoveSelectionEnterAndTabCommand } from '../../commands/commands/set-selection.command'; @@ -114,7 +114,7 @@ export class EditingRenderController extends Disposable implements IRenderModule @ICommandService private readonly _commandService: ICommandService, @Inject(LocaleService) protected readonly _localService: LocaleService, @IEditorService private readonly _editorService: IEditorService, - @Inject(SheetCellEditorResizeService) private readonly _sheetCellEditorResizeService: SheetCellEditorResizeService, + @Inject(SheetCellEditorResizeService) private readonly _sheetCellEditorResizeService: SheetCellEditorResizeService, // RenderModule @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @Inject(SheetInterceptorService) private readonly _sheetInterceptorService: SheetInterceptorService ) { @@ -183,12 +183,12 @@ export class EditingRenderController extends Disposable implements IRenderModule this.disposeWithMe( this._editorBridgeService.visible$ .pipe(distinctUntilChanged((prev, curr) => prev.visible === curr.visible)) - .subscribe((param) => { - if ((param.unitId === DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY || param.unitId === this._context.unitId) && param.visible) { + .subscribe((params) => { + if ((params.unitId === DOCS_FORMULA_BAR_EDITOR_UNIT_ID_KEY || params.unitId === this._context.unitId) && params.visible) { this._isUnitEditing = true; - this._handleEditorVisible(param); + this._handleEditorVisible(params); } else if (this._isUnitEditing) { - this._handleEditorInvisible(param); + this._handleEditorInvisible(params); this._isUnitEditing = false; } }) @@ -558,13 +558,13 @@ export class EditingRenderController extends Disposable implements IRenderModule } const isEmpty = snapshot?.body?.dataStream.length === 2; - const isCellImage = editCellState.documentLayoutObject.documentModel ? this._isCellImageData(editCellState.documentLayoutObject.documentModel.getSnapshot()) : false; + const isCellImage = editCellState.documentLayoutObject.documentModel + ? this._isCellImageData(editCellState.documentLayoutObject.documentModel.getSnapshot()) + : false; + if (snapshot && !(isEmpty && isCellImage)) { - const res = await this._submitCellData(snapshot); - // if the submit was rejected, don't move selection - if (res === false) { - return; - } + const res = await this._submitEdit(snapshot, keycode === (MetaKeys.CTRL_COMMAND | KeyCode.ENTER)); + if (res === false) return; // if the submit was rejected, don't move selection } // moveSelection need to put behind of SetRangeValuesCommand, fix https://github.com/dream-num/univer/issues/1155 @@ -581,10 +581,10 @@ export class EditingRenderController extends Disposable implements IRenderModule } submitCellData(documentDataModel: DocumentDataModel) { - return this._submitCellData(documentDataModel.getSnapshot()); + return this._submitEdit(documentDataModel.getSnapshot()); } - private async _submitCellData(snapshot: IDocumentData) { + private async _submitEdit(snapshot: IDocumentData, wholeSelection = false) { const editCellState = this._editorBridgeService.getEditCellState(); if (editCellState == null) { return true; @@ -618,21 +618,25 @@ export class EditingRenderController extends Disposable implements IRenderModule return true; } - const finalCell = this._sheetInterceptorService.onWriteCell(workbook, worksheet, row, column, cellData); + const finalCell = this._sheetInterceptorService.onWriteCell(workbook, worksheet, row, column, cellData) as ICellData; if (finalCell === worksheet.getCellRaw(row, column)) { return true; } - const redoUndoId = generateRandomId(6); - const res = this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, { - subUnitId: sheetId, - unitId, - range: { + const range = wholeSelection + ? this._workbookSelections.getCurrentLastSelection()?.range + : { startRow: row, startColumn: column, endRow: row, endColumn: column, - }, + }; + + const redoUndoId = generateRandomId(6); + const res = this._commandService.syncExecuteCommand(SetRangeValuesCommand.id, { + subUnitId: sheetId, + unitId, + range, value: finalCell, redoUndoId, }); diff --git a/packages/sheets-ui/src/controllers/sheet-ui.controller.ts b/packages/sheets-ui/src/controllers/sheet-ui.controller.ts index 3f917791ee..fa002879b4 100644 --- a/packages/sheets-ui/src/controllers/sheet-ui.controller.ts +++ b/packages/sheets-ui/src/controllers/sheet-ui.controller.ts @@ -112,6 +112,7 @@ import { SHEETS_UI_PLUGIN_CONFIG_KEY } from './config.schema'; import { menuSchema } from './menu.schema'; import { EditorBreakLineShortcut, + EditorCursorCtrlEnterShortcut, EditorCursorEnterShortcut, EditorCursorEscShortcut, EditorCursorTabShortcut, @@ -338,6 +339,7 @@ export class SheetUIController extends Disposable { EditorDeleteLeftShortcut, EditorDeleteLeftShortcutInActive, EditorCursorEscShortcut, + EditorCursorCtrlEnterShortcut, // operation shortcuts SetRowHiddenShortcutItem, diff --git a/packages/sheets-ui/src/controllers/shortcuts/editor.shortcut.ts b/packages/sheets-ui/src/controllers/shortcuts/editor.shortcut.ts index 085dacf597..b446944c37 100644 --- a/packages/sheets-ui/src/controllers/shortcuts/editor.shortcut.ts +++ b/packages/sheets-ui/src/controllers/shortcuts/editor.shortcut.ts @@ -123,6 +123,17 @@ export const EditorCursorEscShortcut: IShortcutItem = { }, }; +export const EditorCursorCtrlEnterShortcut: IShortcutItem = { + id: SetCellEditVisibleOperation.id, + binding: KeyCode.ENTER | MetaKeys.CTRL_COMMAND, + preconditions: (contextService) => whenEditorDidNotInputFormulaActivated(contextService), + staticParameters: { + visible: false, + eventType: DeviceInputEventType.Keyboard, + keycode: KeyCode.ENTER | MetaKeys.CTRL_COMMAND, + }, +}; + export const EditorBreakLineShortcut: IShortcutItem = { id: BreakLineCommand.id, description: 'shortcut.sheet.break-line', diff --git a/packages/sheets-ui/src/services/editor-bridge.service.ts b/packages/sheets-ui/src/services/editor-bridge.service.ts index f75eeda31e..33ad2542f5 100644 --- a/packages/sheets-ui/src/services/editor-bridge.service.ts +++ b/packages/sheets-ui/src/services/editor-bridge.service.ts @@ -113,7 +113,7 @@ export class EditorBridgeService extends Disposable implements IEditorBridgeServ private _editorIsDirty: boolean = false; private _isDisabled: boolean = false; - private _visible: IEditorBridgeServiceVisibleParam = { + private _visibleParams: IEditorBridgeServiceVisibleParam = { visible: false, eventType: DeviceInputEventType.Dblclick, unitId: '', @@ -134,11 +134,11 @@ export class EditorBridgeService extends Disposable implements IEditorBridgeServ switchMap((editCellState) => this._currentEditCellLayout$.pipe(map((layout) => (editCellState && layout ? { ...editCellState, ...layout } : null)))) ); - private readonly _visible$ = new BehaviorSubject(this._visible); - readonly visible$ = this._visible$.asObservable(); + private readonly _visibleParams$ = new BehaviorSubject(this._visibleParams); + readonly visible$ = this._visibleParams$.asObservable(); - private readonly _afterVisible$ = new BehaviorSubject(this._visible); - readonly afterVisible$ = this._afterVisible$.asObservable(); + private readonly _afterVisibleParams$ = new BehaviorSubject(this._visibleParams); + readonly afterVisible$ = this._afterVisibleParams$.asObservable(); private readonly _forceKeepVisible$ = new BehaviorSubject(false); readonly forceKeepVisible$ = this._forceKeepVisible$.asObservable(); @@ -427,20 +427,20 @@ export class EditorBridgeService extends Disposable implements IEditorBridgeServ return this._editorUnitId; } - changeVisible(param: IEditorBridgeServiceVisibleParam) { - this._visible = param; + changeVisible(params: IEditorBridgeServiceVisibleParam) { + this._visibleParams = params; // Reset the dirty status when the editor is visible. - if (param.visible) { + if (params.visible) { this._editorIsDirty = false; } - this._visible$.next(this._visible); - this._afterVisible$.next(this._visible); + this._visibleParams$.next(this._visibleParams); + this._afterVisibleParams$.next(this._visibleParams); } isVisible() { - return this._visible; + return this._visibleParams; } enableForceKeepVisible(): void {