1
0
mirror of https://github.com/lensapp/lens.git synced 2025-05-20 05:10:56 +00:00

allow extension cluster menus to have a parent

Signed-off-by: Jari Kolehmainen <jari.kolehmainen@gmail.com>
This commit is contained in:
Jari Kolehmainen 2020-11-19 17:33:40 +02:00
parent 0f3f5611ba
commit 140c6f7385
3 changed files with 128 additions and 12 deletions

View File

@ -1,9 +1,10 @@
// Extensions-api -> Register page menu items // Extensions-api -> Register page menu items
import type { IconProps } from "../../renderer/components/icon"; import type { IconProps } from "../../renderer/components/icon";
import type React from "react"; import type React from "react";
import { action } from "mobx"; import { action, computed } from "mobx";
import { BaseRegistry } from "./base-registry"; import { BaseRegistry } from "./base-registry";
import { LensExtension } from "../lens-extension"; import { LensExtension } from "../lens-extension";
import { RegisteredPage } from "./page-registry";
export interface PageMenuTarget<P extends object = any> { export interface PageMenuTarget<P extends object = any> {
extensionId?: string; extensionId?: string;
@ -17,11 +18,16 @@ export interface PageMenuRegistration {
components: PageMenuComponents; components: PageMenuComponents;
} }
export interface ClusterPageMenuRegistration extends PageMenuRegistration {
id?: string;
parentId?: string;
}
export interface PageMenuComponents { export interface PageMenuComponents {
Icon: React.ComponentType<IconProps>; Icon: React.ComponentType<IconProps>;
} }
export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Required<PageMenuRegistration>> { export class GlobalPageMenuRegistry extends BaseRegistry<PageMenuRegistration> {
@action @action
add(items: PageMenuRegistration[], ext: LensExtension) { add(items: PageMenuRegistration[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => { const normalizedItems = items.map(menuItem => {
@ -35,5 +41,31 @@ export class PageMenuRegistry extends BaseRegistry<PageMenuRegistration, Require
} }
} }
export const globalPageMenuRegistry = new PageMenuRegistry(); export class ClusterPageMenuRegistry extends BaseRegistry<ClusterPageMenuRegistration> {
export const clusterPageMenuRegistry = new PageMenuRegistry(); @action
add(items: PageMenuRegistration[], ext: LensExtension) {
const normalizedItems = items.map(menuItem => {
menuItem.target = {
extensionId: ext.name,
...(menuItem.target || {}),
};
return menuItem
})
return super.add(normalizedItems);
}
getRootItems() {
return this.getItems().filter((item) => !item.parentId)
}
getSubItems(parentId: string) {
return this.getItems().filter((item) => item.parentId === parentId)
}
getByPage(page: RegisteredPage) {
return this.getItems().find((item) => item.target?.pageId == page.id && item.target?.extensionId === page.extensionId)
}
}
export const globalPageMenuRegistry = new GlobalPageMenuRegistry();
export const clusterPageMenuRegistry = new ClusterPageMenuRegistry();

View File

@ -34,12 +34,15 @@ import { Terminal } from "./dock/terminal";
import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store";
import logger from "../../main/logger"; import logger from "../../main/logger";
import { webFrame } from "electron"; import { webFrame } from "electron";
import { clusterPageRegistry } from "../../extensions/registries/page-registry"; import { clusterPageRegistry, getExtensionPageUrl, PageRegistration, RegisteredPage } from "../../extensions/registries/page-registry";
import { extensionLoader } from "../../extensions/extension-loader"; import { extensionLoader } from "../../extensions/extension-loader";
import { appEventBus } from "../../common/event-bus" import { appEventBus } from "../../common/event-bus"
import { requestMain } from "../../common/ipc"; import { requestMain } from "../../common/ipc";
import whatInput from 'what-input'; import whatInput from 'what-input';
import { clusterSetFrameIdHandler } from "../../common/cluster-ipc"; import { clusterSetFrameIdHandler } from "../../common/cluster-ipc";
import { clusterPageMenuRegistry } from "../../extensions/registries";
import { TabLayoutRoute, TabLayout } from "./layout/tab-layout";
import { Trans } from "@lingui/macro";
@observer @observer
export class App extends React.Component { export class App extends React.Component {
@ -72,9 +75,58 @@ export class App extends React.Component {
return workloadsURL(); return workloadsURL();
} }
getTabLayoutRoutes(page: RegisteredPage) {
const menuItem = clusterPageMenuRegistry.getByPage(page);
const routes: TabLayoutRoute[] = [];
if (!menuItem.id) {
return routes;
}
clusterPageMenuRegistry.getSubItems(menuItem.id).forEach((item) => {
const page = clusterPageRegistry.getByPageMenuTarget(item.target);
if (page) {
routes.push({
routePath: page.routePath,
url: getExtensionPageUrl({ extensionId: page.extensionId, pageId: page.id, params: item.target.params }),
title: item.title,
component: page.components.Page,
exact: page.exact
});
}
});
if (routes.length > 0) {
routes.unshift({
routePath: page.routePath,
url: getExtensionPageUrl({ extensionId: page.extensionId, pageId: page.id, params: menuItem.target.params }),
title: <Trans>Overview</Trans>,
component: page.components.Page,
exact: page.exact
})
}
return routes;
}
renderExtensionTabLayoutRoutes() {
return clusterPageMenuRegistry.getRootItems().map((menu, index) => {
const page = clusterPageRegistry.getByPageMenuTarget(menu.target)
if (page) {
const tabRoutes = this.getTabLayoutRoutes(page)
if (tabRoutes.length > 0) {
const pageComponent = () => <TabLayout tabs={tabRoutes} />
return <Route key={"extension-tab-layout-route-" + index} component={pageComponent}/>
} else {
const pageComponent = () => <page.components.Page />
return <Route key={"extension-tab-layout-route-" + index} path={page.routePath} exact={page.exact} component={pageComponent}/>
}
}
})
}
renderExtensionRoutes() { renderExtensionRoutes() {
return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => { return clusterPageRegistry.getItems().map((page, index) => {
return <Route key={routePath} path={routePath} exact={exact} component={Page}/> const menu = clusterPageMenuRegistry.getByPage(page)
if (!menu) {
return <Route key={"extension-route-" + index} path={page.routePath} exact={page.exact} component={page.components.Page}/>
}
}) })
} }
@ -96,6 +148,7 @@ export class App extends React.Component {
<Route component={CustomResources} {...crdRoute}/> <Route component={CustomResources} {...crdRoute}/>
<Route component={UserManagement} {...usersManagementRoute}/> <Route component={UserManagement} {...usersManagementRoute}/>
<Route component={Apps} {...appsRoute}/> <Route component={Apps} {...appsRoute}/>
{this.renderExtensionTabLayoutRoutes()}
{this.renderExtensionRoutes()} {this.renderExtensionRoutes()}
<Redirect exact from="/" to={this.startURL}/> <Redirect exact from="/" to={this.startURL}/>
<Route component={NotFound}/> <Route component={NotFound}/>

View File

@ -29,7 +29,7 @@ import { CustomResources } from "../+custom-resources/custom-resources";
import { isActiveRoute, navigation } from "../../navigation"; import { isActiveRoute, navigation } from "../../navigation";
import { isAllowedResource } from "../../../common/rbac" import { isAllowedResource } from "../../../common/rbac"
import { Spinner } from "../spinner"; import { Spinner } from "../spinner";
import { clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries"; import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl, RegisteredPage } from "../../../extensions/registries";
const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false }); const SidebarContext = React.createContext<SidebarContextValue>({ pinned: false });
type SidebarContextValue = { type SidebarContextValue = {
@ -76,6 +76,35 @@ export class Sidebar extends React.Component<Props> {
}); });
} }
getTabLayoutRoutes(menu: ClusterPageMenuRegistration, page: RegisteredPage): TabLayoutRoute[] {
if (!menu.id) {
return [];
}
const routes: TabLayoutRoute[] = [];
clusterPageMenuRegistry.getSubItems(menu.id).forEach((subItem) => {
const subPage = clusterPageRegistry.getByPageMenuTarget(subItem.target);
if (subPage) {
routes.push({
routePath: subPage.id,
url: getExtensionPageUrl({ extensionId: subPage.extensionId, pageId: subPage.id, params: subItem.target.params }),
title: subItem.title,
component: subPage.components.Page,
exact: subPage.exact
});
}
});
if (routes.length > 0) {
routes.unshift({
routePath: page.routePath,
url: getExtensionPageUrl({ extensionId: page.extensionId, pageId: page.id, params: menu.target.params }),
title: <Trans>Overview</Trans>,
component: page.components.Page,
exact: page.exact
});
}
return routes;
}
render() { render() {
const { toggle, isPinned, className } = this.props; const { toggle, isPinned, className } = this.props;
const query = namespaceStore.getContextParams(); const query = namespaceStore.getContextParams();
@ -191,17 +220,19 @@ export class Sidebar extends React.Component<Props> {
> >
{this.renderCustomResources()} {this.renderCustomResources()}
</SidebarNavItem> </SidebarNavItem>
{clusterPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => { {clusterPageMenuRegistry.getRootItems().map((menuItem) => {
const registeredPage = clusterPageRegistry.getByPageMenuTarget(target); const registeredPage = clusterPageRegistry.getByPageMenuTarget(menuItem.target);
if (!registeredPage) return; if (!registeredPage) return;
const { extensionId, id: pageId } = registeredPage; const { extensionId, id: pageId } = registeredPage;
const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params }); const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: menuItem.target.params });
const isActive = pageUrl === navigation.location.pathname; const isActive = pageUrl === navigation.location.pathname;
const tabRoutes = this.getTabLayoutRoutes(menuItem, registeredPage)
return ( return (
<SidebarNavItem <SidebarNavItem
key={pageUrl} url={pageUrl} key={pageUrl} url={pageUrl}
text={title} icon={<Icon/>} text={menuItem.title} icon={<menuItem.components.Icon/>}
isActive={isActive} isActive={isActive}
subMenus={tabRoutes}
/> />
) )
})} })}