Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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