Skip to content

Commit

Permalink
feat: support the horizontal layout of MenuBar (#553)
Browse files Browse the repository at this point in the history
* feat: support the horizontal layout of MenuBar 

* feat: update menuBar unit test

* feat: menubar supports custom icon

* feat: supplement the unit test code of menuBar

* feat: optimize code and supplement unit tests

Co-authored-by: jiming <jiming@dtstack.com>
  • Loading branch information
kiwiwong and jiming authored Dec 20, 2021
1 parent 44b4e64 commit 2cf2abb
Show file tree
Hide file tree
Showing 19 changed files with 802 additions and 21 deletions.
40 changes: 39 additions & 1 deletion src/components/menu/__tests__/menu.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useRef } from 'react';
import { fireEvent, render, waitFor, screen } from '@testing-library/react';
import renderer from 'react-test-renderer';

Expand All @@ -11,6 +11,7 @@ import {
horizontalMenuClassName,
verticalMenuClassName,
} from '../base';
import { MenuRef } from '../index';

const menuData = [
{
Expand Down Expand Up @@ -118,6 +119,11 @@ const menuData = [
];
const TEST_ID = 'test-id';

function MenuTest(props) {
const ref = useRef<MenuRef>(null);
return <Menu ref={ref} data={menuData} {...props} />;
}

describe('Test the Menu Component', () => {
test('Match the List snapshot', () => {
const component = renderer.create(
Expand Down Expand Up @@ -306,4 +312,36 @@ describe('Test the Menu Component', () => {
});
});
});

test('Dispose the Menu', () => {
const TEST_DATA1 = 'test1';
const TEST_DATA2 = 'test2';
const mockData = [
{
id: TEST_DATA1,
name: TEST_DATA1,
title: TEST_DATA1,
data: [
{
id: TEST_DATA2,
name: TEST_DATA2,
'data-testid': TEST_DATA2,
},
],
},
];
const menu = renderer.create(<MenuTest />);
const menuNode: any = (menu as renderer.ReactTestRenderer).root.findByType(
Menu
);
expect(menuNode._fiber).not.toBeUndefined();

const menuRef = menuNode._fiber.ref;
render(<Menu trigger="click" ref={menuRef} data={mockData} />);
expect(menuRef?.current?.dispose).not.toBeUndefined();

menuRef.current?.dispose();
const item = document.body.querySelectorAll('ul')[1];
expect(item.style.opacity).toEqual('0');
});
});
26 changes: 23 additions & 3 deletions src/components/menu/menu.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import React, { useEffect, useCallback, useRef } from 'react';
import React, {
useEffect,
useCallback,
useRef,
useImperativeHandle,
forwardRef,
} from 'react';
import { classNames } from 'mo/common/className';
import { debounce } from 'lodash';
import { mergeFunctions } from 'mo/common/utils';
Expand Down Expand Up @@ -56,7 +62,7 @@ const setPositionForSubMenu = (
subMenu.style.left = `${pos.x}px`;
};

export function Menu(props: React.PropsWithChildren<IMenuProps>) {
function MenuComp(props: React.PropsWithChildren<IMenuProps>, ref) {
const {
className,
mode = MenuMode.Vertical,
Expand All @@ -82,7 +88,7 @@ export function Menu(props: React.PropsWithChildren<IMenuProps>) {
if (data.length > 0) {
const renderMenusByData = (menus: IMenuProps[]) => {
return menus.map((item: IMenuProps) => {
if (item.type === 'divider') return <Divider />;
if (item.type === 'divider') return <Divider key={item.id} />;

const handleClick = mergeFunctions(onClick, item.onClick);
if (item.data && item.data.length > 0) {
Expand Down Expand Up @@ -200,6 +206,12 @@ export function Menu(props: React.PropsWithChildren<IMenuProps>) {
};
}, []);

useImperativeHandle(ref, () => ({
dispose: () => {
initialMenuStyle();
},
}));

return (
<ul
className={claNames}
Expand All @@ -213,3 +225,11 @@ export function Menu(props: React.PropsWithChildren<IMenuProps>) {
</ul>
);
}

export type MenuRef = {
dispose: () => void;
};

export const Menu = forwardRef<MenuRef, React.PropsWithChildren<IMenuProps>>(
MenuComp
);
48 changes: 45 additions & 3 deletions src/controller/__tests__/menuBar.test.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
import { ID_SIDE_BAR } from 'mo/common/id';
import { MonacoService } from 'mo/monaco/monacoService';
import { MenuBarService, BuiltinService } from 'mo/services';
import { MenuBarService, BuiltinService, LayoutService } from 'mo/services';
import { constants, modules } from 'mo/services/builtinService/const';
import 'reflect-metadata';
import { container } from 'tsyringe';
import { MenuBarController } from '../menuBar';
import { MenuBarMode } from 'mo/model/workbench/layout';

const menuBarController = container.resolve(MenuBarController);
const menuBarService = container.resolve(MenuBarService);
const monacoService = container.resolve(MonacoService);
const builtinService = container.resolve(BuiltinService);
const layoutService = container.resolve(LayoutService);

const mockEle = document.createElement('div');

describe('The menuBar controller', () => {
test('Should support to inject the default value', () => {
menuBarController.initView();

expect(menuBarService.getState().data).toEqual(
const mode = layoutService.getMenuBarMode();
const menuBarData = menuBarController.getMenuBarDataByMode(
mode,
modules.builtInMenuBarData()
);

expect(menuBarService.getState().data).toEqual(menuBarData);
menuBarService.reset();
});

Expand Down Expand Up @@ -160,4 +165,41 @@ describe('The menuBar controller', () => {
mockExecute.mockClear();
menuBarService.update = originalUpdate;
});

test('Should support to change the layout mode', () => {
const mockEvent = {} as any;
const mockItem = { id: constants.MENUBAR_MODE_HORIZONTAL };
const mockExecute = jest.fn();
const originalSetMenus = menuBarService.setMenus;
const originalUpdateMenuBarMode = menuBarController.updateMenuBarMode;

// change default mode
const defaultMode = layoutService.getMenuBarMode();
const anotherMode =
defaultMode === MenuBarMode.horizontal
? MenuBarMode.vertical
: MenuBarMode.horizontal;
layoutService.setMenuBarMode(anotherMode);
menuBarController.initView();
expect(layoutService.getMenuBarMode()).toBe(anotherMode);

// update to horizontal mode
menuBarService.setMenus = mockExecute;
layoutService.setMenuBarMode(MenuBarMode.vertical);
menuBarController.onClick(mockEvent, mockItem);
expect(mockExecute).toBeCalled();
mockExecute.mockClear();

// update to vertical mode
mockItem.id = constants.MENUBAR_MODE_VERTICAL;
layoutService.setMenuBarMode(MenuBarMode.horizontal);
menuBarController.onClick(mockEvent, mockItem);
expect(mockExecute).toBeCalled();
mockExecute.mockClear();

menuBarService.setMenus = originalSetMenus;
menuBarController.updateMenuBarMode = originalUpdateMenuBarMode;
layoutService.reset();
menuBarService.reset();
});
});
73 changes: 72 additions & 1 deletion src/controller/menuBar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import 'reflect-metadata';
import { container, singleton } from 'tsyringe';
import { IActivityBarItem, IMenuBarItem } from 'mo/model';
import { MenuBarEvent } from 'mo/model/workbench/menuBar';
import { MenuBarMode } from 'mo/model/workbench/layout';
import { Controller } from 'mo/react/controller';
import {
IMenuBarService,
Expand All @@ -25,6 +26,11 @@ export interface IMenuBarController extends Partial<Controller> {
updateMenuBar?: () => void;
updateActivityBar?: () => void;
updateSideBar?: () => void;
updateMenuBarMode?: (mode: keyof typeof MenuBarMode) => void;
getMenuBarDataByMode?: (
mode: keyof typeof MenuBarMode,
menuData: IMenuBarItem[]
) => IMenuBarItem[];
}

@singleton()
Expand Down Expand Up @@ -60,9 +66,16 @@ export class MenuBarController
MENU_VIEW_STATUSBAR,
MENU_QUICK_COMMAND,
MENU_VIEW_PANEL,
MENUBAR_MODE_HORIZONTAL,
MENUBAR_MODE_VERTICAL,
} = this.builtinService.getConstants();
if (builtInMenuBarData) {
this.menuBarService.setMenus(builtInMenuBarData);
const mode = this.layoutService.getMenuBarMode();
const menuBarData = this.getMenuBarDataByMode(
mode,
builtInMenuBarData
);
this.menuBarService.setMenus(menuBarData);
}
([
[ACTION_QUICK_CREATE_FILE, () => this.createFile()],
Expand All @@ -76,6 +89,14 @@ export class MenuBarController
[MENU_QUICK_COMMAND, () => this.gotoQuickCommand()],
[ID_SIDE_BAR, () => this.updateSideBar()],
[MENU_VIEW_PANEL, () => this.updatePanel()],
[
MENUBAR_MODE_HORIZONTAL,
() => this.updateMenuBarMode(MenuBarMode.horizontal),
],
[
MENUBAR_MODE_VERTICAL,
() => this.updateMenuBarMode(MenuBarMode.vertical),
],
] as [string, () => void][]).forEach(([key, value]) => {
if (key) {
this.automation[key] = value;
Expand Down Expand Up @@ -179,6 +200,13 @@ export class MenuBarController
}
};

public updateMenuBarMode = (mode: keyof typeof MenuBarMode) => {
this.layoutService.setMenuBarMode(mode);
const { builtInMenuBarData } = this.builtinService.getModules();
const menuBarData = this.getMenuBarDataByMode(mode, builtInMenuBarData);
this.menuBarService.setMenus(menuBarData);
};

public updateStatusBar = () => {
const hidden = this.layoutService.toggleStatusBarVisibility();
const { MENU_VIEW_STATUSBAR } = this.builtinService.getConstants();
Expand All @@ -200,4 +228,47 @@ export class MenuBarController
QuickTogglePanelAction.ID
);
};

/**
* Get the menu bar data after filtering out the menu contained in ids
* @param menuData
* @param ids
* @returns Filtered menu bar data
*/
private getFilteredMenuBarData(
menuData: IMenuBarItem[],
ids: (UniqueId | undefined)[]
): IMenuBarItem[] {
const newData: IMenuBarItem[] = [];
if (Array.isArray(menuData)) {
menuData.forEach((item: IMenuBarItem) => {
if (ids.includes(item.id)) return;
const newItem = { ...item };
if (Array.isArray(item.data) && item.data.length > 0) {
newItem.data = this.getFilteredMenuBarData(item.data, ids);
}
newData.push(newItem);
});
}
return newData;
}

public getMenuBarDataByMode(
mode: keyof typeof MenuBarMode,
menuData: IMenuBarItem[]
): IMenuBarItem[] {
const {
MENUBAR_MODE_VERTICAL,
MENUBAR_MODE_HORIZONTAL,
} = this.builtinService.getConstants();
const ids: (string | undefined)[] = [];
if (mode === MenuBarMode.horizontal) {
ids.push(MENUBAR_MODE_HORIZONTAL);
} else if (mode === MenuBarMode.vertical) {
ids.push(MENUBAR_MODE_VERTICAL);
}

const menuBarData = this.getFilteredMenuBarData(menuData, ids);
return menuBarData;
}
}
2 changes: 2 additions & 0 deletions src/extensions/locales-defaults/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"menu.showPanel.title": "Toggle Panel",
"menu.run": "Run",
"menu.help": "Help",
"menu.menuBarHorizontal": "Menu Bar Horizontal Mode",
"menu.menuBarVertical": "Menu Bar Vertical Mode",
"sidebar.explore.title": "Explorer",
"sidebar.explore.folders": "Folders",
"sidebar.explore.openEditor": "Open Editors",
Expand Down
2 changes: 2 additions & 0 deletions src/extensions/locales-defaults/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"menu.runTask": "运行任务",
"menu.help": "帮助",
"menu.about": "关于",
"menu.menuBarHorizontal": "菜单栏水平模式",
"menu.menuBarVertical": "菜单栏垂直模式",
"sidebar.explore.title": "浏览",
"sidebar.explore.openEditor": "打开的编辑器",
"sidebar.explore.openEditor.group": "第 ${i} 组",
Expand Down
2 changes: 2 additions & 0 deletions src/i18n/localization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export type LocaleSourceIdType = {
'menu.showPanel.title': string;
'menu.run': string;
'menu.help': string;
'menu.menuBarHorizontal': string;
'menu.menuBarVertical': string;
'sidebar.explore.title': string;
'sidebar.explore.folders': string;
'sidebar.explore.openEditor': string;
Expand Down
17 changes: 14 additions & 3 deletions src/model/workbench/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ export enum Position {
left = 'left',
right = 'right',
}

export enum MenuBarMode {
horizontal = 'horizontal',
vertical = 'vertical',
}

export interface ViewVisibility {
hidden: boolean;
}
Expand All @@ -12,14 +18,19 @@ export interface IPanelViewState extends ViewVisibility {
export interface ISidebarViewState extends ViewVisibility {
position: keyof typeof Position;
}

export interface IMenuBarViewState extends ViewVisibility {
mode: keyof typeof MenuBarMode;
}

export interface ILayout {
splitPanePos: string[];
horizontalSplitPanePos: string[];
activityBar: ViewVisibility;
panel: IPanelViewState;
statusBar: ViewVisibility;
sidebar: ISidebarViewState;
menuBar: ViewVisibility;
menuBar: IMenuBarViewState;
}

export class LayoutModel implements ILayout {
Expand All @@ -29,15 +40,15 @@ export class LayoutModel implements ILayout {
public panel: IPanelViewState;
public statusBar: ViewVisibility;
public sidebar: ISidebarViewState;
public menuBar: ViewVisibility;
public menuBar: IMenuBarViewState;
constructor(
splitPanePos: string[] = ['300px', 'auto'],
horizontalSplitPanePos = ['70%', 'auto'],
activityBar = { hidden: false },
panel = { hidden: false, panelMaximized: false },
statusBar = { hidden: false },
sidebar = { hidden: false, position: Position.left },
menuBar = { hidden: false }
menuBar = { hidden: false, mode: MenuBarMode.vertical }
) {
this.splitPanePos = splitPanePos;
this.horizontalSplitPanePos = horizontalSplitPanePos;
Expand Down
Loading

0 comments on commit 2cf2abb

Please sign in to comment.