Skip to content

Commit

Permalink
feat: add support for customizable context menu & toolbar
Browse files Browse the repository at this point in the history
  • Loading branch information
jikkai committed May 18, 2024
1 parent 8516acb commit da32c6f
Show file tree
Hide file tree
Showing 8 changed files with 392 additions and 126 deletions.
25 changes: 24 additions & 1 deletion examples/src/sheets/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,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

Expand Down
360 changes: 248 additions & 112 deletions packages/sheets-ui/src/controllers/menu/menu.ts

Large diffs are not rendered by default.

23 changes: 20 additions & 3 deletions packages/sheets-ui/src/controllers/sheet-ui.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ import {
SetStrikeThroughCommand,
SetUnderlineCommand,
} from '@univerjs/sheets';
import type { IDesktopUIController, IMenuItemFactory } from '@univerjs/ui';
import { ComponentManager, DesktopUIPart, ILayoutService, IMenuService, IShortcutService, IUIController } from '@univerjs/ui';
import type { IDesktopUIController, IMenuItemFactory, MenuConfig, MenuItemDefaultValueType } from '@univerjs/ui';
import { ComponentManager, DesktopUIPart, ILayoutService, IMenuService, IShortcutService, IUIController, mergeMenuConfigs } from '@univerjs/ui';
import { Inject, Injector } from '@wendellhu/redi';
import { connectInjector } from '@wendellhu/redi/react-bindings';

Expand Down Expand Up @@ -228,9 +228,16 @@ import {
ZoomOutShortcutItem,
} from './shortcuts/view.shortcut';

export interface IUniverSheetsUIConfig {
menu: MenuConfig; // Add a type parameter to MenuConfig
}

export const DefaultSheetUiConfig = {};

@OnLifecycle(LifecycleStages.Ready, SheetUIController)
export class SheetUIController extends Disposable {
constructor(
private readonly _config: Partial<IUniverSheetsUIConfig>,
@Inject(Injector) private readonly _injector: Injector,
@Inject(ComponentManager) private readonly _componentManager: ComponentManager,
@ILayoutService private readonly _layoutService: ILayoutService,
Expand All @@ -247,6 +254,7 @@ export class SheetUIController extends Disposable {
private _init(): void {
this._initCustomComponents();
this._initCommands();
this._initMenuConfigs();
this._initMenus();
this._initShortcuts();
this._initWorkbenchParts();
Expand Down Expand Up @@ -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 {
(
[
Expand Down Expand Up @@ -403,7 +418,9 @@ export class SheetUIController extends Disposable {
ShowMenuItemFactory,
] as IMenuItemFactory[]
).forEach((factory) => {
this.disposeWithMe(this._menuService.addMenuItem(this._injector.invoke(factory)));
const menuItem = this._injector.invoke(factory);

this.disposeWithMe(this._menuService.addMenuItem(menuItem));
});
}

Expand Down
19 changes: 12 additions & 7 deletions packages/sheets-ui/src/sheets-ui-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -77,17 +78,16 @@ export class UniverSheetsUIPlugin extends Plugin {
static override type = UniverInstanceType.UNIVER_SHEET;

constructor(
_config: undefined,
private readonly _config: Partial<IUniverSheetsUIConfig> = {},
@Inject(Injector) override readonly _injector: Injector,
@Inject(LocaleService) private readonly _localeService: LocaleService,
@IRenderManagerService private readonly _renderManagerService: IRenderManagerService,
@IUniverInstanceService private readonly _univerInstanceService: IUniverInstanceService
) {
super();

this._localeService.load({
zhCN,
});
this._localeService.load({ zhCN });
this._config = Tools.deepMerge({}, DefaultSheetUiConfig, this._config);
}

override onStarting(injector: Injector): void {
Expand Down Expand Up @@ -120,7 +120,12 @@ export class UniverSheetsUIPlugin extends Plugin {
[HeaderFreezeRenderController],
[SheetClipboardController],
[SheetRenderController],
[SheetUIController],
[
SheetUIController,
{
useFactory: () => this._injector.createInstance(SheetUIController, this._config),
},
],
[StartEditController],
[StatusBarController],
[EditingController],
Expand Down
51 changes: 51 additions & 0 deletions packages/ui/src/common/menu-merge-configs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 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 { IMenuItem, MenuItemConfig } from '../services/menu/menu';

export function mergeMenuConfigs<T extends IMenuItem>(baseConfig: T, additionalConfig: MenuItemConfig | null): T {
if (!additionalConfig) 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
updateReactiveProperty(baseConfig, 'hidden$', hidden);
updateReactiveProperty(baseConfig, 'disabled$', disabled);
updateReactiveProperty(baseConfig, 'activated$', activated);

return baseConfig;
}

// Helper function to update reactive properties
function updateReactiveProperty<T extends IMenuItem, K extends keyof T>(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$;
}
}
3 changes: 3 additions & 0 deletions packages/ui/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,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';
Expand All @@ -86,3 +88,4 @@ export { DesktopLocalStorageService } from './services/local-storage/local-stora
export { CanvasPopupService, ICanvasPopupService } 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';
26 changes: 25 additions & 1 deletion packages/ui/src/services/menu/menu.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<IMenuService>('univer.menu-service');

Expand All @@ -30,16 +30,23 @@ 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<IDisplayMenuItem<IMenuItem>>;
getMenuItem(id: string): IMenuItem | null;

setMenuConfigs(id: string, config: MenuItemConfig): void;
getMenuConfig(id: string): MenuConfig | null;
}

export class DesktopMenuService extends Disposable implements IMenuService {
private readonly _menuItemMap = new Map<string, IMenuItem>();

private readonly _menuByPositions = new Map<MenuPosition | string, Array<[string, IMenuItem]>>();

private readonly _menuConfigs = new Map<string, MenuConfig>();

private _menuChanged$ = new BehaviorSubject<void>(undefined);

menuChanged$: Observable<void> = this._menuChanged$.asObservable();
Expand Down Expand Up @@ -112,6 +119,11 @@ export class DesktopMenuService extends Disposable implements IMenuService {
return [] as Array<IDisplayMenuItem<IMenuItem>>;
}

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)!;
Expand All @@ -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<IMenuItem> {
const shortcut = this._shortcutService.getShortcutDisplayOfCommand(menuItem.id);
if (!shortcut) {
Expand Down
11 changes: 9 additions & 2 deletions packages/ui/src/services/menu/menu.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,10 +138,17 @@ export function isMenuSelectorItem<T extends MenuItemDefaultValueType>(v: IMenuI

export type MenuItemDefaultValueType = string | number | undefined;

export type IMenuItem = IMenuButtonItem | IMenuSelectorItem<MenuItemDefaultValueType>;
export type IMenuItem = IMenuButtonItem<MenuItemDefaultValueType> | IMenuSelectorItem<MenuItemDefaultValueType>;

export type IDisplayMenuItem<T extends IMenuItem> = T & {
shortcut?: string;
};

export type IMenuItemFactory = (accessor: IAccessor) => IMenuItem;
export type MenuItemConfig<T extends MenuItemDefaultValueType = MenuItemDefaultValueType> = Partial<Omit<IMenuItem, 'id' | 'subId' | 'value$' | 'hidden$' | 'disabled$' | 'activated$' | 'icon$'> & {
defaultValue?: T;
hidden?: boolean;
disabled?: boolean;
activated?: boolean;
}>;
export type MenuConfig<T extends MenuItemDefaultValueType = MenuItemDefaultValueType> = Record<string, MenuItemConfig<T>>;
export type IMenuItemFactory = (accessor: IAccessor, menuConfig?: MenuConfig<MenuItemDefaultValueType>) => IMenuItem;

0 comments on commit da32c6f

Please sign in to comment.