From b253997ce3126fc99f2829a76a3b7cc5b1476a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=99=BD=E7=86=B1?= Date: Tue, 21 May 2024 16:32:12 +0800 Subject: [PATCH] feat: add support for customizable context menu & toolbar (#2273) * feat: add support for customizable context menu & toolbar * fix: fix types * feat: add support for customizable context menu & toolbar --- examples/src/sheets/main.ts | 25 +- .../src/controllers/debugger.controller.ts | 17 +- packages/debugger/src/controllers/menu.ts | 104 +++-- packages/debugger/src/debugger-plugin.ts | 18 +- .../i-doc-ui-plugin-config.ts | 3 + .../src/controllers/doc-ui.controller.ts | 10 + packages/docs-ui/src/controllers/menu/menu.ts | 124 ++++-- packages/docs-ui/src/docs-ui-plugin.ts | 11 +- .../controllers/find-replace.controller.ts | 17 + .../src/controllers/find-replace.menu.ts | 9 +- packages/find-replace/src/plugin.ts | 25 +- .../src/controllers/cf.menu.controller.ts | 18 +- .../src/menu/manage-rule.ts | 216 ++++++----- .../src/plugin.ts | 16 +- .../__tests__/sheets-filter.menu.spec.ts | 3 +- .../sheets-filter-ui.controller.ts | 17 +- .../src/controllers/sheets-filter.menu.ts | 23 +- packages/sheets-filter-ui/src/plugin.ts | 19 +- .../src/controllers/formula-ui.controller.ts | 20 + .../sheets-formula/src/controllers/menu.ts | 28 +- .../sheets-formula/src/formula-ui-plugin.ts | 27 +- .../more-numfmt-type/MoreNumfmtType.tsx | 3 + .../src/controllers/numfmt.menu.controller.ts | 25 +- packages/sheets-numfmt/src/menu/menu.ts | 220 +++++------ packages/sheets-numfmt/src/numfmt-plugin.ts | 16 +- .../menu/__tests__/create-menu-test-bed.ts | 3 +- .../src/controllers/menu/border.menu.ts | 9 +- .../src/controllers/menu/clear.menu.ts | 45 ++- .../src/controllers/menu/delete.menu.ts | 53 ++- .../src/controllers/menu/insert.menu.ts | 85 +++-- .../sheets-ui/src/controllers/menu/menu.ts | 360 ++++++++++++------ .../src/controllers/menu/merge.menu.ts | 45 ++- .../src/controllers/menu/sheet.menu.ts | 64 +++- .../src/controllers/sheet-ui.controller.ts | 17 +- packages/sheets-ui/src/sheets-ui-plugin.ts | 19 +- .../controllers/zen-editor-ui.controller.ts | 17 +- packages/sheets-zen-editor/src/plugin.ts | 20 +- packages/sheets-zen-editor/src/views/menu.ts | 13 +- packages/ui/src/common/component-manager.ts | 13 + packages/ui/src/common/menu-merge-configs.ts | 52 +++ packages/ui/src/controllers/menus/menus.ts | 16 +- .../controllers/shared-shortcut.controller.ts | 10 + .../ui/src/controllers/ui/ui.controller.ts | 11 + packages/ui/src/index.ts | 3 + packages/ui/src/services/menu/menu.service.ts | 26 +- packages/ui/src/services/menu/menu.ts | 11 +- packages/ui/src/ui-plugin.ts | 20 +- packages/uniscript/src/controllers/menu.ts | 10 +- .../src/controllers/uniscript.controller.ts | 22 +- packages/uniscript/src/index.ts | 2 +- packages/uniscript/src/plugin.ts | 18 +- .../src/services/script-editor.service.ts | 7 +- 52 files changed, 1374 insertions(+), 611 deletions(-) create mode 100644 packages/ui/src/common/menu-merge-configs.ts diff --git a/examples/src/sheets/main.ts b/examples/src/sheets/main.ts index 06c3d845ab5..94c7b46d237 100644 --- a/examples/src/sheets/main.ts +++ b/examples/src/sheets/main.ts @@ -66,7 +66,30 @@ univer.registerPlugin(UniverUIPlugin, { univer.registerPlugin(UniverDocsUIPlugin); univer.registerPlugin(UniverSheetsPlugin); -univer.registerPlugin(UniverSheetsUIPlugin); +univer.registerPlugin(UniverSheetsUIPlugin, { + // menu: { + // 'sheet.command.set-range-bold': { + // tooltip: 'aaaa', + // // group: [], + // // type: '', + // // icon: '', + // title: 'x', + // // positions: '', + // // disabled: '', + // // value: '', + // // activated: '', + // hidden: true, + // disabled: true, + // activated: true, + // }, + // 'sheet.command.set-range-fontsize': { + // defaultValue: 40, + // }, + // 'sheet.command.set-range-text-color': { + // defaultValue: '#ee00bb', + // }, + // }, +}); // sheet feature plugins diff --git a/packages/debugger/src/controllers/debugger.controller.ts b/packages/debugger/src/controllers/debugger.controller.ts index 1d4ae14610b..f2e26cf25ed 100644 --- a/packages/debugger/src/controllers/debugger.controller.ts +++ b/packages/debugger/src/controllers/debugger.controller.ts @@ -15,7 +15,7 @@ */ import { Disposable, ICommandService, IUniverInstanceService } from '@univerjs/core'; -import type { IMenuItemFactory } from '@univerjs/ui'; +import type { IMenuItemFactory, MenuConfig } from '@univerjs/ui'; import { ComponentManager, IMenuService } from '@univerjs/ui'; import { Inject, Injector } from '@wendellhu/redi'; @@ -52,8 +52,15 @@ import { import { RecordController } from './local-save/record.controller'; import { ExportController } from './local-save/export.controller'; +export interface IUniverDebuggerConfig { + menu: MenuConfig; +} + +export const DefaultDebuggerConfig = {}; + export class DebuggerController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @IMenuService private readonly _menuService: IMenuService, @ICommandService private readonly _commandService: ICommandService, @@ -64,6 +71,7 @@ export class DebuggerController extends Disposable { this._initializeContextMenu(); this._initCustomComponents(); + this._initMenuConfigs(); [ LocaleOperation, @@ -109,4 +117,11 @@ export class DebuggerController extends Disposable { framework: 'vue3', })); } + + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } } diff --git a/packages/debugger/src/controllers/menu.ts b/packages/debugger/src/controllers/menu.ts index 72b42d18823..8404c8d68b7 100644 --- a/packages/debugger/src/controllers/menu.ts +++ b/packages/debugger/src/controllers/menu.ts @@ -17,7 +17,7 @@ import { LocaleType } from '@univerjs/core'; import { defaultTheme, greenTheme } from '@univerjs/design'; import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; -import { MenuItemType, MenuPosition } from '@univerjs/ui'; +import { IMenuService, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { ConfirmOperation } from '../commands/operations/confirm.operation'; @@ -32,7 +32,11 @@ import { ThemeOperation } from '../commands/operations/theme.operation'; import { CreateEmptySheetCommand, DisposeCurrentUnitCommand } from '../commands/commands/unit.command'; export function LocaleMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(LocaleOperation.id); + + return mergeMenuConfigs({ id: LocaleOperation.id, icon: 'VueI18nIcon', tooltip: 'i18n', @@ -52,11 +56,15 @@ export function LocaleMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { value: LocaleType.RU_RU, }, ], - }; + }, menuItemConfig); } export function ThemeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ThemeOperation.id); + + return mergeMenuConfigs({ id: ThemeOperation.id, title: 'Theme', tooltip: 'Theme', @@ -72,11 +80,15 @@ export function ThemeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { value: defaultTheme as any, }, ], - }; + }, menuItemConfig); } export function NotificationMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(NotificationOperation.id); + + return mergeMenuConfigs({ id: NotificationOperation.id, title: 'Notification', tooltip: 'Notification', @@ -100,11 +112,15 @@ export function NotificationMenuItemFactory(accessor: IAccessor): IMenuSelectorI value: 'Notification Error', }, ], - }; + }, menuItemConfig); } export function DialogMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(DialogOperation.id); + + return mergeMenuConfigs({ id: DialogOperation.id, title: 'Dialog', tooltip: 'Dialog', @@ -120,11 +136,15 @@ export function DialogMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { value: 'draggable', }, ], - }; + }, menuItemConfig); } export function ConfirmMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ConfirmOperation.id); + + return mergeMenuConfigs({ id: ConfirmOperation.id, title: 'Confirm', tooltip: 'Confirm', @@ -136,11 +156,15 @@ export function ConfirmMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { value: 'confirm', }, ], - }; + }, menuItemConfig); } export function MessageMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(MessageOperation.id); + + return mergeMenuConfigs({ id: MessageOperation.id, title: 'Message', tooltip: 'Message', @@ -152,11 +176,15 @@ export function MessageMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { value: '', }, ], - }; + }, menuItemConfig); } export function SidebarMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SidebarOperation.id); + + return mergeMenuConfigs({ id: SidebarOperation.id, title: 'Sidebar', tooltip: 'Sidebar', @@ -172,11 +200,15 @@ export function SidebarMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { value: 'close', }, ], - }; + }, menuItemConfig); } export function SetEditableMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetEditable.id); + + return mergeMenuConfigs({ id: SetEditable.id, title: 'Editable', tooltip: 'Editable', @@ -192,11 +224,15 @@ export function SetEditableMenuItemFactory(accessor: IAccessor): IMenuSelectorIt value: 'sheet', }, ], - }; + }, menuItemConfig); } export function SaveSnapshotSetEditableMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SaveSnapshotOptions.id); + + return mergeMenuConfigs({ id: SaveSnapshotOptions.id, type: MenuItemType.SELECTOR, title: 'Snapshot', @@ -215,39 +251,51 @@ export function SaveSnapshotSetEditableMenuItemFactory(accessor: IAccessor): IMe value: 'record', }, ], - }; + }, menuItemConfig); } const UNIT_ITEM_MENU_ID = 'debugger.unit-menu-item'; -export function UnitMenuItemFactory(): IMenuSelectorItem { - return { +export function UnitMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(UNIT_ITEM_MENU_ID); + + return mergeMenuConfigs({ id: UNIT_ITEM_MENU_ID, title: 'Unit', tooltip: 'Unit Commands', type: MenuItemType.SUBITEMS, positions: [MenuPosition.TOOLBAR_OTHERS], - }; + }, menuItemConfig); } -export function DisposeCurrentUnitMenuItemFactory(): IMenuButtonItem { - return { +export function DisposeCurrentUnitMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(DisposeCurrentUnitCommand.id); + + return mergeMenuConfigs({ id: DisposeCurrentUnitCommand.id, title: 'Dispose Current Unit', tooltip: 'Dispose Current Unit', icon: 'DS', type: MenuItemType.BUTTON, positions: [UNIT_ITEM_MENU_ID], - }; + }, menuItemConfig); } -export function CreateEmptySheetMenuItemFactory(): IMenuButtonItem { - return { +export function CreateEmptySheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CreateEmptySheetCommand.id); + + return mergeMenuConfigs({ id: CreateEmptySheetCommand.id, title: 'Create Another Sheet', tooltip: 'Create Another Sheet', icon: 'CR', type: MenuItemType.BUTTON, positions: [UNIT_ITEM_MENU_ID], - }; + }, menuItemConfig); } diff --git a/packages/debugger/src/debugger-plugin.ts b/packages/debugger/src/debugger-plugin.ts index 5355df568c2..8e7772afb27 100644 --- a/packages/debugger/src/debugger-plugin.ts +++ b/packages/debugger/src/debugger-plugin.ts @@ -14,26 +14,27 @@ * limitations under the License. */ -import { Plugin } from '@univerjs/core'; +import { Plugin, Tools } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; -import { DebuggerController } from './controllers/debugger.controller'; +import type { IUniverDebuggerConfig } from './controllers/debugger.controller'; +import { DebuggerController, DefaultDebuggerConfig } from './controllers/debugger.controller'; import { PerformanceMonitorController } from './controllers/performance-monitor.controller'; import { E2EMemoryController } from './controllers/e2e/e2e-memory.controller'; -export interface IDebuggerPluginConfig { } - export class UniverDebuggerPlugin extends Plugin { static override pluginName = 'DEBUGGER_PLUGIN'; private _debuggerController!: DebuggerController; constructor( - _config: IDebuggerPluginConfig, + private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector ) { super(); + + this._config = Tools.deepMerge({}, DefaultDebuggerConfig, this._config); } override onStarting(injector: Injector): void { @@ -44,7 +45,12 @@ export class UniverDebuggerPlugin extends Plugin { } override onRendered(): void { - this._injector.add([DebuggerController]); + this._injector.add([ + DebuggerController, + { + useFactory: () => this._injector.createInstance(DebuggerController, this._config), + }, + ]); this._debuggerController = this._injector.get(DebuggerController); } diff --git a/packages/docs-ui/src/basics/interfaces/component-config/i-doc-ui-plugin-config.ts b/packages/docs-ui/src/basics/interfaces/component-config/i-doc-ui-plugin-config.ts index c6d55d1067c..a286c54d282 100644 --- a/packages/docs-ui/src/basics/interfaces/component-config/i-doc-ui-plugin-config.ts +++ b/packages/docs-ui/src/basics/interfaces/component-config/i-doc-ui-plugin-config.ts @@ -14,12 +14,15 @@ * limitations under the License. */ +import type { MenuConfig } from '@univerjs/ui'; + export interface ILayout { docContainerConfig?: DocContainerConfig; toolbarConfig?: DocToolbarConfig; } export interface IUniverDocsUIConfig { + menu?: MenuConfig; container?: HTMLElement | string; layout?: ILayout; } diff --git a/packages/docs-ui/src/controllers/doc-ui.controller.ts b/packages/docs-ui/src/controllers/doc-ui.controller.ts index 3de2770e23d..7d5ba671096 100644 --- a/packages/docs-ui/src/controllers/doc-ui.controller.ts +++ b/packages/docs-ui/src/controllers/doc-ui.controller.ts @@ -31,6 +31,7 @@ import { } from '../components/font-family'; import { FONT_SIZE_COMPONENT, FontSize } from '../components/font-size'; import { DocBackground } from '../views/doc-background/DocBackground'; +import type { IUniverDocsUIConfig } from '../basics'; import { AlignCenterMenuItemFactory, AlignJustifyMenuItemFactory, @@ -55,6 +56,7 @@ import { @OnLifecycle(LifecycleStages.Rendered, DocUIController) export class DocUIController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @Inject(ComponentManager) private readonly _componentManager: ComponentManager, @ILayoutService private readonly _layoutService: ILayoutService, @@ -76,6 +78,13 @@ export class DocUIController extends Disposable { this.disposeWithMe(componentManager.register(FONT_SIZE_COMPONENT, FontSize)); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initMenus(): void { // init menus ( @@ -105,6 +114,7 @@ export class DocUIController extends Disposable { private _init(): void { this._initCustomComponents(); + this._initMenuConfigs(); this._initMenus(); this._initDocBackground(); this._initFocusHandler(); diff --git a/packages/docs-ui/src/controllers/menu/menu.ts b/packages/docs-ui/src/controllers/menu/menu.ts index 71a5bab8593..8a33c9265bb 100644 --- a/packages/docs-ui/src/controllers/menu/menu.ts +++ b/packages/docs-ui/src/controllers/menu/menu.ts @@ -51,9 +51,11 @@ import { FONT_FAMILY_LIST, FONT_SIZE_LIST, getMenuHiddenObservable, + IMenuService, MenuGroup, MenuItemType, MenuPosition, + mergeMenuConfigs, } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { Observable } from 'rxjs'; @@ -64,8 +66,11 @@ import { FONT_SIZE_COMPONENT } from '../../components/font-size'; export function BoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatBoldCommand.id); + + return mergeMenuConfigs({ id: SetInlineFormatBoldCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -95,13 +100,16 @@ export function BoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function ItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatItalicCommand.id); - return { + return mergeMenuConfigs({ id: SetInlineFormatItalicCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -131,13 +139,16 @@ export function ItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function UnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatUnderlineCommand.id); - return { + return mergeMenuConfigs({ id: SetInlineFormatUnderlineCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -167,13 +178,16 @@ export function UnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function StrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatStrikethroughCommand.id); + + return mergeMenuConfigs({ id: SetInlineFormatStrikethroughCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -203,13 +217,16 @@ export function StrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonIt return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function SubscriptMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatSubscriptCommand.id); - return { + return mergeMenuConfigs({ id: SetInlineFormatSubscriptCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -238,13 +255,16 @@ export function SubscriptMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function SuperscriptMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatSuperscriptCommand.id); + + return mergeMenuConfigs({ id: SetInlineFormatSuperscriptCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -273,13 +293,16 @@ export function SuperscriptMenuItemFactory(accessor: IAccessor): IMenuButtonItem return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function FontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatFontFamilyCommand.id); - return { + return mergeMenuConfigs({ id: SetInlineFormatFontFamilyCommand.id, tooltip: 'toolbar.font', group: MenuGroup.TOOLBAR_FORMAT, @@ -317,13 +340,16 @@ export function FontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSel return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatFontSizeCommand.id); - return { + return mergeMenuConfigs({ id: SetInlineFormatFontSizeCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.SELECTOR, @@ -362,14 +388,17 @@ export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelec return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function TextColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); const themeService = accessor.get(ThemeService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatTextColorCommand.id); + + return mergeMenuConfigs({ id: SetInlineFormatTextColorCommand.id, icon: 'FontColor', tooltip: 'toolbar.textColor.main', @@ -399,13 +428,16 @@ export function TextColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSele }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), // disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export function AlignLeftMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(AlignLeftCommand.id); - return { + return mergeMenuConfigs({ id: AlignLeftCommand.id, group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.BUTTON, @@ -434,13 +466,16 @@ export function AlignLeftMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function AlignCenterMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(AlignCenterCommand.id); + + return mergeMenuConfigs({ id: AlignCenterCommand.id, group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.BUTTON, @@ -469,13 +504,16 @@ export function AlignCenterMenuItemFactory(accessor: IAccessor): IMenuButtonItem return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function AlignRightMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(AlignRightCommand.id); - return { + return mergeMenuConfigs({ id: AlignRightCommand.id, group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.BUTTON, @@ -504,13 +542,16 @@ export function AlignRightMenuItemFactory(accessor: IAccessor): IMenuButtonItem return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function AlignJustifyMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(AlignJustifyCommand.id); - return { + return mergeMenuConfigs({ id: AlignJustifyCommand.id, group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.BUTTON, @@ -539,11 +580,15 @@ export function AlignJustifyMenuItemFactory(accessor: IAccessor): IMenuButtonIte return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function OrderListMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(OrderListCommand.id); + + return mergeMenuConfigs({ id: OrderListCommand.id, group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.BUTTON, @@ -551,11 +596,15 @@ export function OrderListMenuItemFactory(accessor: IAccessor): IMenuButtonItem { tooltip: 'toolbar.order', positions: [MenuPosition.TOOLBAR_START], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function BulletListMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(BulletListCommand.id); + + return mergeMenuConfigs({ id: BulletListCommand.id, group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.BUTTON, @@ -563,24 +612,31 @@ export function BulletListMenuItemFactory(accessor: IAccessor): IMenuButtonItem tooltip: 'toolbar.unorder', positions: [MenuPosition.TOOLBAR_START], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } export function ResetBackgroundColorMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ResetInlineFormatTextBackgroundColorCommand.id); + + return mergeMenuConfigs({ id: ResetInlineFormatTextBackgroundColorCommand.id, type: MenuItemType.BUTTON, title: 'toolbar.resetColor', icon: 'NoColor', positions: SetInlineFormatTextBackgroundColorCommand.id, - }; + }, menuItemConfig); } export function BackgroundColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); const themeService = accessor.get(ThemeService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetInlineFormatTextBackgroundColorCommand.id); - return { + return mergeMenuConfigs({ id: SetInlineFormatTextBackgroundColorCommand.id, tooltip: 'toolbar.fillColor.main', group: MenuGroup.TOOLBAR_FORMAT, @@ -608,7 +664,7 @@ export function BackgroundColorSelectorMenuItemFactory(accessor: IAccessor): IMe return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_DOC), - }; + }, menuItemConfig); } function getFontStyleAtCursor(accessor: IAccessor) { diff --git a/packages/docs-ui/src/docs-ui-plugin.ts b/packages/docs-ui/src/docs-ui-plugin.ts index e45af290504..38989109de4 100644 --- a/packages/docs-ui/src/docs-ui-plugin.ts +++ b/packages/docs-ui/src/docs-ui-plugin.ts @@ -68,9 +68,7 @@ export class UniverDocsUIPlugin extends Plugin { ) { super(); - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); this._config = Tools.deepMerge({}, DefaultDocUiConfig, this._config); this._initDependencies(_injector); @@ -104,7 +102,12 @@ export class UniverDocsUIPlugin extends Plugin { private _initDependencies(injector: Injector) { const dependencies: Dependency[] = [ // Controller - [DocUIController], + [ + DocUIController, + { + useFactory: () => this._injector.createInstance(DocUIController, this._config), + }, + ], [DocClipboardController], [DocEditorBridgeController], [DocRenderController], diff --git a/packages/find-replace/src/controllers/find-replace.controller.ts b/packages/find-replace/src/controllers/find-replace.controller.ts index 858b05fdef1..58d0cc6f718 100644 --- a/packages/find-replace/src/controllers/find-replace.controller.ts +++ b/packages/find-replace/src/controllers/find-replace.controller.ts @@ -23,6 +23,8 @@ import { RxDisposable, } from '@univerjs/core'; import { SearchSingle16 } from '@univerjs/icons'; +import type { + MenuConfig } from '@univerjs/ui'; import { ComponentManager, IDialogService, @@ -51,6 +53,12 @@ import { } from './find-replace.shortcut'; import { FindReplaceMenuItemFactory } from './find-replace.menu'; +export interface IUniverFindReplaceConfig { + menu: MenuConfig; +} + +export const DefaultFindReplaceConfig = {}; + const FIND_REPLACE_DIALOG_ID = 'DESKTOP_FIND_REPLACE_DIALOG'; const FIND_REPLACE_PANEL_WIDTH = 350; @@ -60,6 +68,7 @@ const FIND_REPLACE_PANEL_TOP_PADDING = -90; @OnLifecycle(LifecycleStages.Rendered, FindReplaceController) export class FindReplaceController extends RxDisposable { constructor( + private readonly _config: Partial, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @IMenuService private readonly _menuService: IMenuService, @IShortcutService private readonly _shortcutService: IShortcutService, @@ -74,6 +83,7 @@ export class FindReplaceController extends RxDisposable { super(); this._initCommands(); + this._initMenuConfigs(); this._initUI(); this._initShortcuts(); } @@ -101,6 +111,13 @@ export class FindReplaceController extends RxDisposable { ].forEach((s) => this.disposeWithMe(this._shortcutService.registerShortcut(s))); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initUI(): void { this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(FindReplaceMenuItemFactory))); this.disposeWithMe(this._componentManager.register('FindReplaceDialog', FindReplaceDialog)); diff --git a/packages/find-replace/src/controllers/find-replace.menu.ts b/packages/find-replace/src/controllers/find-replace.menu.ts index ea6f3840787..2f037ec619b 100644 --- a/packages/find-replace/src/controllers/find-replace.menu.ts +++ b/packages/find-replace/src/controllers/find-replace.menu.ts @@ -16,7 +16,7 @@ import { EDITOR_ACTIVATED, FOCUSING_SHEET, IContextService, UniverInstanceType } from '@univerjs/core'; import type { IMenuButtonItem } from '@univerjs/ui'; -import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { combineLatest, map } from 'rxjs'; @@ -24,8 +24,11 @@ import { OpenFindDialogOperation } from '../commands/operations/find-replace.ope export function FindReplaceMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const contextService = accessor.get(IContextService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(OpenFindDialogOperation.id); + + return mergeMenuConfigs({ id: OpenFindDialogOperation.id, icon: 'SearchIcon', tooltip: 'find-replace.toolbar', @@ -37,5 +40,5 @@ export function FindReplaceMenuItemFactory(accessor: IAccessor): IMenuButtonItem contextService.subscribeContextValue$(EDITOR_ACTIVATED), contextService.subscribeContextValue$(FOCUSING_SHEET), ]).pipe(map(([editorActivated, focusingSheet]) => editorActivated || !focusingSheet)), - }; + }, menuItemConfig); } diff --git a/packages/find-replace/src/plugin.ts b/packages/find-replace/src/plugin.ts index 487eaddf314..c46da5155b2 100644 --- a/packages/find-replace/src/plugin.ts +++ b/packages/find-replace/src/plugin.ts @@ -14,10 +14,11 @@ * limitations under the License. */ -import { LocaleService, Plugin } from '@univerjs/core'; -import { type Dependency, Inject, type Injector } from '@wendellhu/redi'; +import { LocaleService, Plugin, Tools } from '@univerjs/core'; +import { type Dependency, Inject, Injector } from '@wendellhu/redi'; -import { FindReplaceController } from './controllers/find-replace.controller'; +import type { IUniverFindReplaceConfig } from './controllers/find-replace.controller'; +import { DefaultFindReplaceConfig, FindReplaceController } from './controllers/find-replace.controller'; import { zhCN } from './locale'; import { FindReplaceService, IFindReplaceService } from './services/find-replace.service'; @@ -27,18 +28,26 @@ export class UniverFindReplacePlugin extends Plugin { static override pluginName = PLUGIN_NAME; constructor( - protected readonly _injector: Injector, + private readonly _config: Partial = {}, + @Inject(Injector) override readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService ) { super(); - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); + this._config = Tools.deepMerge({}, DefaultFindReplaceConfig, this._config); } override onStarting(injector: Injector): void { - ([[FindReplaceController], [IFindReplaceService, { useClass: FindReplaceService }]] as Dependency[]).forEach( + ([ + [ + FindReplaceController, + { + useFactory: () => this._injector.createInstance(FindReplaceController, this._config), + }, + ], + [IFindReplaceService, { useClass: FindReplaceService }], + ] as Dependency[]).forEach( (d) => { injector.add(d); } diff --git a/packages/sheets-conditional-formatting-ui/src/controllers/cf.menu.controller.ts b/packages/sheets-conditional-formatting-ui/src/controllers/cf.menu.controller.ts index 767a0812768..65269c3bc28 100644 --- a/packages/sheets-conditional-formatting-ui/src/controllers/cf.menu.controller.ts +++ b/packages/sheets-conditional-formatting-ui/src/controllers/cf.menu.controller.ts @@ -17,17 +17,25 @@ import type { IDisposable } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; import { Disposable, IUniverInstanceService, LifecycleStages, LocaleService, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import type { MenuConfig } from '@univerjs/ui'; import { ComponentManager, IMenuService, ISidebarService } from '@univerjs/ui'; import type { IConditionFormattingRule } from '@univerjs/sheets-conditional-formatting'; import { FactoryManageConditionalFormattingRule } from '../menu/manage-rule'; import { ConditionFormattingPanel } from '../components/panel'; +export interface IUniverSheetsConditionalFormattingUIConfig { + menu: MenuConfig; +} + +export const DefaultSheetConditionalFormattingUiConfig = {}; + const CF_PANEL_KEY = 'sheet.conditional.formatting.panel'; @OnLifecycle(LifecycleStages.Ready, ConditionalFormattingMenuController) export class ConditionalFormattingMenuController extends Disposable { private _sidebarDisposable: IDisposable | null = null; constructor( + private readonly _config: Partial, @IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService, @Inject(Injector) private _injector: Injector, @Inject(ComponentManager) private _componentManager: ComponentManager, @@ -37,6 +45,7 @@ export class ConditionalFormattingMenuController extends Disposable { ) { super(); + this._initMenuConfigs(); this._initMenu(); this._initPanel(); @@ -60,8 +69,15 @@ export class ConditionalFormattingMenuController extends Disposable { this._sidebarDisposable = this._sidebarService.open(props); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initMenu() { - this._menuService.addMenuItem(FactoryManageConditionalFormattingRule(this._componentManager)(this._injector)); + this._menuService.addMenuItem(FactoryManageConditionalFormattingRule(this._injector)); } private _initPanel() { diff --git a/packages/sheets-conditional-formatting-ui/src/menu/manage-rule.ts b/packages/sheets-conditional-formatting-ui/src/menu/manage-rule.ts index 667ae26dd6f..7fb1615666a 100644 --- a/packages/sheets-conditional-formatting-ui/src/menu/manage-rule.ts +++ b/packages/sheets-conditional-formatting-ui/src/menu/manage-rule.ts @@ -15,31 +15,93 @@ */ import { merge, Observable } from 'rxjs'; -import type { ComponentManager, IMenuSelectorItem } from '@univerjs/ui'; +import type { IMenuSelectorItem } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; -import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import { SelectionManagerService, SetWorksheetActiveOperation } from '@univerjs/sheets'; import { debounceTime } from 'rxjs/operators'; import type { Workbook } from '@univerjs/core'; -import { ICommandService, IUniverInstanceService, LocaleService, Rectangle, UniverInstanceType } from '@univerjs/core'; -import { Conditions } from '@univerjs/icons'; +import { ICommandService, IUniverInstanceService, Rectangle, UniverInstanceType } from '@univerjs/core'; import { AddConditionalRuleMutation, ConditionalFormattingRuleModel, DeleteConditionalRuleMutation, MoveConditionalRuleMutation, SetConditionalRuleMutation } from '@univerjs/sheets-conditional-formatting'; import { CF_MENU_OPERATION, OpenConditionalFormattingOperator } from '../commands/operations/open-conditional-formatting-panel'; const commandList = [SetWorksheetActiveOperation.id, AddConditionalRuleMutation.id, SetConditionalRuleMutation.id, DeleteConditionalRuleMutation.id, MoveConditionalRuleMutation.id]; -export const FactoryManageConditionalFormattingRule = (componentManager: ComponentManager) => { - const key = 'conditional-formatting-menu-icon'; - componentManager.register(key, Conditions); - return (accessor: IAccessor) => { - const localeService = accessor.get(LocaleService); - const selectionManagerService = accessor.get(SelectionManagerService); - const commandService = accessor.get(ICommandService); - const univerInstanceService = accessor.get(IUniverInstanceService); - const conditionalFormattingRuleModel = accessor.get(ConditionalFormattingRuleModel); - const clearRangeEnable$ = new Observable((subscriber) => merge( +const commonSelections = [ + { + label: 'sheet.cf.ruleType.highlightCell', + value: CF_MENU_OPERATION.highlightCell, + }, + { + label: 'sheet.cf.panel.rankAndAverage', + value: CF_MENU_OPERATION.rank, + }, + { + label: 'sheet.cf.ruleType.formula', + value: CF_MENU_OPERATION.formula, + }, + { + label: 'sheet.cf.ruleType.colorScale', + value: CF_MENU_OPERATION.colorScale, + }, + { + label: 'sheet.cf.ruleType.dataBar', + value: CF_MENU_OPERATION.dataBar, + }, { + label: 'sheet.cf.ruleType.iconSet', + value: CF_MENU_OPERATION.icon, + }, + { + label: 'sheet.cf.menu.manageConditionalFormatting', + value: CF_MENU_OPERATION.viewRule, + }, { + label: 'sheet.cf.menu.createConditionalFormatting', + value: CF_MENU_OPERATION.createRule, + }, + { + label: 'sheet.cf.menu.clearRangeRules', + value: CF_MENU_OPERATION.clearRangeRules, + disabled: false, + }, + { + label: 'sheet.cf.menu.clearWorkSheetRules', + value: CF_MENU_OPERATION.clearWorkSheetRules, + }, +]; + +export const FactoryManageConditionalFormattingRule = (accessor: IAccessor) => { + const selectionManagerService = accessor.get(SelectionManagerService); + const commandService = accessor.get(ICommandService); + const univerInstanceService = accessor.get(IUniverInstanceService); + const conditionalFormattingRuleModel = accessor.get(ConditionalFormattingRuleModel); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(OpenConditionalFormattingOperator.id); + + const clearRangeEnable$ = new Observable((subscriber) => merge( + selectionManagerService.selectionMoveEnd$, + new Observable((commandSubscribe) => { + const disposable = commandService.onCommandExecuted((commandInfo) => { + const { id, params } = commandInfo; + const unitId = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)?.getUnitId(); + if (commandList.includes(id) && (params as { unitId: string }).unitId === unitId) { + commandSubscribe.next(null); + } + }); + return () => disposable.dispose(); + }) + ).pipe(debounceTime(16)).subscribe(() => { + const ranges = selectionManagerService.getSelections()?.map((selection) => selection.range) || []; + const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); + if (!workbook) return; + const allRule = conditionalFormattingRuleModel.getSubunitRules(workbook.getUnitId(), workbook.getActiveSheet().getSheetId()) || []; + const ruleList = allRule.filter((rule) => rule.ranges.some((ruleRange) => ranges.some((range) => Rectangle.intersects(range, ruleRange)))); + subscriber.next(!!ruleList.length); + })); + const clearSheetEnable$ = new Observable((subscriber) => + merge( selectionManagerService.selectionMoveEnd$, new Observable((commandSubscribe) => { const disposable = commandService.onCommandExecuted((commandInfo) => { @@ -52,104 +114,38 @@ export const FactoryManageConditionalFormattingRule = (componentManager: Compone return () => disposable.dispose(); }) ).pipe(debounceTime(16)).subscribe(() => { - const ranges = selectionManagerService.getSelections()?.map((selection) => selection.range) || []; const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); if (!workbook) return; const allRule = conditionalFormattingRuleModel.getSubunitRules(workbook.getUnitId(), workbook.getActiveSheet().getSheetId()) || []; - const ruleList = allRule.filter((rule) => rule.ranges.some((ruleRange) => ranges.some((range) => Rectangle.intersects(range, ruleRange)))); - subscriber.next(!!ruleList.length); - })); - const clearSheetEnable$ = new Observable((subscriber) => - merge( - selectionManagerService.selectionMoveEnd$, - new Observable((commandSubscribe) => { - const disposable = commandService.onCommandExecuted((commandInfo) => { - const { id, params } = commandInfo; - const unitId = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)?.getUnitId(); - if (commandList.includes(id) && (params as { unitId: string }).unitId === unitId) { - commandSubscribe.next(null); - } - }); - return () => disposable.dispose(); - }) - ).pipe(debounceTime(16)).subscribe(() => { - const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET); - if (!workbook) return; - const allRule = conditionalFormattingRuleModel.getSubunitRules(workbook.getUnitId(), workbook.getActiveSheet().getSheetId()) || []; - subscriber.next(!!allRule.length); - }) - ); - const selections$ = new Observable((subscriber) => { - const commonSelections = getCommonSelection(localeService); - clearRangeEnable$.subscribe((v) => { - const item = commonSelections.find((item) => item.value === CF_MENU_OPERATION.clearRangeRules); - if (item) { - item.disabled = !v; - subscriber.next(commonSelections); - } - }); - clearSheetEnable$.subscribe((v) => { - const item = commonSelections.find((item) => item.value === CF_MENU_OPERATION.clearWorkSheetRules); - if (item) { - item.disabled = !v; - subscriber.next(commonSelections); - } - }); - subscriber.next(commonSelections); + subscriber.next(!!allRule.length); + }) + ); + const selections$ = new Observable((subscriber) => { + clearRangeEnable$.subscribe((v) => { + const item = commonSelections.find((item) => item.value === CF_MENU_OPERATION.clearRangeRules); + if (item) { + item.disabled = !v; + subscriber.next(commonSelections); + } }); - return { - id: OpenConditionalFormattingOperator.id, - type: MenuItemType.SELECTOR, - group: MenuGroup.TOOLBAR_FORMULAS_INSERT, - positions: [MenuPosition.TOOLBAR_START], - icon: key, - tooltip: localeService.t('sheet.cf.title'), - selections: selections$, - hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - } as IMenuSelectorItem; - }; -}; + clearSheetEnable$.subscribe((v) => { + const item = commonSelections.find((item) => item.value === CF_MENU_OPERATION.clearWorkSheetRules); + if (item) { + item.disabled = !v; + subscriber.next(commonSelections); + } + }); + subscriber.next(commonSelections); + }); -function getCommonSelection(localeService: LocaleService) { - return [ - { - label: localeService.t('sheet.cf.ruleType.highlightCell'), - value: CF_MENU_OPERATION.highlightCell, - }, - { - label: localeService.t('sheet.cf.panel.rankAndAverage'), - value: CF_MENU_OPERATION.rank, - }, - { - label: localeService.t('sheet.cf.ruleType.formula'), - value: CF_MENU_OPERATION.formula, - }, - { - label: localeService.t('sheet.cf.ruleType.colorScale'), - value: CF_MENU_OPERATION.colorScale, - }, - { - label: localeService.t('sheet.cf.ruleType.dataBar'), - value: CF_MENU_OPERATION.dataBar, - }, { - label: localeService.t('sheet.cf.ruleType.iconSet'), - value: CF_MENU_OPERATION.icon, - }, - { - label: localeService.t('sheet.cf.menu.manageConditionalFormatting'), - value: CF_MENU_OPERATION.viewRule, - }, { - label: localeService.t('sheet.cf.menu.createConditionalFormatting'), - value: CF_MENU_OPERATION.createRule, - }, - { - label: localeService.t('sheet.cf.menu.clearRangeRules'), - value: CF_MENU_OPERATION.clearRangeRules, - disabled: false, - }, - { - label: localeService.t('sheet.cf.menu.clearWorkSheetRules'), - value: CF_MENU_OPERATION.clearWorkSheetRules, - }, - ]; -} + return mergeMenuConfigs({ + id: OpenConditionalFormattingOperator.id, + type: MenuItemType.SELECTOR, + group: MenuGroup.TOOLBAR_FORMULAS_INSERT, + positions: [MenuPosition.TOOLBAR_START], + icon: 'Conditions', + tooltip: 'sheet.cf.title', + selections: selections$, + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + }, menuItemConfig) as IMenuSelectorItem; +}; diff --git a/packages/sheets-conditional-formatting-ui/src/plugin.ts b/packages/sheets-conditional-formatting-ui/src/plugin.ts index 017ab706a03..7fcbd67b5a9 100644 --- a/packages/sheets-conditional-formatting-ui/src/plugin.ts +++ b/packages/sheets-conditional-formatting-ui/src/plugin.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ICommandService, Plugin, UniverInstanceType } from '@univerjs/core'; +import { ICommandService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; import { SHEET_CONDITIONAL_FORMATTING_PLUGIN, SheetsConditionalFormattingPlugin } from '@univerjs/sheets-conditional-formatting'; import { AddAverageCfCommand } from './commands/commands/add-average-cf.command'; @@ -37,7 +37,8 @@ import { AddCfCommand } from './commands/commands/add-cf.command'; import { RenderController } from './controllers/cf.render.controller'; import { ConditionalFormattingCopyPasteController } from './controllers/cf.copy-paste.controller'; import { ConditionalFormattingAutoFillController } from './controllers/cf.auto-fill.controller'; -import { ConditionalFormattingMenuController } from './controllers/cf.menu.controller'; +import type { IUniverSheetsConditionalFormattingUIConfig } from './controllers/cf.menu.controller'; +import { ConditionalFormattingMenuController, DefaultSheetConditionalFormattingUiConfig } from './controllers/cf.menu.controller'; import { ConditionalFormattingI18nController } from './controllers/cf.i18n.controller'; import { RefRangeController } from './controllers/cf.ref-range.controller'; import { ConditionalFormattingEditorController } from './controllers/cf.editor.controller'; @@ -67,11 +68,13 @@ export class UniverSheetsConditionalFormattingUIPlugin extends Plugin { ]; constructor( - _config: unknown, + private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector, @Inject(ICommandService) private _commandService: ICommandService ) { super(); + this._config = Tools.deepMerge({}, DefaultSheetConditionalFormattingUiConfig, this._config); + this._initCommand(); SheetsConditionalFormattingPlugin.dependencyList.forEach((dependency) => { this._injector.add(dependency); @@ -80,7 +83,12 @@ export class UniverSheetsConditionalFormattingUIPlugin extends Plugin { this._injector.add([RefRangeController]); this._injector.add([ConditionalFormattingCopyPasteController]); this._injector.add([ConditionalFormattingAutoFillController]); - this._injector.add([ConditionalFormattingMenuController]); + this._injector.add([ + ConditionalFormattingMenuController, + { + useFactory: () => this._injector.createInstance(ConditionalFormattingMenuController, this._config), + }, + ]); this._injector.add([ConditionalFormattingI18nController]); this._injector.add([ConditionalFormattingEditorController]); this._injector.add([ConditionalFormattingClearController]); diff --git a/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts b/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts index d3561dfd9b3..40dcd4e0856 100644 --- a/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts +++ b/packages/sheets-filter-ui/src/controllers/__tests__/sheets-filter.menu.spec.ts @@ -19,7 +19,7 @@ import { DisposableCollection, ICommandService, LocaleType, Plugin, RANGE_TYPE, import { NORMAL_SELECTION_PLUGIN_NAME, RefRangeService, SelectionManagerService, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; import type { ISetSheetsFilterCriteriaMutationParams, ISetSheetsFilterRangeMutationParams } from '@univerjs/sheets-filter'; import { RemoveSheetsFilterMutation, SetSheetsFilterCriteriaMutation, SetSheetsFilterRangeMutation, UniverSheetsFilterPlugin } from '@univerjs/sheets-filter'; -import { DesktopMenuService, DesktopShortcutService, IMenuService, IShortcutService } from '@univerjs/ui'; +import { DesktopMenuService, DesktopPlatformService, DesktopShortcutService, IMenuService, IPlatformService, IShortcutService } from '@univerjs/ui'; import { Injector } from '@wendellhu/redi'; import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SmartToggleSheetsFilterCommand } from '../../commands/sheets-filter.command'; @@ -61,6 +61,7 @@ function createSheetsFilterMenuTestBed() { } override onStarting(injector: Injector): void { + injector.add([IPlatformService, { useClass: DesktopPlatformService }]); injector.add([RefRangeService]); injector.add([SelectionManagerService]); injector.add([IShortcutService, { useClass: DesktopShortcutService }]); diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts index 35c50665483..41fe45036af 100644 --- a/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter-ui.controller.ts @@ -16,7 +16,7 @@ import type { Nullable } from '@univerjs/core'; import { ICommandService, IContextService, LifecycleStages, OnLifecycle, RxDisposable, UniverInstanceType } from '@univerjs/core'; -import type { IMenuItemFactory } from '@univerjs/ui'; +import type { IMenuItemFactory, MenuConfig } from '@univerjs/ui'; import { ComponentManager, IMenuService, IShortcutService } from '@univerjs/ui'; import type { IDisposable } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; @@ -34,6 +34,12 @@ import { SmartToggleFilterShortcut } from './sheets-filter.shortcut'; import { ClearFilterCriteriaMenuItemFactory, ReCalcFilterMenuItemFactory, SmartToggleFilterMenuItemFactory } from './sheets-filter.menu'; import { SheetsFilterRenderController } from './sheets-filter-render.controller'; +export interface IUniverSheetsFilterUIConfig { + menu: MenuConfig; +} + +export const DefaultSheetFilterUiConfig = {}; + export const FILTER_PANEL_POPUP_KEY = 'FILTER_PANEL_POPUP'; /** @@ -42,6 +48,7 @@ export const FILTER_PANEL_POPUP_KEY = 'FILTER_PANEL_POPUP'; @OnLifecycle(LifecycleStages.Ready, SheetsFilterUIController) export class SheetsFilterUIController extends RxDisposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @Inject(ComponentManager) private readonly _componentManager: ComponentManager, @Inject(SheetsFilterPanelService) private readonly _sheetsFilterPanelService: SheetsFilterPanelService, @@ -56,6 +63,7 @@ export class SheetsFilterUIController extends RxDisposable { this._initCommands(); this._initShortcuts(); + this._initMenuConfigs(); this._initMenuItems(); this._initUI(); this._initRenderControllers(); @@ -89,6 +97,13 @@ export class SheetsFilterUIController extends RxDisposable { }); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initMenuItems(): void { ([ SmartToggleFilterMenuItemFactory, diff --git a/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts b/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts index d5f083928e6..a7f47750ba2 100644 --- a/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts +++ b/packages/sheets-filter-ui/src/controllers/sheets-filter.menu.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { SheetsFilterService } from '@univerjs/sheets-filter'; @@ -25,8 +25,11 @@ import { ClearSheetsFilterCriteriaCommand, ReCalcSheetsFilterCommand, SmartToggl export function SmartToggleFilterMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const sheetsFilterService = accessor.get(SheetsFilterService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SmartToggleSheetsFilterCommand.id); + + return mergeMenuConfigs({ id: SmartToggleSheetsFilterCommand.id, group: MenuGroup.TOOLBAR_FORMULAS_INSERT, type: MenuItemType.BUTTON_SELECTOR, @@ -35,13 +38,16 @@ export function SmartToggleFilterMenuItemFactory(accessor: IAccessor): IMenuSele positions: [MenuPosition.TOOLBAR_START], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), activated$: sheetsFilterService.activeFilterModel$.pipe(map((model) => !!model)), - }; + }, menuItemConfig); } export function ClearFilterCriteriaMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const sheetsFilterService = accessor.get(SheetsFilterService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ClearSheetsFilterCriteriaCommand.id); - return { + return mergeMenuConfigs({ id: ClearSheetsFilterCriteriaCommand.id, group: MenuGroup.TOOLBAR_OTHERS, type: MenuItemType.BUTTON, @@ -49,13 +55,16 @@ export function ClearFilterCriteriaMenuItemFactory(accessor: IAccessor): IMenuBu positions: [SmartToggleSheetsFilterCommand.id], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: sheetsFilterService.activeFilterModel$.pipe(switchMap((model) => model?.hasCriteria$.pipe(map((m) => !m)) ?? of(true))), - }; + }, menuItemConfig); } export function ReCalcFilterMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const sheetsFilterService = accessor.get(SheetsFilterService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ReCalcSheetsFilterCommand.id); - return { + return mergeMenuConfigs({ id: ReCalcSheetsFilterCommand.id, group: MenuGroup.TOOLBAR_OTHERS, type: MenuItemType.BUTTON, @@ -63,5 +72,5 @@ export function ReCalcFilterMenuItemFactory(accessor: IAccessor): IMenuButtonIte positions: [SmartToggleSheetsFilterCommand.id], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: sheetsFilterService.activeFilterModel$.pipe(switchMap((model) => model?.hasCriteria$.pipe(map((m) => !m)) ?? of(true))), - }; + }, menuItemConfig); } diff --git a/packages/sheets-filter-ui/src/plugin.ts b/packages/sheets-filter-ui/src/plugin.ts index fbc58faf04b..982e84562b8 100644 --- a/packages/sheets-filter-ui/src/plugin.ts +++ b/packages/sheets-filter-ui/src/plugin.ts @@ -14,12 +14,13 @@ * limitations under the License. */ -import { LocaleService, Plugin, UniverInstanceType } from '@univerjs/core'; +import { LocaleService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; import { zhCN } from './locale'; -import { SheetsFilterUIController } from './controllers/sheets-filter-ui.controller'; +import type { IUniverSheetsFilterUIConfig } from './controllers/sheets-filter-ui.controller'; +import { DefaultSheetFilterUiConfig, SheetsFilterUIController } from './controllers/sheets-filter-ui.controller'; import { SheetsFilterPanelService } from './services/sheets-filter-panel.service'; const NAME = 'SHEET_FILTER_UI_PLUGIN'; @@ -29,21 +30,25 @@ export class UniverSheetsFilterUIPlugin extends Plugin { static override pluginName = NAME; constructor( - _config: unknown, + private readonly _config: Partial = {}, @Inject(Injector) protected readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService ) { super(); - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); + this._config = Tools.deepMerge({}, DefaultSheetFilterUiConfig, this._config); } override onStarting(injector: Injector): void { ([ [SheetsFilterPanelService], - [SheetsFilterUIController], + [ + SheetsFilterUIController, + { + useFactory: () => this._injector.createInstance(SheetsFilterUIController, this._config), + }, + ], ] as Dependency[]).forEach((d) => injector.add(d)); } } diff --git a/packages/sheets-formula/src/controllers/formula-ui.controller.ts b/packages/sheets-formula/src/controllers/formula-ui.controller.ts index 95e138a13c6..2b10714d09e 100644 --- a/packages/sheets-formula/src/controllers/formula-ui.controller.ts +++ b/packages/sheets-formula/src/controllers/formula-ui.controller.ts @@ -15,11 +15,14 @@ */ import { Disposable, ICommandService, LifecycleStages, OnLifecycle, UniverInstanceType } from '@univerjs/core'; +import type { MenuConfig } from '@univerjs/ui'; import { ComponentManager, DesktopUIPart, IMenuService, IShortcutService, IUIPartsService } from '@univerjs/ui'; +import type { Ctor } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; import { connectInjector } from '@wendellhu/redi/react-bindings'; import { IRenderManagerService } from '@univerjs/engine-render'; +import type { BaseFunction, IFunctionInfo, IFunctionNames } from '@univerjs/engine-formula'; import { SheetOnlyPasteFormulaCommand } from '../commands/commands/formula-clipboard.command'; import { InsertFunctionCommand } from '../commands/commands/insert-function.command'; import { SelectEditorFormulaOperation } from '../commands/operations/editor-formula.operation'; @@ -43,9 +46,18 @@ import { } from './shortcuts/prompt.shortcut'; import { FormulaEditorShowController } from './formula-editor-show.controller'; +export interface IUniverSheetsFormulaConfig { + menu: MenuConfig; + description: IFunctionInfo[]; + function: Array<[Ctor, IFunctionNames]>; +} + +export const DefaultSheetFormulaConfig = {}; + @OnLifecycle(LifecycleStages.Ready, FormulaUIController) export class FormulaUIController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @IMenuService private readonly _menuService: IMenuService, @ICommandService private readonly _commandService: ICommandService, @@ -61,12 +73,20 @@ export class FormulaUIController extends Disposable { private _initialize(): void { this._registerCommands(); + this._registerMenuConfigs(); this._registerMenus(); this._registerShortcuts(); this._registerComponents(); this._registerRenderControllers(); } + private _registerMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _registerMenus(): void { [InsertFunctionMenuItemFactory, MoreFunctionsMenuItemFactory, PasteFormulaMenuItemFactory].forEach( (factory) => { diff --git a/packages/sheets-formula/src/controllers/menu.ts b/packages/sheets-formula/src/controllers/menu.ts index a67d1fa34f0..e3315466ea9 100644 --- a/packages/sheets-formula/src/controllers/menu.ts +++ b/packages/sheets-formula/src/controllers/menu.ts @@ -18,7 +18,7 @@ import { UniverInstanceType } from '@univerjs/core'; import { getCurrentSheetDisabled$ } from '@univerjs/sheets'; import { PASTE_SPECIAL_MENU_ID } from '@univerjs/sheets-ui'; import type { IMenuItem } from '@univerjs/ui'; -import { getMenuHiddenObservable, IClipboardInterfaceService, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IClipboardInterfaceService, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { Observable } from 'rxjs'; @@ -27,7 +27,11 @@ import { InsertFunctionOperation } from '../commands/operations/insert-function. import { MoreFunctionsOperation } from '../commands/operations/more-functions.operation'; export function InsertFunctionMenuItemFactory(accessor: IAccessor): IMenuItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(InsertFunctionOperation.id); + + return mergeMenuConfigs({ id: InsertFunctionOperation.id, icon: 'FunctionSingle', tooltip: 'formula.insert.tooltip', @@ -63,16 +67,20 @@ export function InsertFunctionMenuItemFactory(accessor: IAccessor): IMenuItem { ], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } -export function MoreFunctionsMenuItemFactory(): IMenuItem { - return { +export function MoreFunctionsMenuItemFactory(accessor: IAccessor): IMenuItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(MoreFunctionsOperation.id); + + return mergeMenuConfigs({ id: MoreFunctionsOperation.id, title: 'formula.insert.more', positions: InsertFunctionOperation.id, type: MenuItemType.BUTTON, - }; + }, menuItemConfig); } function menuClipboardDisabledObservable(injector: IAccessor): Observable { @@ -80,11 +88,15 @@ function menuClipboardDisabledObservable(injector: IAccessor): Observable, IFunctionNames]>; -} export class UniverSheetsFormulaPlugin extends Plugin { static override pluginName = FORMULA_UI_PLUGIN_NAME; static override type = UniverInstanceType.UNIVER_SHEET; constructor( - private _config: Partial, + private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService ) { super(); + + this._config = Tools.deepMerge({}, DefaultSheetFormulaConfig, this._config); } override onStarting(): void { @@ -68,9 +66,7 @@ export class UniverSheetsFormulaPlugin extends Plugin { } _init(): void { - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); const dependencies: Dependency[] = [ // services @@ -88,7 +84,12 @@ export class UniverSheetsFormulaPlugin extends Plugin { [RegisterOtherFormulaService], // controllers - [FormulaUIController], + [ + FormulaUIController, + { + useFactory: () => this._injector.createInstance(FormulaUIController, this._config), + }, + ], [PromptController], [FormulaAutoFillController], [FormulaClipboardController], diff --git a/packages/sheets-numfmt/src/components/more-numfmt-type/MoreNumfmtType.tsx b/packages/sheets-numfmt/src/components/more-numfmt-type/MoreNumfmtType.tsx index c0865a58d07..05cc3ba8b90 100644 --- a/packages/sheets-numfmt/src/components/more-numfmt-type/MoreNumfmtType.tsx +++ b/packages/sheets-numfmt/src/components/more-numfmt-type/MoreNumfmtType.tsx @@ -29,6 +29,9 @@ import { SetNumfmtCommand } from '../../commands/commands/set-numfmt.command'; import { OpenNumfmtPanelOperator } from '../../commands/operations/open.numfmt.panel.operation'; import { getPatternPreview, getPatternType } from '../../utils/pattern'; +export const MORE_NUMFMT_TYPE_KEY = 'sheet.numfmt.moreNumfmtType'; +export const OPTIONS_KEY = 'sheet.numfmt.moreNumfmtType.options'; + export const MoreNumfmtType = (props: { value?: string }) => { const localeService = useDependency(LocaleService); const value = props.value ?? localeService.t('sheet.numfmt.general'); diff --git a/packages/sheets-numfmt/src/controllers/numfmt.menu.controller.ts b/packages/sheets-numfmt/src/controllers/numfmt.menu.controller.ts index 780d46fe33a..5aa61c6f765 100644 --- a/packages/sheets-numfmt/src/controllers/numfmt.menu.controller.ts +++ b/packages/sheets-numfmt/src/controllers/numfmt.menu.controller.ts @@ -15,27 +15,46 @@ */ import { Disposable, LifecycleStages, OnLifecycle } from '@univerjs/core'; +import type { MenuConfig } from '@univerjs/ui'; import { ComponentManager, IMenuService } from '@univerjs/ui'; import { Inject, Injector } from '@wendellhu/redi'; import { AddDecimalMenuItem, CurrencyMenuItem, FactoryOtherMenuItem, PercentMenuItem, SubtractDecimalMenuItem } from '../menu/menu'; +import { MORE_NUMFMT_TYPE_KEY, MoreNumfmtType, Options, OPTIONS_KEY } from '../components/more-numfmt-type/MoreNumfmtType'; + +export interface IUniverSheetsNumfmtConfig { + menu: MenuConfig; +} + +export const DefaultSheetNumfmtConfig = {}; @OnLifecycle(LifecycleStages.Rendered, NumfmtMenuController) export class NumfmtMenuController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private _injector: Injector, @Inject(ComponentManager) private _componentManager: ComponentManager, @Inject(IMenuService) private _menuService: IMenuService ) { super(); + this._initMenuConfigs(); this._initMenu(); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initMenu() { [PercentMenuItem, AddDecimalMenuItem, SubtractDecimalMenuItem, CurrencyMenuItem, FactoryOtherMenuItem] - .map((factory) => factory(this._componentManager)) - .forEach((configFactory) => { - this.disposeWithMe(this._menuService.addMenuItem(configFactory(this._injector))); + .forEach((factory) => { + this.disposeWithMe(this._menuService.addMenuItem(factory(this._injector))); }); + + this.disposeWithMe((this._componentManager.register(MORE_NUMFMT_TYPE_KEY, MoreNumfmtType))); + this.disposeWithMe((this._componentManager.register(OPTIONS_KEY, Options))); } } diff --git a/packages/sheets-numfmt/src/menu/menu.ts b/packages/sheets-numfmt/src/menu/menu.ts index 6a4c4b51907..9a0516ab393 100644 --- a/packages/sheets-numfmt/src/menu/menu.ts +++ b/packages/sheets-numfmt/src/menu/menu.ts @@ -15,7 +15,6 @@ */ import { ICommandService, IUniverInstanceService, LocaleService, UniverInstanceType } from '@univerjs/core'; -import { AddDigitsSingle, MoreDownSingle, PercentSingle, ReduceDigitsSingle, RmbSingle } from '@univerjs/icons'; import { getCurrentSheetDisabled$, INumfmtService, @@ -23,8 +22,8 @@ import { SelectionManagerService, SetNumfmtMutation, } from '@univerjs/sheets'; -import type { ComponentManager, IMenuSelectorItem } from '@univerjs/ui'; -import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import type { IMenuSelectorItem } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { merge, Observable } from 'rxjs'; @@ -34,35 +33,35 @@ import { AddDecimalCommand } from '../commands/commands/add-decimal.command'; import { SetCurrencyCommand } from '../commands/commands/set-currency.command'; import { SubtractDecimalCommand } from '../commands/commands/subtract-decimal.command'; import { OpenNumfmtPanelOperator } from '../commands/operations/open.numfmt.panel.operation'; -import { MoreNumfmtType, Options } from '../components/more-numfmt-type/MoreNumfmtType'; +import { MORE_NUMFMT_TYPE_KEY, OPTIONS_KEY } from '../components/more-numfmt-type/MoreNumfmtType'; import { isPatternEqualWithoutDecimal } from '../utils/decimal'; import { SetPercentCommand } from '../commands/commands/set-percent.command'; -export const CurrencyMenuItem = (componentManager: ComponentManager) => { - const iconKey = 'icon-rmbSingle'; - componentManager.register(iconKey, RmbSingle); - componentManager.register('MoreDownSingle', MoreDownSingle); - - return (accessor: IAccessor) => { - return { - icon: iconKey, - id: SetCurrencyCommand.id, - title: 'sheet.numfmt.currency', - tooltip: 'sheet.numfmt.currency', - type: MenuItemType.BUTTON, - group: MenuGroup.TOOLBAR_FORMULAS_INSERT, - positions: [MenuPosition.TOOLBAR_START], - hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - disabled$: getCurrentSheetDisabled$(accessor), - }; - }; +export const CurrencyMenuItem = (accessor: IAccessor) => { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetCurrencyCommand.id); + + return mergeMenuConfigs({ + icon: 'RmbSingle', + id: SetCurrencyCommand.id, + title: 'sheet.numfmt.currency', + tooltip: 'sheet.numfmt.currency', + type: MenuItemType.BUTTON, + group: MenuGroup.TOOLBAR_FORMULAS_INSERT, + positions: [MenuPosition.TOOLBAR_START], + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + disabled$: getCurrentSheetDisabled$(accessor), + }, menuItemConfig); }; -export const AddDecimalMenuItem = (componentManager: ComponentManager) => { - const iconKey = 'icon-addDigitsSingle'; - componentManager.register(iconKey, AddDigitsSingle); - return (accessor: IAccessor) => ({ - icon: iconKey, +export const AddDecimalMenuItem = (accessor: IAccessor) => { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetCurrencyCommand.id); + + return mergeMenuConfigs({ + icon: 'AddDigitsSingle', id: AddDecimalCommand.id, title: 'sheet.numfmt.addDecimal', tooltip: 'sheet.numfmt.addDecimal', @@ -71,13 +70,16 @@ export const AddDecimalMenuItem = (componentManager: ComponentManager) => { group: MenuGroup.TOOLBAR_FORMULAS_INSERT, hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }); + }, menuItemConfig); }; -export const SubtractDecimalMenuItem = (componentManager: ComponentManager) => { - const iconKey = 'icon-reduceDigitsSingle'; - componentManager.register(iconKey, ReduceDigitsSingle); - return (accessor: IAccessor) => ({ - icon: iconKey, + +export const SubtractDecimalMenuItem = (accessor: IAccessor) => { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetCurrencyCommand.id); + + return mergeMenuConfigs({ + icon: 'ReduceDigitsSingle', id: SubtractDecimalCommand.id, title: 'sheet.numfmt.subtractDecimal', tooltip: 'sheet.numfmt.subtractDecimal', @@ -86,14 +88,16 @@ export const SubtractDecimalMenuItem = (componentManager: ComponentManager) => { positions: [MenuPosition.TOOLBAR_START], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }); + }, menuItemConfig); }; -export const PercentMenuItem = (componentManager: ComponentManager) => { - const iconKey = 'icon-PercentSingle'; - componentManager.register(iconKey, PercentSingle); - return (accessor: IAccessor) => ({ - icon: iconKey, +export const PercentMenuItem = (accessor: IAccessor) => { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetCurrencyCommand.id); + + return mergeMenuConfigs({ + icon: 'PercentSingle', id: SetPercentCommand.id, title: 'sheet.numfmt.percent', tooltip: 'sheet.numfmt.percent', @@ -102,81 +106,77 @@ export const PercentMenuItem = (componentManager: ComponentManager) => { positions: [MenuPosition.TOOLBAR_START], hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }); + }, menuItemConfig); }; -export const FactoryOtherMenuItem = (componentManager: ComponentManager) => { - const moreTypeKey = 'sheet.numfmt.moreNumfmtType'; - const optionsKey = 'sheet.numfmt.moreNumfmtType.options'; - - componentManager.register(moreTypeKey, MoreNumfmtType); - componentManager.register(optionsKey, Options); - - return (_accessor: IAccessor) => { - const numfmtService = _accessor.get(INumfmtService); - const univerInstanceService = _accessor.get(IUniverInstanceService); - const commandService = _accessor.get(ICommandService); - const localeService = _accessor.get(LocaleService); - - const selectionManagerService = _accessor.get(SelectionManagerService); - const value$ = deriveStateFromActiveSheet$(univerInstanceService, '', ({ workbook, worksheet }) => new Observable((subscribe) => - merge( - selectionManagerService.selectionMoveEnd$, - new Observable((commandSubscribe) => { - const commandList = [RemoveNumfmtMutation.id, SetNumfmtMutation.id]; - const disposable = commandService.onCommandExecuted((commandInfo) => { - if (commandList.includes(commandInfo.id)) { - commandSubscribe.next(null); - } - }); - return () => disposable.dispose(); - }) - ).subscribe(() => { - const selections = selectionManagerService.getSelections(); - if (selections && selections[0]) { - const range = selections[0].range; - const row = range.startRow; - const col = range.startColumn; - - const numfmtValue = numfmtService.getValue(workbook.getUnitId(), worksheet.getSheetId(), row, col); - - const pattern = numfmtValue?.pattern; - let value: string = localeService.t('sheet.numfmt.general'); - - if (pattern) { - const item = MENU_OPTIONS.filter((item) => typeof item === 'object' && item.pattern).find( - (item) => isPatternEqualWithoutDecimal(pattern, (item as { pattern: string }).pattern) - ); - if (item && typeof item === 'object' && item.pattern) { - value = localeService.t(item.label); - } else { - value = localeService.t('sheet.numfmt.moreFmt'); - } +export const FactoryOtherMenuItem = (accessor: IAccessor) => { + const numfmtService = accessor.get(INumfmtService); + const univerInstanceService = accessor.get(IUniverInstanceService); + const commandService = accessor.get(ICommandService); + const localeService = accessor.get(LocaleService); + + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetCurrencyCommand.id); + + const selectionManagerService = accessor.get(SelectionManagerService); + const value$ = deriveStateFromActiveSheet$(univerInstanceService, '', ({ workbook, worksheet }) => new Observable((subscribe) => + merge( + selectionManagerService.selectionMoveEnd$, + new Observable((commandSubscribe) => { + const commandList = [RemoveNumfmtMutation.id, SetNumfmtMutation.id]; + const disposable = commandService.onCommandExecuted((commandInfo) => { + if (commandList.includes(commandInfo.id)) { + commandSubscribe.next(null); } + }); + return () => disposable.dispose(); + }) + ).subscribe(() => { + const selections = selectionManagerService.getSelections(); + if (selections && selections[0]) { + const range = selections[0].range; + const row = range.startRow; + const col = range.startColumn; - subscribe.next(value); + const numfmtValue = numfmtService.getValue(workbook.getUnitId(), worksheet.getSheetId(), row, col); + + const pattern = numfmtValue?.pattern; + let value: string = localeService.t('sheet.numfmt.general'); + + if (pattern) { + const item = MENU_OPTIONS.filter((item) => typeof item === 'object' && item.pattern).find( + (item) => isPatternEqualWithoutDecimal(pattern, (item as { pattern: string }).pattern) + ); + if (item && typeof item === 'object' && item.pattern) { + value = localeService.t(item.label); + } else { + value = localeService.t('sheet.numfmt.moreFmt'); + } } - }) - )); - - return { - label: moreTypeKey, - id: OpenNumfmtPanelOperator.id, - tooltip: 'sheet.numfmt.title', - type: MenuItemType.SELECTOR, - group: MenuGroup.TOOLBAR_FORMULAS_INSERT, - positions: [MenuPosition.TOOLBAR_START], - selections: [ - { - label: { - name: optionsKey, - hoverable: false, - }, + + subscribe.next(value); + } + }) + )); + + return mergeMenuConfigs({ + label: MORE_NUMFMT_TYPE_KEY, + id: OpenNumfmtPanelOperator.id, + tooltip: 'sheet.numfmt.title', + type: MenuItemType.SELECTOR, + group: MenuGroup.TOOLBAR_FORMULAS_INSERT, + positions: [MenuPosition.TOOLBAR_START], + selections: [ + { + label: { + name: OPTIONS_KEY, + hoverable: false, }, - ], - value$, - hidden$: getMenuHiddenObservable(_accessor, UniverInstanceType.UNIVER_SHEET), - disabled$: getCurrentSheetDisabled$(_accessor), - } as IMenuSelectorItem; - }; + }, + ], + value$, + hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), + disabled$: getCurrentSheetDisabled$(accessor), + }, menuItemConfig) as IMenuSelectorItem; }; diff --git a/packages/sheets-numfmt/src/numfmt-plugin.ts b/packages/sheets-numfmt/src/numfmt-plugin.ts index 356c3641216..68f2e315e2b 100644 --- a/packages/sheets-numfmt/src/numfmt-plugin.ts +++ b/packages/sheets-numfmt/src/numfmt-plugin.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { Plugin, UniverInstanceType } from '@univerjs/core'; +import { Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import { Inject, Injector } from '@wendellhu/redi'; import { SHEET_NUMFMT_PLUGIN } from './base/const/PLUGIN_NAME'; @@ -22,7 +22,8 @@ import { NumfmtCellContent } from './controllers/numfmt.cell-content.controller' import { NumfmtController } from './controllers/numfmt.controller'; import { NumfmtEditorController } from './controllers/numfmt.editor.controller'; import { NumfmtI18nController } from './controllers/numfmt.i18n.controller'; -import { NumfmtMenuController } from './controllers/numfmt.menu.controller'; +import type { IUniverSheetsNumfmtConfig } from './controllers/numfmt.menu.controller'; +import { DefaultSheetNumfmtConfig, NumfmtMenuController } from './controllers/numfmt.menu.controller'; import { INumfmtController } from './controllers/type'; import { UserHabitController } from './controllers/user-habit.controller'; @@ -31,10 +32,11 @@ export class UniverSheetsNumfmtPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; constructor( - _config: unknown, + private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector ) { super(); + this._config = Tools.deepMerge({}, DefaultSheetNumfmtConfig, this._config); } override onStarting(): void { @@ -43,6 +45,12 @@ export class UniverSheetsNumfmtPlugin extends Plugin { this._injector.add([UserHabitController]); this._injector.add([NumfmtCellContent]); this._injector.add([NumfmtI18nController]); - this._injector.add([NumfmtMenuController]); + this._injector.add( + [ + NumfmtMenuController, + { + useFactory: () => this._injector.createInstance(NumfmtMenuController, this._config), + }, + ]); } } diff --git a/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts b/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts index 0e99b9bd9ad..bdcfff33710 100644 --- a/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts +++ b/packages/sheets-ui/src/controllers/menu/__tests__/create-menu-test-bed.ts @@ -17,7 +17,7 @@ import type { IWorkbookData } from '@univerjs/core'; import { LocaleType, Plugin, Univer, UniverInstanceType } from '@univerjs/core'; import { SelectionManagerService, SheetInterceptorService, SheetPermissionService } from '@univerjs/sheets'; -import { DesktopMenuService, DesktopShortcutService, IMenuService, IShortcutService } from '@univerjs/ui'; +import { DesktopMenuService, DesktopPlatformService, DesktopShortcutService, IMenuService, IPlatformService, IShortcutService } from '@univerjs/ui'; import { Inject, Injector } from '@wendellhu/redi'; const TEST_WORKBOOK_DATA_DEMO: IWorkbookData = { @@ -60,6 +60,7 @@ export function createMenuTestBed() { } override onStarting(injector: Injector): void { + injector.add([IPlatformService, { useClass: DesktopPlatformService }]); injector.add([SelectionManagerService]); injector.add([IShortcutService, { useClass: DesktopShortcutService }]); injector.add([IMenuService, { useClass: DesktopMenuService }]); diff --git a/packages/sheets-ui/src/controllers/menu/border.menu.ts b/packages/sheets-ui/src/controllers/menu/border.menu.ts index 83d0b350a21..226a48afcd1 100644 --- a/packages/sheets-ui/src/controllers/menu/border.menu.ts +++ b/packages/sheets-ui/src/controllers/menu/border.menu.ts @@ -18,7 +18,7 @@ import { ICommandService, UniverInstanceType } from '@univerjs/core'; import type { IBorderInfo } from '@univerjs/sheets'; import { BorderStyleManagerService, getCurrentSheetDisabled$, SetBorderBasicCommand } from '@univerjs/sheets'; import type { IMenuSelectorItem } from '@univerjs/ui'; -import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { Observable } from 'rxjs'; @@ -28,10 +28,13 @@ export function CellBorderSelectorMenuItemFactory(accessor: IAccessor): IMenuSel // const permissionService = accessor.get(IPermissionService); const borderStyleManagerService = accessor.get(BorderStyleManagerService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetBorderBasicCommand.id); const disabled$ = getCurrentSheetDisabled$(accessor); - return { + return mergeMenuConfigs({ id: SetBorderBasicCommand.id, icon: new Observable((subscriber) => { const defaultIcon = 'AllBorderSingle'; @@ -72,5 +75,5 @@ export function CellBorderSelectorMenuItemFactory(accessor: IAccessor): IMenuSel value$: borderStyleManagerService.borderInfo$, hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$, - }; + }, menuItemConfig); } diff --git a/packages/sheets-ui/src/controllers/menu/clear.menu.ts b/packages/sheets-ui/src/controllers/menu/clear.menu.ts index b131311b828..327a730f64b 100644 --- a/packages/sheets-ui/src/controllers/menu/clear.menu.ts +++ b/packages/sheets-ui/src/controllers/menu/clear.menu.ts @@ -16,13 +16,18 @@ import { ClearSelectionAllCommand, ClearSelectionContentCommand, ClearSelectionFormatCommand } from '@univerjs/sheets'; import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; -import { MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; +import type { IAccessor } from '@wendellhu/redi'; import { SheetMenuPosition } from './menu'; const CLEAR_SELECTION_MENU_ID = 'sheet.menu.clear-selection'; -export function ClearSelectionMenuItemFactory(): IMenuSelectorItem { - return { +export function ClearSelectionMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CLEAR_SELECTION_MENU_ID); + + return mergeMenuConfigs({ id: CLEAR_SELECTION_MENU_ID, group: MenuGroup.CONTEXT_MENU_FORMAT, type: MenuItemType.SUBITEMS, @@ -33,30 +38,44 @@ export function ClearSelectionMenuItemFactory(): IMenuSelectorItem { SheetMenuPosition.COL_HEADER_CONTEXT_MENU, SheetMenuPosition.ROW_HEADER_CONTEXT_MENU, ], - }; + }, menuItemConfig); } -export function ClearSelectionContentMenuItemFactory(): IMenuButtonItem { - return { +export function ClearSelectionContentMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ClearSelectionContentCommand.id); + + return mergeMenuConfigs({ id: ClearSelectionContentCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.clearContent', positions: [CLEAR_SELECTION_MENU_ID], - }; + }, menuItemConfig); } -export function ClearSelectionFormatMenuItemFactory(): IMenuButtonItem { - return { + +export function ClearSelectionFormatMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ClearSelectionFormatCommand.id); + + return mergeMenuConfigs({ id: ClearSelectionFormatCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.clearFormat', positions: [CLEAR_SELECTION_MENU_ID], - }; + }, menuItemConfig); } -export function ClearSelectionAllMenuItemFactory(): IMenuButtonItem { - return { + +export function ClearSelectionAllMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ClearSelectionAllCommand.id); + + return mergeMenuConfigs({ id: ClearSelectionAllCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.clearAll', positions: [CLEAR_SELECTION_MENU_ID], - }; + }, menuItemConfig); } diff --git a/packages/sheets-ui/src/controllers/menu/delete.menu.ts b/packages/sheets-ui/src/controllers/menu/delete.menu.ts index a681c002085..eb38e9f00a7 100644 --- a/packages/sheets-ui/src/controllers/menu/delete.menu.ts +++ b/packages/sheets-ui/src/controllers/menu/delete.menu.ts @@ -15,8 +15,9 @@ */ import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; -import { MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; +import type { IAccessor } from '@wendellhu/redi'; import { DeleteRangeMoveLeftConfirmCommand } from '../../commands/commands/delete-range-move-left-confirm.command '; import { DeleteRangeMoveUpConfirmCommand } from '../../commands/commands/delete-range-move-up-confirm.command'; import { @@ -26,57 +27,77 @@ import { import { SheetMenuPosition } from './menu'; const DELETE_RANGE_MENU_ID = 'sheet.menu.delete'; -export function DeleteRangeMenuItemFactory(): IMenuSelectorItem { - return { +export function DeleteRangeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(DELETE_RANGE_MENU_ID); + + return mergeMenuConfigs({ id: DELETE_RANGE_MENU_ID, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.SUBITEMS, title: 'rightClick.delete', icon: 'Reduce', positions: [MenuPosition.CONTEXT_MENU], - }; + }, menuItemConfig); } -export function RemoveColMenuItemFactory(): IMenuButtonItem { - return { +export function RemoveColMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(RemoveColConfirmCommand.id); + + return mergeMenuConfigs({ id: RemoveColConfirmCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, icon: 'DeleteColumn', positions: [DELETE_RANGE_MENU_ID, SheetMenuPosition.COL_HEADER_CONTEXT_MENU], title: 'rightClick.deleteSelectedColumn', - }; + }, menuItemConfig); } -export function RemoveRowMenuItemFactory(): IMenuButtonItem { - return { +export function RemoveRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(RemoveRowConfirmCommand.id); + + return mergeMenuConfigs({ id: RemoveRowConfirmCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, icon: 'DeleteRow', positions: [DELETE_RANGE_MENU_ID, SheetMenuPosition.ROW_HEADER_CONTEXT_MENU], title: 'rightClick.deleteSelectedRow', - }; + }, menuItemConfig); } -export function DeleteRangeMoveLeftMenuItemFactory(): IMenuButtonItem { - return { +export function DeleteRangeMoveLeftMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(DeleteRangeMoveLeftConfirmCommand.id); + + return mergeMenuConfigs({ id: DeleteRangeMoveLeftConfirmCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, title: 'rightClick.moveLeft', icon: 'DeleteCellShiftLeft', positions: [DELETE_RANGE_MENU_ID], - }; + }, menuItemConfig); } -export function DeleteRangeMoveUpMenuItemFactory(): IMenuButtonItem { - return { +export function DeleteRangeMoveUpMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(DeleteRangeMoveUpConfirmCommand.id); + + return mergeMenuConfigs({ id: DeleteRangeMoveUpConfirmCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, title: 'rightClick.moveUp', icon: 'DeleteCellShiftUp', positions: [DELETE_RANGE_MENU_ID], - }; + }, menuItemConfig); } diff --git a/packages/sheets-ui/src/controllers/menu/insert.menu.ts b/packages/sheets-ui/src/controllers/menu/insert.menu.ts index ccdc6fe064b..b6d73ba9089 100644 --- a/packages/sheets-ui/src/controllers/menu/insert.menu.ts +++ b/packages/sheets-ui/src/controllers/menu/insert.menu.ts @@ -22,7 +22,7 @@ import { SelectionManagerService, } from '@univerjs/sheets'; import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; -import { MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { Observable } from 'rxjs'; @@ -31,43 +31,60 @@ import { InsertRangeMoveRightConfirmCommand } from '../../commands/commands/inse import { SheetMenuPosition } from './menu'; const COL_INSERT_MENU_ID = 'sheet.menu.col-insert'; -export function ColInsertMenuItemFactory(): IMenuSelectorItem { - return { +export function ColInsertMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(COL_INSERT_MENU_ID); + + return mergeMenuConfigs({ id: COL_INSERT_MENU_ID, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.SUBITEMS, title: 'rightClick.insert', icon: 'Insert', positions: [SheetMenuPosition.COL_HEADER_CONTEXT_MENU], - }; + }, menuItemConfig); } + const ROW_INSERT_MENU_ID = 'sheet.menu.row-insert'; -export function RowInsertMenuItemFactory(): IMenuSelectorItem { - return { +export function RowInsertMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ROW_INSERT_MENU_ID); + + return mergeMenuConfigs({ id: ROW_INSERT_MENU_ID, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.SUBITEMS, title: 'rightClick.insert', icon: 'Insert', positions: [SheetMenuPosition.ROW_HEADER_CONTEXT_MENU], - }; + }, menuItemConfig); } + const CELL_INSERT_MENU_ID = 'sheet.menu.cell-insert'; -export function CellInsertMenuItemFactory(): IMenuSelectorItem { - return { +export function CellInsertMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CELL_INSERT_MENU_ID); + + return mergeMenuConfigs({ id: CELL_INSERT_MENU_ID, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.SUBITEMS, title: 'rightClick.insert', icon: 'Insert', positions: [MenuPosition.CONTEXT_MENU], - }; + }, menuItemConfig); } export function InsertRowBeforeMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); const selectionManager = accessor.get(SelectionManagerService); - return { + const menuItemConfig = menuService.getMenuConfig(InsertRowBeforeCommand.id); + + return mergeMenuConfigs({ id: InsertRowBeforeCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.insertRowBefore', @@ -78,12 +95,16 @@ export function InsertRowBeforeMenuItemFactory(accessor: IAccessor): IMenuButton const selections = selectionManager.getSelections(); observer.next(selections?.length !== 1); }), - }; + }, menuItemConfig); } export function InsertRowAfterMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const selectionManager = accessor.get(SelectionManagerService); - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(InsertRowAfterCommand.id); + + return mergeMenuConfigs({ id: InsertRowAfterCommand.id, type: MenuItemType.BUTTON, positions: [ROW_INSERT_MENU_ID], @@ -94,12 +115,16 @@ export function InsertRowAfterMenuItemFactory(accessor: IAccessor): IMenuButtonI const selections = selectionManager.getSelections(); observer.next(selections?.length !== 1); }), - }; + }, menuItemConfig); } export function InsertColBeforeMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const selectionManager = accessor.get(SelectionManagerService); - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(InsertColBeforeCommand.id); + + return mergeMenuConfigs({ id: InsertColBeforeCommand.id, type: MenuItemType.BUTTON, positions: [COL_INSERT_MENU_ID, CELL_INSERT_MENU_ID], @@ -110,12 +135,16 @@ export function InsertColBeforeMenuItemFactory(accessor: IAccessor): IMenuButton const selections = selectionManager.getSelections(); observer.next(selections?.length !== 1); }), - }; + }, menuItemConfig); } export function InsertColAfterMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const selectionManager = accessor.get(SelectionManagerService); - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(InsertColAfterCommand.id); + + return mergeMenuConfigs({ id: InsertColAfterCommand.id, type: MenuItemType.BUTTON, positions: [COL_INSERT_MENU_ID], @@ -126,25 +155,33 @@ export function InsertColAfterMenuItemFactory(accessor: IAccessor): IMenuButtonI const selections = selectionManager.getSelections(); observer.next(selections?.length !== 1); }), - }; + }, menuItemConfig); } -export function InsertRangeMoveRightMenuItemFactory(): IMenuButtonItem { - return { +export function InsertRangeMoveRightMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(InsertRangeMoveRightConfirmCommand.id); + + return mergeMenuConfigs({ id: InsertRangeMoveRightConfirmCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.moveRight', icon: 'InsertCellShiftRight', positions: [CELL_INSERT_MENU_ID], - }; + }, menuItemConfig); } -export function InsertRangeMoveDownMenuItemFactory(): IMenuButtonItem { - return { +export function InsertRangeMoveDownMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(InsertRangeMoveDownConfirmCommand.id); + + return mergeMenuConfigs({ id: InsertRangeMoveDownConfirmCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.moveDown', icon: 'InsertCellDown', positions: [CELL_INSERT_MENU_ID], - }; + }, menuItemConfig); } diff --git a/packages/sheets-ui/src/controllers/menu/menu.ts b/packages/sheets-ui/src/controllers/menu/menu.ts index e09592c328f..97c0dc7d659 100644 --- a/packages/sheets-ui/src/controllers/menu/menu.ts +++ b/packages/sheets-ui/src/controllers/menu/menu.ts @@ -16,6 +16,7 @@ import { BooleanNumber, + DEFAULT_STYLES, DOCS_NORMAL_EDITOR_UNIT_ID_KEY, EDITOR_ACTIVATED, FOCUSING_SHEET, @@ -66,9 +67,11 @@ import { FONT_SIZE_LIST, getMenuHiddenObservable, IClipboardInterfaceService, + IMenuService, MenuGroup, MenuItemType, MenuPosition, + mergeMenuConfigs, PasteCommand, } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; @@ -115,8 +118,11 @@ export enum SheetMenuPosition { export function FormatPainterMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const formatPainterService = accessor.get(IFormatPainterService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetOnceFormatPainterCommand.id); + + return mergeMenuConfigs({ id: SetOnceFormatPainterCommand.id, subId: SetInfiniteFormatPainterCommand.id, group: MenuGroup.TOOLBAR_FORMAT, @@ -141,7 +147,7 @@ export function FormatPainterMenuItemFactory(accessor: IAccessor): IMenuButtonIt }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export function BoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { @@ -149,8 +155,11 @@ export function BoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const contextService = accessor.get(IContextService); const selectionManagerService = accessor.get(SelectionManagerService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetRangeBoldCommand.id); - return { + return mergeMenuConfigs({ id: SetRangeBoldCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -205,7 +214,7 @@ export function BoldMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - }; + }, menuItemConfig); } export function ItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { @@ -213,8 +222,11 @@ export function ItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); const contextService = accessor.get(IContextService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetRangeItalicCommand.id); + + return mergeMenuConfigs({ id: SetRangeItalicCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -261,7 +273,7 @@ export function ItalicMenuItemFactory(accessor: IAccessor): IMenuButtonItem { return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - }; + }, menuItemConfig); } export function UnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { @@ -269,8 +281,11 @@ export function UnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); const contextService = accessor.get(IContextService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetRangeUnderlineCommand.id); - return { + return mergeMenuConfigs({ id: SetRangeUnderlineCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -318,7 +333,7 @@ export function UnderlineMenuItemFactory(accessor: IAccessor): IMenuButtonItem { })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - }; + }, menuItemConfig); } export function StrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonItem { @@ -326,8 +341,11 @@ export function StrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonIt const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); const contextService = accessor.get(IContextService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetRangeStrickThroughCommand.id); + + return mergeMenuConfigs({ id: SetRangeStrickThroughCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.BUTTON, @@ -378,16 +396,19 @@ export function StrikeThroughMenuItemFactory(accessor: IAccessor): IMenuButtonIt return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - }; + }, menuItemConfig); } export function FontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); - const defaultValue = FONT_FAMILY_LIST[0].value; + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetRangeFontFamilyCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? String(menuItemConfig?.defaultValue) : DEFAULT_STYLES.ff; - return { + return mergeMenuConfigs({ id: SetRangeFontFamilyCommand.id, tooltip: 'toolbar.font', group: MenuGroup.TOOLBAR_FORMAT, @@ -430,7 +451,7 @@ export function FontFamilySelectorMenuItemFactory(accessor: IAccessor): IMenuSel return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - }; + }, menuItemConfig); } export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { @@ -439,9 +460,12 @@ export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelec const selectionManagerService = accessor.get(SelectionManagerService); const contextService = accessor.get(IContextService); const disabled$ = getCurrentSheetDisabled$(accessor); - const DEFAULT_SIZE = 11; + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetRangeFontSizeCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? Number(menuItemConfig?.defaultValue) : DEFAULT_STYLES.fs; + + return mergeMenuConfigs({ id: SetRangeFontSizeCommand.id, group: MenuGroup.TOOLBAR_FORMAT, type: MenuItemType.SELECTOR, @@ -457,7 +481,7 @@ export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelec positions: [MenuPosition.TOOLBAR_START], selections: FONT_SIZE_LIST, disabled$, - value$: deriveStateFromActiveSheet$(univerInstanceService, DEFAULT_SIZE, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { const disposable = commandService.onCommandExecuted((c) => { const id = c.id; if (id === SetRangeValuesMutation.id || id === SetSelectionsOperation.id || id === SetWorksheetActiveOperation.id) { @@ -467,7 +491,7 @@ export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelec const range = worksheet.getRange(primary.startRow, primary.startColumn); fs = range?.getFontSize(); } - subscriber.next(fs ?? DEFAULT_SIZE); + subscriber.next(fs ?? defaultValue); } if ( @@ -482,7 +506,7 @@ export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelec } const fs = textRun.ts?.fs; - subscriber.next(fs ?? DEFAULT_SIZE); + subscriber.next(fs ?? defaultValue); } }); @@ -492,30 +516,37 @@ export function FontSizeSelectorMenuItemFactory(accessor: IAccessor): IMenuSelec const range = worksheet.getRange(primary.startRow, primary.startColumn); fs = range?.getFontSize(); } - subscriber.next(fs ?? DEFAULT_SIZE); + subscriber.next(fs ?? defaultValue); return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), - }; + }, menuItemConfig); } export function ResetTextColorMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ResetTextColorCommand.id); + + return mergeMenuConfigs({ id: ResetTextColorCommand.id, type: MenuItemType.BUTTON, title: 'toolbar.resetColor', icon: 'NoColor', positions: SetRangeTextColorCommand.id, disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export function TextColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); const themeService = accessor.get(ThemeService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetRangeTextColorCommand.id); + + return mergeMenuConfigs({ id: SetRangeTextColorCommand.id, icon: 'FontColor', tooltip: 'toolbar.textColor.main', @@ -532,38 +563,45 @@ export function TextColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSele }, ], value$: new Observable((subscriber) => { - const defaultColor = themeService.getCurrentTheme().textColor; + const defaultValue = menuItemConfig?.defaultValue ? String(menuItemConfig?.defaultValue) : themeService.getCurrentTheme().textColor; const disposable = commandService.onCommandExecuted((c) => { if (c.id === SetRangeTextColorCommand.id) { const color = (c.params as { value: string }).value; - subscriber.next(color ?? defaultColor); + subscriber.next(color ?? defaultValue); } }); - subscriber.next(defaultColor); + subscriber.next(defaultValue); return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export function ResetBackgroundColorMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ResetBackgroundColorCommand.id); + + return mergeMenuConfigs({ id: ResetBackgroundColorCommand.id, type: MenuItemType.BUTTON, title: 'toolbar.resetColor', icon: 'NoColor', positions: SetBackgroundColorCommand.id, disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export function BackgroundColorSelectorMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const commandService = accessor.get(ICommandService); const themeService = accessor.get(ThemeService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetBackgroundColorCommand.id); - return { + return mergeMenuConfigs({ id: SetBackgroundColorCommand.id, tooltip: 'toolbar.fillColor.main', group: MenuGroup.TOOLBAR_FORMAT, @@ -579,20 +617,20 @@ export function BackgroundColorSelectorMenuItemFactory(accessor: IAccessor): IMe }, ], value$: new Observable((subscriber) => { - const defaultColor = themeService.getCurrentTheme().primaryColor; + const defaultValue = menuItemConfig?.defaultValue ? String(menuItemConfig?.defaultValue) : themeService.getCurrentTheme().primaryColor; const disposable = commandService.onCommandExecuted((c) => { if (c.id === SetBackgroundColorCommand.id) { const color = (c.params as { value: string }).value; - subscriber.next(color ?? defaultColor); + subscriber.next(color ?? defaultValue); } }); - subscriber.next(defaultColor); + subscriber.next(defaultValue); return disposable.dispose; }), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export const HORIZONTAL_ALIGN_CHILDREN = [ @@ -616,8 +654,12 @@ export const HORIZONTAL_ALIGN_CHILDREN = [ export function HorizontalAlignMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetHorizontalTextAlignCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? (menuItemConfig?.defaultValue as unknown as HorizontalAlign) : HorizontalAlign.LEFT; - return { + return mergeMenuConfigs({ id: SetHorizontalTextAlignCommand.id, icon: HORIZONTAL_ALIGN_CHILDREN[0].icon, positions: [MenuPosition.TOOLBAR_START], @@ -625,7 +667,7 @@ export function HorizontalAlignMenuItemFactory(accessor: IAccessor): IMenuSelect group: MenuGroup.TOOLBAR_LAYOUT, type: MenuItemType.SELECTOR, selections: HORIZONTAL_ALIGN_CHILDREN, - value$: deriveStateFromActiveSheet$(univerInstanceService, HorizontalAlign.LEFT, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { const disposable = accessor.get(ICommandService).onCommandExecuted((c) => { const id = c.id; if (id !== SetHorizontalTextAlignCommand.id && id !== SetSelectionsOperation.id && id !== SetWorksheetActiveOperation.id) { @@ -639,7 +681,7 @@ export function HorizontalAlignMenuItemFactory(accessor: IAccessor): IMenuSelect ha = range?.getHorizontalAlignment(); } - subscriber.next(ha ?? HorizontalAlign.LEFT); + subscriber.next(ha ?? defaultValue); }); const primary = selectionManagerService.getLast()?.primary; @@ -649,13 +691,13 @@ export function HorizontalAlignMenuItemFactory(accessor: IAccessor): IMenuSelect ha = range?.getHorizontalAlignment(); } - subscriber.next(ha ?? HorizontalAlign.LEFT); + subscriber.next(ha ?? defaultValue); return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export const VERTICAL_ALIGN_CHILDREN = [ @@ -679,8 +721,12 @@ export const VERTICAL_ALIGN_CHILDREN = [ export function VerticalAlignMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetVerticalTextAlignCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? (menuItemConfig?.defaultValue as unknown as VerticalAlign) : VerticalAlign.BOTTOM; + + return mergeMenuConfigs({ id: SetVerticalTextAlignCommand.id, icon: VERTICAL_ALIGN_CHILDREN[2].icon, tooltip: 'toolbar.verticalAlignMode.main', @@ -688,7 +734,7 @@ export function VerticalAlignMenuItemFactory(accessor: IAccessor): IMenuSelector type: MenuItemType.SELECTOR, positions: [MenuPosition.TOOLBAR_START], selections: VERTICAL_ALIGN_CHILDREN, - value$: deriveStateFromActiveSheet$(univerInstanceService, VerticalAlign.BOTTOM, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { const disposable = accessor.get(ICommandService).onCommandExecuted((c) => { const id = c.id; if (id !== SetVerticalTextAlignCommand.id && id !== SetSelectionsOperation.id && id !== SetWorksheetActiveOperation.id) { @@ -702,7 +748,7 @@ export function VerticalAlignMenuItemFactory(accessor: IAccessor): IMenuSelector va = range?.getVerticalAlignment(); } - subscriber.next(va ?? VerticalAlign.BOTTOM); + subscriber.next(va ?? defaultValue); }); const primary = selectionManagerService.getLast()?.primary; @@ -712,13 +758,13 @@ export function VerticalAlignMenuItemFactory(accessor: IAccessor): IMenuSelector va = range?.getVerticalAlignment(); } - subscriber.next(va ?? VerticalAlign.BOTTOM); + subscriber.next(va ?? defaultValue); return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export const TEXT_WRAP_CHILDREN = [ @@ -742,8 +788,12 @@ export const TEXT_WRAP_CHILDREN = [ export function WrapTextMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const selectionManagerService = accessor.get(SelectionManagerService); const univerInstanceService = accessor.get(IUniverInstanceService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetTextWrapCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? (menuItemConfig?.defaultValue as unknown as WrapStrategy) : WrapStrategy.OVERFLOW; - return { + return mergeMenuConfigs({ id: SetTextWrapCommand.id, tooltip: 'toolbar.textWrapMode.main', icon: TEXT_WRAP_CHILDREN[0].icon, @@ -751,7 +801,7 @@ export function WrapTextMenuItemFactory(accessor: IAccessor): IMenuSelectorItem< type: MenuItemType.SELECTOR, positions: [MenuPosition.TOOLBAR_START], selections: TEXT_WRAP_CHILDREN, - value$: deriveStateFromActiveSheet$(univerInstanceService, WrapStrategy.OVERFLOW, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { const disposable = accessor.get(ICommandService).onCommandExecuted((c) => { const id = c.id; if (id !== SetTextWrapCommand.id && id !== SetSelectionsOperation.id && id !== SetWorksheetActiveOperation.id) { @@ -765,7 +815,7 @@ export function WrapTextMenuItemFactory(accessor: IAccessor): IMenuSelectorItem< ws = range?.getWrapStrategy(); } - subscriber.next(ws ?? WrapStrategy.OVERFLOW); + subscriber.next(ws ?? defaultValue); }); const primary = selectionManagerService.getLast()?.primary; @@ -775,13 +825,13 @@ export function WrapTextMenuItemFactory(accessor: IAccessor): IMenuSelectorItem< ws = range?.getWrapStrategy(); } - subscriber.next(ws ?? WrapStrategy.OVERFLOW); + subscriber.next(ws ?? defaultValue); return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } export const TEXT_ROTATE_CHILDREN = [ @@ -820,8 +870,12 @@ export const TEXT_ROTATE_CHILDREN = [ export function TextRotateMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const selectionManagerService = accessor.get(SelectionManagerService); const univerInstanceService = accessor.get(IUniverInstanceService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetTextRotationCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? Number(menuItemConfig?.defaultValue) : 0; + + return mergeMenuConfigs({ id: SetTextRotationCommand.id, tooltip: 'toolbar.textRotateMode.main', icon: TEXT_ROTATE_CHILDREN[0].icon, @@ -829,7 +883,7 @@ export function TextRotateMenuItemFactory(accessor: IAccessor): IMenuSelectorIte type: MenuItemType.SELECTOR, selections: TEXT_ROTATE_CHILDREN, positions: [MenuPosition.TOOLBAR_START], - value$: deriveStateFromActiveSheet$(univerInstanceService, 0, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { const disposable = accessor.get(ICommandService).onCommandExecuted((c) => { const id = c.id; if (id !== SetTextRotationCommand.id && id !== SetSelectionsOperation.id && id !== SetWorksheetActiveOperation.id) { @@ -846,7 +900,7 @@ export function TextRotateMenuItemFactory(accessor: IAccessor): IMenuSelectorIte if (tr?.v === BooleanNumber.TRUE) { subscriber.next('v'); } else { - subscriber.next((tr && tr.a) ?? 0); + subscriber.next((tr && tr.a) ?? defaultValue); } }); @@ -860,14 +914,14 @@ export function TextRotateMenuItemFactory(accessor: IAccessor): IMenuSelectorIte if (tr?.v === BooleanNumber.TRUE) { subscriber.next('v'); } else { - subscriber.next((tr && tr.a) ?? 0); + subscriber.next((tr && tr.a) ?? defaultValue); } return disposable.dispose; })), hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } // #region - copy cut paste @@ -877,8 +931,12 @@ function menuClipboardDisabledObservable(injector: IAccessor): Observable subscriber.next(!injector.get(IClipboardInterfaceService).supportClipboard)); } -export function CopyMenuItemFactory(): IMenuButtonItem { - return { +export function CopyMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CopyCommand.id); + + return mergeMenuConfigs({ id: CopyCommand.id, group: MenuGroup.CONTEXT_MENU_FORMAT, type: MenuItemType.BUTTON, @@ -889,11 +947,15 @@ export function CopyMenuItemFactory(): IMenuButtonItem { SheetMenuPosition.COL_HEADER_CONTEXT_MENU, SheetMenuPosition.ROW_HEADER_CONTEXT_MENU, ], - }; + }, menuItemConfig); } -export function CutMenuItemFactory(): IMenuButtonItem { - return { +export function CutMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CutCommand.id); + + return mergeMenuConfigs({ id: CutCommand.id, group: MenuGroup.CONTEXT_MENU_FORMAT, type: MenuItemType.BUTTON, @@ -903,11 +965,15 @@ export function CutMenuItemFactory(): IMenuButtonItem { SheetMenuPosition.COL_HEADER_CONTEXT_MENU, SheetMenuPosition.ROW_HEADER_CONTEXT_MENU, ], - }; + }, menuItemConfig); } export function PasteMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(PasteCommand.id); + + return mergeMenuConfigs({ id: PasteCommand.id, group: MenuGroup.CONTEXT_MENU_FORMAT, type: MenuItemType.BUTTON, @@ -919,12 +985,16 @@ export function PasteMenuItemFactory(accessor: IAccessor): IMenuButtonItem { SheetMenuPosition.COL_HEADER_CONTEXT_MENU, SheetMenuPosition.ROW_HEADER_CONTEXT_MENU, ], - }; + }, menuItemConfig); } export const PASTE_SPECIAL_MENU_ID = 'sheet.menu.paste-special'; export function PasteSpacialMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(PASTE_SPECIAL_MENU_ID); + + return mergeMenuConfigs({ id: PASTE_SPECIAL_MENU_ID, group: MenuGroup.CONTEXT_MENU_FORMAT, type: MenuItemType.SUBITEMS, @@ -936,156 +1006,211 @@ export function PasteSpacialMenuItemFactory(accessor: IAccessor): IMenuSelectorI SheetMenuPosition.COL_HEADER_CONTEXT_MENU, SheetMenuPosition.ROW_HEADER_CONTEXT_MENU, ], - }; + }, menuItemConfig); } export function PasteValueMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SheetPasteValueCommand.id); + + return mergeMenuConfigs({ id: SheetPasteValueCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.pasteValue', positions: [PASTE_SPECIAL_MENU_ID], disabled$: menuClipboardDisabledObservable(accessor), - }; + }, menuItemConfig); } export function PasteFormatMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SheetPasteFormatCommand.id); + + return mergeMenuConfigs({ id: SheetPasteFormatCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.pasteFormat', positions: [PASTE_SPECIAL_MENU_ID], disabled$: menuClipboardDisabledObservable(accessor), - }; + }, menuItemConfig); } export function PasteColWidthMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SheetPasteColWidthCommand.id); + + return mergeMenuConfigs({ id: SheetPasteColWidthCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.pasteColWidth', positions: [PASTE_SPECIAL_MENU_ID], disabled$: menuClipboardDisabledObservable(accessor), - }; + }, menuItemConfig); } export function PasteBesidesBorderMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SheetPasteBesidesBorderCommand.id); + + return mergeMenuConfigs({ id: SheetPasteBesidesBorderCommand.id, type: MenuItemType.BUTTON, title: 'rightClick.pasteBesidesBorder', positions: [PASTE_SPECIAL_MENU_ID], disabled$: menuClipboardDisabledObservable(accessor), - }; + }, menuItemConfig); } -export function FitContentMenuItemFactory(): IMenuButtonItem { - return { +export function FitContentMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetWorksheetRowIsAutoHeightCommand.id); + + return mergeMenuConfigs({ id: SetWorksheetRowIsAutoHeightCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.ROW_HEADER_CONTEXT_MENU], icon: 'AutoHeight', title: 'rightClick.fitContent', - }; + }, menuItemConfig); } export const SHEET_FROZEN_MENU_ID = 'sheet.menu.sheet-frozen'; -export function SheetFrozenMenuItemFactory(): IMenuSelectorItem { - return { +export function SheetFrozenMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SHEET_FROZEN_MENU_ID); + + return mergeMenuConfigs({ id: SHEET_FROZEN_MENU_ID, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.SUBITEMS, title: 'rightClick.freeze', icon: 'FreezeToSelectedSingle', positions: [MenuPosition.CONTEXT_MENU], - }; + }, menuItemConfig); } export const SHEET_FROZEN_HEADER_MENU_ID = 'sheet.header-menu.sheet-frozen'; -export function SheetFrozenHeaderMenuItemFactory(): IMenuSelectorItem { - return { +export function SheetFrozenHeaderMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SHEET_FROZEN_HEADER_MENU_ID); + + return mergeMenuConfigs({ id: SHEET_FROZEN_HEADER_MENU_ID, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.SUBITEMS, title: 'rightClick.freeze', icon: 'FreezeToSelectedSingle', positions: [SheetMenuPosition.ROW_HEADER_CONTEXT_MENU, SheetMenuPosition.COL_HEADER_CONTEXT_MENU], - }; + }, menuItemConfig); } -export function FrozenMenuItemFactory(): IMenuButtonItem { - return { +export function FrozenMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetSelectionFrozenCommand.id); + + return mergeMenuConfigs({ id: SetSelectionFrozenCommand.id, type: MenuItemType.BUTTON, positions: [SHEET_FROZEN_MENU_ID, SHEET_FROZEN_HEADER_MENU_ID], title: 'rightClick.freeze', icon: 'FreezeToSelectedSingle', - }; + }, menuItemConfig); } -export function FrozenRowMenuItemFactory(): IMenuButtonItem { - return { +export function FrozenRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetRowFrozenCommand.id); + + return mergeMenuConfigs({ id: SetRowFrozenCommand.id, type: MenuItemType.BUTTON, positions: [SHEET_FROZEN_MENU_ID], title: 'rightClick.freezeRow', icon: 'FreezeRowSingle', - }; + }, menuItemConfig); } -export function FrozenColMenuItemFactory(): IMenuButtonItem { - return { +export function FrozenColMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetColumnFrozenCommand.id); + + return mergeMenuConfigs({ id: SetColumnFrozenCommand.id, type: MenuItemType.BUTTON, positions: [SHEET_FROZEN_MENU_ID], title: 'rightClick.freezeCol', icon: 'FreezeColumnSingle', - }; + }, menuItemConfig); } -export function CancelFrozenMenuItemFactory(): IMenuButtonItem { - return { +export function CancelFrozenMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CancelFrozenCommand.id); + + return mergeMenuConfigs({ id: CancelFrozenCommand.id, type: MenuItemType.BUTTON, positions: [SHEET_FROZEN_MENU_ID, SHEET_FROZEN_HEADER_MENU_ID], title: 'rightClick.cancelFreeze', icon: 'CancelFreezeSingle', - }; + }, menuItemConfig); } -export function HideRowMenuItemFactory(): IMenuButtonItem { - return { +export function HideRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(HideRowConfirmCommand.id); + + return mergeMenuConfigs({ id: HideRowConfirmCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.ROW_HEADER_CONTEXT_MENU], icon: 'Hide', title: 'rightClick.hideSelectedRow', - }; + }, menuItemConfig); } -export function HideColMenuItemFactory(): IMenuButtonItem { - return { +export function HideColMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(HideColConfirmCommand.id); + + return mergeMenuConfigs({ id: HideColConfirmCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.COL_HEADER_CONTEXT_MENU], icon: 'Hide', title: 'rightClick.hideSelectedColumn', - }; + }, menuItemConfig); } export function ShowRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); - + const menuService = accessor.get(IMenuService); const commandService = accessor.get(ICommandService); + const affectedCommands = [SetSelectionsOperation, SetRowHiddenMutation, SetRowVisibleMutation].map((c) => c.id); - return { + const menuItemConfig = menuService.getMenuConfig(SetSelectedRowsVisibleCommand.id); + + return mergeMenuConfigs({ id: SetSelectedRowsVisibleCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, @@ -1111,7 +1236,7 @@ export function ShowRowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { subscriber.next(!hasHiddenRowsInSelections()); return () => disposable.dispose(); })), - }; + }, menuItemConfig); } export function ShowColMenuItemFactory(accessor: IAccessor): IMenuButtonItem { @@ -1119,8 +1244,11 @@ export function ShowColMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const selectionManagerService = accessor.get(SelectionManagerService); const commandService = accessor.get(ICommandService); const affectedCommands = [SetSelectionsOperation, SetColHiddenMutation, SetColVisibleMutation].map((c) => c.id); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(SetSelectedColsVisibleCommand.id); + + return mergeMenuConfigs({ id: SetSelectedColsVisibleCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, @@ -1147,15 +1275,19 @@ export function ShowColMenuItemFactory(accessor: IAccessor): IMenuButtonItem { subscriber.next(!hasHiddenColsInSelections()); return () => disposable.dispose(); })), - }; + }, menuItemConfig); } export function SetRowHeightMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetRowHeightCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? Number(menuItemConfig?.defaultValue) : 0; - return { + return mergeMenuConfigs({ id: SetRowHeightCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, @@ -1170,10 +1302,10 @@ export function SetRowHeightMenuItemFactory(accessor: IAccessor): IMenuButtonIte max: 1000, }, }, - value$: deriveStateFromActiveSheet$(univerInstanceService, 0, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { function update() { const primary = selectionManagerService.getLast()?.primary; - const rowHeight = primary ? worksheet.getRowHeight(primary.startRow) : 0; + const rowHeight = primary ? worksheet.getRowHeight(primary.startRow) : defaultValue; subscriber.next(rowHeight); } @@ -1187,15 +1319,19 @@ export function SetRowHeightMenuItemFactory(accessor: IAccessor): IMenuButtonIte update(); return disposable.dispose; })), - }; + }, menuItemConfig); } export function SetColWidthMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const commandService = accessor.get(ICommandService); const univerInstanceService = accessor.get(IUniverInstanceService); const selectionManagerService = accessor.get(SelectionManagerService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetColWidthCommand.id); + const defaultValue = menuItemConfig?.defaultValue ? Number(menuItemConfig?.defaultValue) : 0; - return { + return mergeMenuConfigs({ id: SetColWidthCommand.id, group: MenuGroup.CONTEXT_MENU_LAYOUT, type: MenuItemType.BUTTON, @@ -1210,10 +1346,10 @@ export function SetColWidthMenuItemFactory(accessor: IAccessor): IMenuButtonItem max: 1000, }, }, - value$: deriveStateFromActiveSheet$(univerInstanceService, 0, ({ worksheet }) => new Observable((subscriber) => { + value$: deriveStateFromActiveSheet$(univerInstanceService, defaultValue, ({ worksheet }) => new Observable((subscriber) => { function update() { const primary = selectionManagerService.getLast()?.primary; - let colWidth: number = 0; + let colWidth: number = defaultValue; if (primary != null) { colWidth = worksheet.getColumnWidth(primary.startColumn); } @@ -1231,7 +1367,7 @@ export function SetColWidthMenuItemFactory(accessor: IAccessor): IMenuButtonItem update(); return disposable.dispose; })), - }; + }, menuItemConfig); } function getFontStyleAtCursor(accessor: IAccessor) { diff --git a/packages/sheets-ui/src/controllers/menu/merge.menu.ts b/packages/sheets-ui/src/controllers/menu/merge.menu.ts index 155fdd831bf..aa706b78637 100644 --- a/packages/sheets-ui/src/controllers/menu/merge.menu.ts +++ b/packages/sheets-ui/src/controllers/menu/merge.menu.ts @@ -17,7 +17,7 @@ import { UniverInstanceType } from '@univerjs/core'; import { getCurrentSheetDisabled$, RemoveWorksheetMergeCommand } from '@univerjs/sheets'; import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; -import { getMenuHiddenObservable, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { combineLatestWith, map } from 'rxjs'; @@ -32,8 +32,11 @@ import { getSheetSelectionsDisabled$ } from '../utils/selections-tools'; export function CellMergeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const disabled$ = getCurrentSheetDisabled$(accessor); const selectionsHasCross$ = getSheetSelectionsDisabled$(accessor); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(AddWorksheetMergeCommand.id); + + return mergeMenuConfigs({ id: AddWorksheetMergeCommand.id, icon: 'MergeAllSingle', tooltip: 'toolbar.mergeCell.main', @@ -46,41 +49,61 @@ export function CellMergeMenuItemFactory(accessor: IAccessor): IMenuSelectorItem combineLatestWith(selectionsHasCross$), map(([disable, hasCross]) => disable || hasCross) ), - }; + }, menuItemConfig); } + export function CellMergeAllMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(AddWorksheetMergeAllCommand.id); + + return mergeMenuConfigs({ id: AddWorksheetMergeAllCommand.id, type: MenuItemType.BUTTON, title: 'merge.all', icon: 'MergeAllSingle', positions: [AddWorksheetMergeCommand.id], - }; + }, menuItemConfig); } + export function CellMergeVerticalMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(AddWorksheetMergeVerticalCommand.id); + + return mergeMenuConfigs({ id: AddWorksheetMergeVerticalCommand.id, type: MenuItemType.BUTTON, title: 'merge.vertical', icon: 'VerticalIntegrationSingle', positions: [AddWorksheetMergeCommand.id], - }; + }, menuItemConfig); } + export function CellMergeHorizontalMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(AddWorksheetMergeHorizontalCommand.id); + + return mergeMenuConfigs({ id: AddWorksheetMergeHorizontalCommand.id, type: MenuItemType.BUTTON, title: 'merge.horizontal', icon: 'HorizontalMergeSingle', positions: [AddWorksheetMergeCommand.id], - }; + }, menuItemConfig); } + export function CellMergeCancelMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(RemoveWorksheetMergeCommand.id); + + return mergeMenuConfigs({ id: RemoveWorksheetMergeCommand.id, type: MenuItemType.BUTTON, title: 'merge.cancel', icon: 'CancelMergeSingle', positions: [AddWorksheetMergeCommand.id], - }; + }, menuItemConfig); } diff --git a/packages/sheets-ui/src/controllers/menu/sheet.menu.ts b/packages/sheets-ui/src/controllers/menu/sheet.menu.ts index cd15becfcb5..9cdeab4dacf 100644 --- a/packages/sheets-ui/src/controllers/menu/sheet.menu.ts +++ b/packages/sheets-ui/src/controllers/menu/sheet.menu.ts @@ -26,7 +26,7 @@ import { SetWorksheetShowCommand, } from '@univerjs/sheets'; import type { IMenuButtonItem, IMenuSelectorItem } from '@univerjs/ui'; -import { MenuItemType } from '@univerjs/ui'; +import { IMenuService, MenuItemType, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { Observable } from 'rxjs'; @@ -39,7 +39,11 @@ import { SheetMenuPosition } from './menu'; export function DeleteSheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const commandService = accessor.get(ICommandService); - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(RemoveSheetConfirmCommand.id); + + return mergeMenuConfigs({ id: RemoveSheetConfirmCommand.id, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.SHEET_BAR], @@ -64,29 +68,41 @@ export function DeleteSheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem subscriber.next(false); return disposable.dispose; }), - }; + }, menuItemConfig); } -export function CopySheetMenuItemFactory(): IMenuButtonItem { - return { +export function CopySheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(CopySheetCommand.id); + + return mergeMenuConfigs({ id: CopySheetCommand.id, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.SHEET_BAR], title: 'sheetConfig.copy', - }; + }, menuItemConfig); } -export function RenameSheetMenuItemFactory(): IMenuButtonItem { - return { +export function RenameSheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(RenameSheetOperation.id); + + return mergeMenuConfigs({ id: RenameSheetOperation.id, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.SHEET_BAR], title: 'sheetConfig.rename', - }; + }, menuItemConfig); } -export function ChangeColorSheetMenuItemFactory(): IMenuSelectorItem { - return { +export function ChangeColorSheetMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetTabColorCommand.id); + + return mergeMenuConfigs({ id: SetTabColorCommand.id, title: 'sheetConfig.changeColor', positions: [SheetMenuPosition.SHEET_BAR], @@ -99,13 +115,17 @@ export function ChangeColorSheetMenuItemFactory(): IMenuSelectorItem { }, }, ], - }; + }, menuItemConfig); } export function HideSheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const commandService = accessor.get(ICommandService); - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetWorksheetHideCommand.id); + + return mergeMenuConfigs({ id: SetWorksheetHideCommand.id, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.SHEET_BAR], @@ -130,19 +150,23 @@ export function HideSheetMenuItemFactory(accessor: IAccessor): IMenuButtonItem { subscriber.next(false); return disposable.dispose; }), - }; + }, menuItemConfig); } export function UnHideSheetMenuItemFactory(accessor: IAccessor): IMenuSelectorItem { const univerInstanceService = accessor.get(IUniverInstanceService); const commandService = accessor.get(ICommandService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(SetWorksheetShowCommand.id); + const workbook = univerInstanceService.getCurrentUnitForType(UniverInstanceType.UNIVER_SHEET)!; const hiddenList = workbook.getHiddenWorksheets().map((s) => ({ label: workbook.getSheetBySheetId(s)?.getName() || '', value: s, })); - return { + return mergeMenuConfigs({ id: SetWorksheetShowCommand.id, type: MenuItemType.SELECTOR, positions: [SheetMenuPosition.SHEET_BAR], @@ -172,13 +196,17 @@ export function UnHideSheetMenuItemFactory(accessor: IAccessor): IMenuSelectorIt subscriber.next(hiddenList); return disposable.dispose; }), - }; + }, menuItemConfig); } export function ShowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const univerInstanceService = accessor.get(IUniverInstanceService); const commandService = accessor.get(ICommandService); - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ShowMenuListCommand.id); + + return mergeMenuConfigs({ id: ShowMenuListCommand.id, type: MenuItemType.BUTTON, positions: [SheetMenuPosition.SHEET_BAR], @@ -201,5 +229,5 @@ export function ShowMenuItemFactory(accessor: IAccessor): IMenuButtonItem { subscriber.next(false); return disposable.dispose; }), - }; + }, menuItemConfig); } diff --git a/packages/sheets-ui/src/controllers/sheet-ui.controller.ts b/packages/sheets-ui/src/controllers/sheet-ui.controller.ts index 32037f2396a..e9c8217fe98 100644 --- a/packages/sheets-ui/src/controllers/sheet-ui.controller.ts +++ b/packages/sheets-ui/src/controllers/sheet-ui.controller.ts @@ -23,7 +23,7 @@ import { SetStrikeThroughCommand, SetUnderlineCommand, } from '@univerjs/sheets'; -import type { IMenuItemFactory } from '@univerjs/ui'; +import type { IMenuItemFactory, MenuConfig } from '@univerjs/ui'; import { ComponentManager, DesktopUIPart, ILayoutService, IMenuService, IShortcutService, IUIPartsService } from '@univerjs/ui'; import { Inject, Injector } from '@wendellhu/redi'; import { connectInjector } from '@wendellhu/redi/react-bindings'; @@ -228,9 +228,16 @@ import { ZoomOutShortcutItem, } from './shortcuts/view.shortcut'; +export interface IUniverSheetsUIConfig { + menu: MenuConfig; +} + +export const DefaultSheetUiConfig = {}; + @OnLifecycle(LifecycleStages.Ready, SheetUIController) export class SheetUIController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @Inject(ComponentManager) private readonly _componentManager: ComponentManager, @ILayoutService private readonly _layoutService: ILayoutService, @@ -247,6 +254,7 @@ export class SheetUIController extends Disposable { private _init(): void { this._initCustomComponents(); this._initCommands(); + this._initMenuConfigs(); this._initMenus(); this._initShortcuts(); this._initWorkbenchParts(); @@ -328,6 +336,13 @@ export class SheetUIController extends Disposable { }); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initMenus(): void { ( [ diff --git a/packages/sheets-ui/src/sheets-ui-plugin.ts b/packages/sheets-ui/src/sheets-ui-plugin.ts index 7356b75315c..f0de83474cf 100644 --- a/packages/sheets-ui/src/sheets-ui-plugin.ts +++ b/packages/sheets-ui/src/sheets-ui-plugin.ts @@ -15,7 +15,7 @@ */ import type { Workbook } from '@univerjs/core'; -import { IUniverInstanceService, LocaleService, Plugin, UniverInstanceType } from '@univerjs/core'; +import { IUniverInstanceService, LocaleService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; import { filter } from 'rxjs/operators'; @@ -37,7 +37,8 @@ import { HeaderUnhideRenderController } from './controllers/render-controllers/h import { MarkSelectionRenderController } from './controllers/mark-selection.controller'; import { SelectionRenderController } from './controllers/render-controllers/selection.render-controller'; import { SheetRenderController } from './controllers/sheet-render.controller'; -import { SheetUIController } from './controllers/sheet-ui.controller'; +import type { IUniverSheetsUIConfig } from './controllers/sheet-ui.controller'; +import { DefaultSheetUiConfig, SheetUIController } from './controllers/sheet-ui.controller'; import { StatusBarController } from './controllers/status-bar.controller'; import { zhCN } from './locale'; import { AutoFillService, IAutoFillService } from './services/auto-fill/auto-fill.service'; @@ -77,7 +78,7 @@ export class UniverSheetsUIPlugin extends Plugin { static override type = UniverInstanceType.UNIVER_SHEET; constructor( - _config: undefined, + private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService, @IRenderManagerService private readonly _renderManagerService: IRenderManagerService, @@ -85,9 +86,8 @@ export class UniverSheetsUIPlugin extends Plugin { ) { super(); - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); + this._config = Tools.deepMerge({}, DefaultSheetUiConfig, this._config); } override onStarting(injector: Injector): void { @@ -120,7 +120,12 @@ export class UniverSheetsUIPlugin extends Plugin { [HeaderFreezeRenderController], [SheetClipboardController], [SheetRenderController], - [SheetUIController], + [ + SheetUIController, + { + useFactory: () => this._injector.createInstance(SheetUIController, this._config), + }, + ], [StartEditController], [StatusBarController], [EditingController], diff --git a/packages/sheets-zen-editor/src/controllers/zen-editor-ui.controller.ts b/packages/sheets-zen-editor/src/controllers/zen-editor-ui.controller.ts index 808513b31d6..c818a4a781a 100644 --- a/packages/sheets-zen-editor/src/controllers/zen-editor-ui.controller.ts +++ b/packages/sheets-zen-editor/src/controllers/zen-editor-ui.controller.ts @@ -15,7 +15,7 @@ */ import { Disposable, ICommandService, LifecycleStages, OnLifecycle } from '@univerjs/core'; -import type { IMenuItemFactory } from '@univerjs/ui'; +import type { IMenuItemFactory, MenuConfig } from '@univerjs/ui'; import { IMenuService, IShortcutService, IZenZoneService } from '@univerjs/ui'; import { Inject, Injector } from '@wendellhu/redi'; @@ -25,9 +25,16 @@ import { ZenEditorMenuItemFactory } from '../views/menu'; import { ZEN_EDITOR_COMPONENT, ZenEditor } from '../views/zen-editor'; import { ZenEditorCancelShortcut, ZenEditorConfirmShortcut } from './shortcuts/zen-editor.shortcut'; +export interface IUniverSheetsZenEditorUIConfig { + menu: MenuConfig; +} + +export const DefaultSheetZenEditorUiConfig = {}; + @OnLifecycle(LifecycleStages.Rendered, ZenEditorUIController) export class ZenEditorUIController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @IZenZoneService private readonly _zenZoneService: IZenZoneService, @ICommandService private readonly _commandService: ICommandService, @@ -42,6 +49,7 @@ export class ZenEditorUIController extends Disposable { private _initialize(): void { this._initCustomComponents(); this._initCommands(); + this._initMenuConfigs(); this._initMenus(); this._initShortcuts(); } @@ -56,6 +64,13 @@ export class ZenEditorUIController extends Disposable { }); } + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _initMenus(): void { ( [ diff --git a/packages/sheets-zen-editor/src/plugin.ts b/packages/sheets-zen-editor/src/plugin.ts index 41d77e52a26..7210fdbff7e 100644 --- a/packages/sheets-zen-editor/src/plugin.ts +++ b/packages/sheets-zen-editor/src/plugin.ts @@ -14,22 +14,22 @@ * limitations under the License. */ -import { LocaleService, Plugin, UniverInstanceType } from '@univerjs/core'; +import { LocaleService, Plugin, Tools, UniverInstanceType } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; import { ZenEditorController } from './controllers/zen-editor.controller'; -import { ZenEditorUIController } from './controllers/zen-editor-ui.controller'; +import type { IUniverSheetsZenEditorUIConfig } from './controllers/zen-editor-ui.controller'; +import { DefaultSheetZenEditorUiConfig, ZenEditorUIController } from './controllers/zen-editor-ui.controller'; import { IZenEditorManagerService, ZenEditorManagerService } from './services/zen-editor.service'; import { zhCN } from './locale'; -export interface IUniverSheetsZenEditorPluginConfig {} export class UniverSheetsZenEditorPlugin extends Plugin { static override pluginName = 'zen-editor'; static override type = UniverInstanceType.UNIVER_DOC; constructor( - _config: IUniverSheetsZenEditorPluginConfig, + private readonly _config: Partial = {}, @Inject(Injector) override readonly _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService ) { @@ -37,17 +37,21 @@ export class UniverSheetsZenEditorPlugin extends Plugin { this._initialize(); this._initializeDependencies(this._injector); + this._config = Tools.deepMerge({}, DefaultSheetZenEditorUiConfig, this._config); } private _initialize(): void { - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); } private _initializeDependencies(injector: Injector) { const dependencies: Dependency[] = [ - [ZenEditorUIController], + [ + ZenEditorUIController, + { + useFactory: () => this._injector.createInstance(ZenEditorUIController, this._config), + }, + ], [ZenEditorController], [IZenEditorManagerService, { useClass: ZenEditorManagerService }], ]; diff --git a/packages/sheets-zen-editor/src/views/menu.ts b/packages/sheets-zen-editor/src/views/menu.ts index 8511bf8f4de..46225038c23 100644 --- a/packages/sheets-zen-editor/src/views/menu.ts +++ b/packages/sheets-zen-editor/src/views/menu.ts @@ -14,17 +14,22 @@ * limitations under the License. */ -import { type IMenuButtonItem, MenuGroup, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { type IMenuButtonItem, IMenuService, MenuGroup, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; +import type { IAccessor } from '@wendellhu/redi'; import { OpenZenEditorOperation } from '../commands/operations/zen-editor.operation'; -export function ZenEditorMenuItemFactory(): IMenuButtonItem { - return { +export function ZenEditorMenuItemFactory(accessor: IAccessor): IMenuButtonItem { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(OpenZenEditorOperation.id); + + return mergeMenuConfigs({ id: OpenZenEditorOperation.id, group: MenuGroup.CONTEXT_MENU_OTHERS, type: MenuItemType.BUTTON, title: 'rightClick.zenEditor', icon: 'AmplifySingle', positions: [MenuPosition.CONTEXT_MENU], - }; + }, menuItemConfig); } diff --git a/packages/ui/src/common/component-manager.ts b/packages/ui/src/common/component-manager.ts index 60f7f5ef3ad..79b607ef87e 100644 --- a/packages/ui/src/common/component-manager.ts +++ b/packages/ui/src/common/component-manager.ts @@ -16,6 +16,7 @@ import { toDisposable } from '@univerjs/core'; import { + AddDigitsSingle, AdjustHeight, AdjustWidth, AlignBottomSingle, @@ -34,6 +35,7 @@ import { ClearFormat, CntSingle, CodeSingle, + Conditions, ContentSingle16, Copy, DeleteCellMoveDown, @@ -75,6 +77,7 @@ import { MaxSingle, MergeAllSingle, MinSingle, + MoreDownSingle, NoBorderSingle, NoColor, NoRotationSingle, @@ -83,15 +86,18 @@ import { OverflowSingle, PaintBucket, PasteSpecial, + PercentSingle, PipingSingle, RedoSingle, Reduce, + ReduceDigitsSingle, RightBorder, RightDoubleDiagonalSingle, RightInsertColumn, RightJustifyingSingle, RightRotationFortyFiveDegreesSingle, RightRotationNinetyDegreesSingle, + RmbSingle, SlashSingle, StrikethroughSingle, SubscriptSingle, @@ -135,6 +141,7 @@ export type ComponentList = Map; export class ComponentManager { private _components: ComponentList = new Map(); + // eslint-disable-next-line max-lines-per-function constructor() { const iconList: Record> = { AlignBottomSingle, @@ -227,6 +234,12 @@ export class ComponentManager { DirectExportSingle, FolderSingle, ExportSingle, + Conditions, + RmbSingle, + MoreDownSingle, + AddDigitsSingle, + ReduceDigitsSingle, + PercentSingle, }; for (const k in iconList) { diff --git a/packages/ui/src/common/menu-merge-configs.ts b/packages/ui/src/common/menu-merge-configs.ts new file mode 100644 index 00000000000..0a5fea0533c --- /dev/null +++ b/packages/ui/src/common/menu-merge-configs.ts @@ -0,0 +1,52 @@ +/** + * 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 { BehaviorSubject, switchMap } from 'rxjs'; +import type { MenuConfig, MenuItemConfig } from '../services/menu/menu'; + +export function mergeMenuConfigs(baseConfig: T, additionalConfig: MenuItemConfig | null): T { + if (!additionalConfig || !baseConfig) return baseConfig; + + // Destructure additionalConfig with default values + const { hidden, disabled, activated } = additionalConfig; + + // Update properties directly if they exist in additionalConfig + const properties: (keyof MenuItemConfig)[] = ['group', 'type', 'icon', 'title', 'tooltip', 'positions']; + properties.forEach((prop) => { + if (additionalConfig[prop] !== undefined) { + // Use type assertion to assure TypeScript about the operation's safety + (baseConfig as any)[prop] = additionalConfig[prop]; + } + }); + + // Update reactive properties + const observableProperties = ['hidden$', 'disabled$', 'activated$']; + observableProperties.forEach((prop) => { + updateReactiveProperty(baseConfig, prop as keyof typeof baseConfig, (additionalConfig as any)[prop]); + }); + + return baseConfig; +} + +// Helper function to update reactive properties +function updateReactiveProperty(baseConfig: T, key: K, value: any): void { + if (value !== undefined && baseConfig[key]) { + const subject$ = (baseConfig[key] as any).pipe( + switchMap(() => new BehaviorSubject(value)) + ); + baseConfig[key] = subject$; + } +} diff --git a/packages/ui/src/controllers/menus/menus.ts b/packages/ui/src/controllers/menus/menus.ts index b17fe1ad77b..9cd0c51f71c 100644 --- a/packages/ui/src/controllers/menus/menus.ts +++ b/packages/ui/src/controllers/menus/menus.ts @@ -20,11 +20,16 @@ import { map } from 'rxjs/operators'; import type { IMenuButtonItem } from '../../services/menu/menu'; import { MenuGroup, MenuItemType, MenuPosition } from '../../services/menu/menu'; +import { IMenuService } from '../../services/menu/menu.service'; +import { mergeMenuConfigs } from '../../common/menu-merge-configs'; export function UndoMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const undoRedoService = accessor.get(IUndoRedoService); + const menuService = accessor.get(IMenuService); - return { + const menuItemConfig = menuService.getMenuConfig(UndoCommand.id); + + return mergeMenuConfigs({ id: UndoCommand.id, group: MenuGroup.TOOLBAR_HISTORY, type: MenuItemType.BUTTON, @@ -33,13 +38,16 @@ export function UndoMenuItemFactory(accessor: IAccessor): IMenuButtonItem { tooltip: 'toolbar.undo', positions: [MenuPosition.TOOLBAR_START], disabled$: undoRedoService.undoRedoStatus$.pipe(map((v) => v.undos <= 0)), - }; + }, menuItemConfig); } export function RedoMenuItemFactory(accessor: IAccessor): IMenuButtonItem { const undoRedoService = accessor.get(IUndoRedoService); + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(RedoCommand.id); - return { + return mergeMenuConfigs({ id: RedoCommand.id, group: MenuGroup.TOOLBAR_HISTORY, type: MenuItemType.BUTTON, @@ -48,5 +56,5 @@ export function RedoMenuItemFactory(accessor: IAccessor): IMenuButtonItem { tooltip: 'toolbar.redo', positions: [MenuPosition.TOOLBAR_START], disabled$: undoRedoService.undoRedoStatus$.pipe(map((v) => v.redos <= 0)), - }; + }, menuItemConfig); } diff --git a/packages/ui/src/controllers/shared-shortcut.controller.ts b/packages/ui/src/controllers/shared-shortcut.controller.ts index b486ba04379..705f3857fa0 100644 --- a/packages/ui/src/controllers/shared-shortcut.controller.ts +++ b/packages/ui/src/controllers/shared-shortcut.controller.ts @@ -25,6 +25,7 @@ import type { IShortcutItem } from '../services/shortcut/shortcut.service'; import { IShortcutService } from '../services/shortcut/shortcut.service'; import { SetEditorResizeOperation } from '../commands/operations/editor/set-editor-resize.operation'; import { RedoMenuItemFactory, UndoMenuItemFactory } from './menus/menus'; +import type { IUniverUIConfig } from './ui/ui.controller'; // Not that the clipboard shortcut items would only be invoked when the browser fully supports clipboard API. // If not, the corresponding shortcut would not be triggered and we will perform clipboard operations @@ -93,6 +94,7 @@ export const RedoShortcutItem: IShortcutItem = { @OnLifecycle(LifecycleStages.Ready, SharedController) export class SharedController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, @IMenuService private readonly _menuService: IMenuService, @IShortcutService private readonly _shortcutService: IShortcutService, @@ -106,9 +108,17 @@ export class SharedController extends Disposable { initialize(): void { this._registerCommands(); this._registerShortcuts(); + this._registerMenuConfigs(); this._registerMenus(); } + private _registerMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } + private _registerMenus(): void { [UndoMenuItemFactory, RedoMenuItemFactory].forEach((factory) => { this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory))); diff --git a/packages/ui/src/controllers/ui/ui.controller.ts b/packages/ui/src/controllers/ui/ui.controller.ts index 4f348ff230f..712f7dd8d5e 100644 --- a/packages/ui/src/controllers/ui/ui.controller.ts +++ b/packages/ui/src/controllers/ui/ui.controller.ts @@ -14,7 +14,9 @@ * limitations under the License. */ +import type { DependencyOverride } from '@univerjs/core'; import { createIdentifier } from '@wendellhu/redi'; +import type { MenuConfig } from '../../services/menu/menu'; export interface IWorkbenchOptions { container?: string | HTMLElement; @@ -29,3 +31,12 @@ export interface IUIController { } export const IUIController = createIdentifier('univer.ui-controller'); + +export interface IUniverUIConfig extends IWorkbenchOptions { + /** Disable auto focus when Univer bootstraps. */ + disableAutoFocus?: true; + + override?: DependencyOverride; + + menu: MenuConfig; +} diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 1a56fd9afcc..1ccdf6c63fb 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -58,9 +58,11 @@ export { type IMenuItemFactory, type IMenuSelectorItem, type IValueOption, + type MenuConfig, MenuGroup, MenuItemType, MenuPosition, + type MenuItemDefaultValueType, } from './services/menu/menu'; export { DesktopMenuService, IMenuService } from './services/menu/menu.service'; export { DesktopMessageService } from './services/message/desktop-message.service'; @@ -88,3 +90,4 @@ export { DesktopLocalStorageService } from './services/local-storage/local-stora export { CanvasPopupService, ICanvasPopupService, type IPopup } from './services/popup/canvas-popup.service'; export { ProgressBar } from './components/progress-bar/ProgressBar'; export { IProgressService } from './services/progress/progress.service'; +export { mergeMenuConfigs } from './common/menu-merge-configs'; diff --git a/packages/ui/src/services/menu/menu.service.ts b/packages/ui/src/services/menu/menu.service.ts index e5496f96c9e..02042a88c52 100644 --- a/packages/ui/src/services/menu/menu.service.ts +++ b/packages/ui/src/services/menu/menu.service.ts @@ -21,7 +21,7 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; import { IShortcutService } from '../shortcut/shortcut.service'; -import type { IDisplayMenuItem, IMenuItem, MenuPosition } from './menu'; +import type { IDisplayMenuItem, IMenuItem, MenuConfig, MenuItemConfig, MenuPosition } from './menu'; export const IMenuService = createIdentifier('univer.menu-service'); @@ -30,9 +30,14 @@ export interface IMenuService { addMenuItem(item: IMenuItem): IDisposable; + setMenuItem(item: IMenuItem): void; + /** Get menu items for display at a given position or a submenu. */ getMenuItems(position: MenuPosition | string): Array>; getMenuItem(id: string): IMenuItem | null; + + setMenuConfigs(id: string, config: MenuItemConfig): void; + getMenuConfig(id: string): MenuConfig | null; } export class DesktopMenuService extends Disposable implements IMenuService { @@ -40,6 +45,8 @@ export class DesktopMenuService extends Disposable implements IMenuService { private readonly _menuByPositions = new Map>(); + private readonly _menuConfigs = new Map(); + private _menuChanged$ = new BehaviorSubject(undefined); menuChanged$: Observable = this._menuChanged$.asObservable(); @@ -112,6 +119,11 @@ export class DesktopMenuService extends Disposable implements IMenuService { return [] as Array>; } + setMenuItem(item: IMenuItem): void { + this._menuItemMap.set(item.id, item); + this._menuChanged$.next(); + } + getMenuItem(id: string): IMenuItem | null { if (this._menuItemMap.has(id)) { return this._menuItemMap.get(id)!; @@ -120,6 +132,18 @@ export class DesktopMenuService extends Disposable implements IMenuService { return null; } + setMenuConfigs(id: string, config: MenuConfig): void { + this._menuConfigs.set(id, config); + } + + getMenuConfig(id: string): MenuConfig | null { + if (this._menuConfigs.has(id)) { + return this._menuConfigs.get(id)!; + } + + return null; + } + private _getDisplayMenuItems(menuItem: IMenuItem): IDisplayMenuItem { const shortcut = this._shortcutService.getShortcutDisplayOfCommand(menuItem.id); if (!shortcut) { diff --git a/packages/ui/src/services/menu/menu.ts b/packages/ui/src/services/menu/menu.ts index 0c4a364ead5..eb43ac5c74b 100644 --- a/packages/ui/src/services/menu/menu.ts +++ b/packages/ui/src/services/menu/menu.ts @@ -138,10 +138,17 @@ export function isMenuSelectorItem(v: IMenuI export type MenuItemDefaultValueType = string | number | undefined; -export type IMenuItem = IMenuButtonItem | IMenuSelectorItem; +export type IMenuItem = IMenuButtonItem | IMenuSelectorItem; export type IDisplayMenuItem = T & { shortcut?: string; }; -export type IMenuItemFactory = (accessor: IAccessor) => IMenuItem; +export type MenuItemConfig = Partial & { + defaultValue?: T; + hidden?: boolean; + disabled?: boolean; + activated?: boolean; +}>; +export type MenuConfig = Record>; +export type IMenuItemFactory = (accessor: IAccessor, menuConfig?: MenuConfig) => IMenuItem; diff --git a/packages/ui/src/ui-plugin.ts b/packages/ui/src/ui-plugin.ts index 8b1c7105e86..76774bdb8f1 100644 --- a/packages/ui/src/ui-plugin.ts +++ b/packages/ui/src/ui-plugin.ts @@ -14,8 +14,7 @@ * limitations under the License. */ -import type { DependencyOverride } from '@univerjs/core'; -import { IContextService, ILocalStorageService, LocaleService, mergeOverrideWithDependencies, Plugin } from '@univerjs/core'; +import { IContextService, ILocalStorageService, LocaleService, mergeOverrideWithDependencies, Plugin, Tools } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; @@ -27,7 +26,7 @@ import { ZIndexManager } from './common/z-index-manager'; import { ErrorController } from './controllers/error/error.controller'; import { SharedController } from './controllers/shared-shortcut.controller'; import { ShortcutPanelController } from './controllers/shortcut-display/shortcut-panel.controller'; -import type { IWorkbenchOptions } from './controllers/ui/ui.controller'; +import type { IUniverUIConfig } from './controllers/ui/ui.controller'; import { IUIController } from './controllers/ui/ui.controller'; import { DesktopUIController } from './controllers/ui/ui-desktop.controller'; import { zhCN } from './locale'; @@ -59,12 +58,7 @@ import { DesktopUIPartsService, IUIPartsService } from './services/parts/parts.s const PLUGIN_NAME = 'ui'; -export interface IUniverUIConfig extends IWorkbenchOptions { - /** Disable auto focus when Univer bootstraps. */ - disableAutoFocus?: true; - - override?: DependencyOverride; -} +export const DefaultUiConfig = {}; export const DISABLE_AUTO_FOCUS_KEY = 'DISABLE_AUTO_FOCUS'; @@ -83,6 +77,7 @@ export class UniverUIPlugin extends Plugin { super(); this._localeService.load({ zhCN }); + this._config = Tools.deepMerge({}, DefaultUiConfig, this._config); if (this._config.disableAutoFocus) { this._contextService.setContextValue(DISABLE_AUTO_FOCUS_KEY, true); @@ -128,7 +123,12 @@ export class UniverUIPlugin extends Plugin { // controllers [IUIController, { useClass: DesktopUIController }], - [SharedController], + [ + SharedController, + { + useFactory: () => this._injector.createInstance(SharedController, this._config), + }, + ], [ErrorController], [ShortcutPanelController], ], this._config.override); diff --git a/packages/uniscript/src/controllers/menu.ts b/packages/uniscript/src/controllers/menu.ts index f872bdc1717..92fe452b095 100644 --- a/packages/uniscript/src/controllers/menu.ts +++ b/packages/uniscript/src/controllers/menu.ts @@ -15,14 +15,18 @@ */ import type { IMenuButtonItem } from '@univerjs/ui'; -import { getMenuHiddenObservable, MenuItemType, MenuPosition } from '@univerjs/ui'; +import { getMenuHiddenObservable, IMenuService, MenuItemType, MenuPosition, mergeMenuConfigs } from '@univerjs/ui'; import type { IAccessor } from '@wendellhu/redi'; import { UniverInstanceType } from '@univerjs/core'; import { ToggleScriptPanelOperation } from '../commands/operations/panel.operation'; export function UniscriptMenuItemFactory(accessor: IAccessor): IMenuButtonItem { - return { + const menuService = accessor.get(IMenuService); + + const menuItemConfig = menuService.getMenuConfig(ToggleScriptPanelOperation.id); + + return mergeMenuConfigs({ id: ToggleScriptPanelOperation.id, title: 'toggle-script-panel', tooltip: 'script-panel.tooltip.menu-button', @@ -32,5 +36,5 @@ export function UniscriptMenuItemFactory(accessor: IAccessor): IMenuButtonItem { // FIXME hidden$ and disabled$ are not correctly in doc hidden$: getMenuHiddenObservable(accessor, UniverInstanceType.UNIVER_SHEET), // disabled$: getCurrentSheetDisabled$(accessor), - }; + }, menuItemConfig); } diff --git a/packages/uniscript/src/controllers/uniscript.controller.ts b/packages/uniscript/src/controllers/uniscript.controller.ts index 779f9f8a9b3..8f5d96d13b3 100644 --- a/packages/uniscript/src/controllers/uniscript.controller.ts +++ b/packages/uniscript/src/controllers/uniscript.controller.ts @@ -15,6 +15,7 @@ */ import { Disposable, ICommandService, LifecycleStages, OnLifecycle } from '@univerjs/core'; +import type { MenuConfig } from '@univerjs/ui'; import { ComponentManager, IMenuService } from '@univerjs/ui'; import { Inject, Injector } from '@wendellhu/redi'; @@ -22,18 +23,35 @@ import { ScriptPanelComponentName, ToggleScriptPanelOperation } from '../command import { ScriptEditorPanel } from '../views/components/ScriptEditorPanel'; import { UniscriptMenuItemFactory } from './menu'; +export interface IUniverUniscriptConfig { + getWorkerUrl(moduleID: string, label: string): string; + menu: MenuConfig; +} + +export const DefaultUniscriptConfig = {}; + @OnLifecycle(LifecycleStages.Steady, UniscriptController) export class UniscriptController extends Disposable { constructor( + private readonly _config: Partial, @Inject(Injector) private readonly _injector: Injector, - @IMenuService menuService: IMenuService, + @IMenuService private readonly _menuService: IMenuService, @ICommandService commandService: ICommandService, @Inject(ComponentManager) componentManager: ComponentManager ) { super(); - this.disposeWithMe(menuService.addMenuItem(this._injector.invoke(UniscriptMenuItemFactory))); + this._initMenuConfigs(); + + this.disposeWithMe(_menuService.addMenuItem(this._injector.invoke(UniscriptMenuItemFactory))); this.disposeWithMe(componentManager.register(ScriptPanelComponentName, ScriptEditorPanel)); this.disposeWithMe(commandService.registerCommand(ToggleScriptPanelOperation)); } + + private _initMenuConfigs() { + const { menu = {} } = this._config; + Object.entries(menu).forEach(([id, config]) => { + this._menuService.setMenuConfigs(id, config); + }); + } } diff --git a/packages/uniscript/src/index.ts b/packages/uniscript/src/index.ts index 9a8590cd3e4..ff1db05ae5d 100644 --- a/packages/uniscript/src/index.ts +++ b/packages/uniscript/src/index.ts @@ -15,5 +15,5 @@ */ export { enUS, zhCN, ruRU } from './locale'; -export { type IUniscriptConfig, UniverUniscriptPlugin } from './plugin'; +export { UniverUniscriptPlugin } from './plugin'; export { ScriptEditorService } from './services/script-editor.service'; diff --git a/packages/uniscript/src/plugin.ts b/packages/uniscript/src/plugin.ts index 6353bfb89eb..79b5c8d9ee0 100644 --- a/packages/uniscript/src/plugin.ts +++ b/packages/uniscript/src/plugin.ts @@ -14,36 +14,36 @@ * limitations under the License. */ -import { LocaleService, Plugin } from '@univerjs/core'; +import { LocaleService, Plugin, Tools } from '@univerjs/core'; import type { Dependency } from '@wendellhu/redi'; import { Inject, Injector } from '@wendellhu/redi'; -import { UniscriptController } from './controllers/uniscript.controller'; +import type { IUniverUniscriptConfig } from './controllers/uniscript.controller'; +import { DefaultUniscriptConfig, UniscriptController } from './controllers/uniscript.controller'; import { zhCN } from './locale'; -import type { IScriptEditorServiceConfig } from './services/script-editor.service'; import { ScriptEditorService } from './services/script-editor.service'; import { UniscriptExecutionService } from './services/script-execution.service'; import { ScriptPanelService } from './services/script-panel.service'; const PLUGIN_NAME = 'uniscript'; -export interface IUniscriptConfig extends IScriptEditorServiceConfig {} - export class UniverUniscriptPlugin extends Plugin { static override pluginName = PLUGIN_NAME; constructor( - private readonly _config: IUniscriptConfig, + private readonly _config: Partial = {}, @Inject(Injector) protected override _injector: Injector, @Inject(LocaleService) private readonly _localeService: LocaleService ) { super(); + + this._config = Tools.deepMerge({}, DefaultUniscriptConfig, this._config); } override onStarting(injector: Injector): void { const dependencies: Dependency[] = [ // controllers - [UniscriptController], + [UniscriptController, { useFactory: () => injector.createInstance(UniscriptController, this._config) }], // services [ScriptEditorService, { useFactory: () => injector.createInstance(ScriptEditorService, this._config) }], @@ -53,8 +53,6 @@ export class UniverUniscriptPlugin extends Plugin { dependencies.forEach((d) => injector.add(d)); - this._localeService.load({ - zhCN, - }); + this._localeService.load({ zhCN }); } } diff --git a/packages/uniscript/src/services/script-editor.service.ts b/packages/uniscript/src/services/script-editor.service.ts index 627dbb88561..249606b9c5c 100644 --- a/packages/uniscript/src/services/script-editor.service.ts +++ b/packages/uniscript/src/services/script-editor.service.ts @@ -17,10 +17,7 @@ import { Disposable, toDisposable } from '@univerjs/core'; import type { IDisposable } from '@wendellhu/redi'; import type { editor } from 'monaco-editor'; - -export interface IScriptEditorServiceConfig { - getWorkerUrl(moduleID: string, label: string): string; -} +import type { IUniverUniscriptConfig } from '../controllers/uniscript.controller'; /** * This service is for loading monaco editor and its resources. It also holds the @@ -29,7 +26,7 @@ export interface IScriptEditorServiceConfig { export class ScriptEditorService extends Disposable { private _editorInstance: editor.IStandaloneCodeEditor | null = null; - constructor(private readonly _config: IScriptEditorServiceConfig) { + constructor(private readonly _config: Partial) { super(); }