From 622c45cd6d532a3e3ff3c49c7724bc0573ddb264 Mon Sep 17 00:00:00 2001 From: Jari Kolehmainen Date: Thu, 19 Nov 2020 21:39:53 +0200 Subject: [PATCH] Allow extension cluster menus to have a parent (#1452) Signed-off-by: Jari Kolehmainen --- .../registries/page-menu-registry.ts | 40 ++++++++++-- src/renderer/components/app.tsx | 49 ++++++++++++++- src/renderer/components/layout/sidebar.tsx | 63 ++++++++++++++----- 3 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/extensions/registries/page-menu-registry.ts b/src/extensions/registries/page-menu-registry.ts index 15f11139e6..53131f507b 100644 --- a/src/extensions/registries/page-menu-registry.ts +++ b/src/extensions/registries/page-menu-registry.ts @@ -1,9 +1,10 @@ // Extensions-api -> Register page menu items import type { IconProps } from "../../renderer/components/icon"; import type React from "react"; -import { action } from "mobx"; +import { action, computed } from "mobx"; import { BaseRegistry } from "./base-registry"; import { LensExtension } from "../lens-extension"; +import { RegisteredPage } from "./page-registry"; export interface PageMenuTarget

{ extensionId?: string; @@ -17,11 +18,16 @@ export interface PageMenuRegistration { components: PageMenuComponents; } +export interface ClusterPageMenuRegistration extends PageMenuRegistration { + id?: string; + parentId?: string; +} + export interface PageMenuComponents { Icon: React.ComponentType; } -export class PageMenuRegistry extends BaseRegistry> { +export class GlobalPageMenuRegistry extends BaseRegistry { @action add(items: PageMenuRegistration[], ext: LensExtension) { const normalizedItems = items.map(menuItem => { @@ -35,5 +41,31 @@ export class PageMenuRegistry extends BaseRegistry { + @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(parent: ClusterPageMenuRegistration) { + return this.getItems().filter((item) => item.parentId === parent.id && item.target.extensionId === parent.target.extensionId); + } + + 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(); diff --git a/src/renderer/components/app.tsx b/src/renderer/components/app.tsx index 185a9650fa..7aea1125ad 100755 --- a/src/renderer/components/app.tsx +++ b/src/renderer/components/app.tsx @@ -34,12 +34,15 @@ import { Terminal } from "./dock/terminal"; import { getHostedCluster, getHostedClusterId } from "../../common/cluster-store"; import logger from "../../main/logger"; 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 { appEventBus } from "../../common/event-bus"; import { requestMain } from "../../common/ipc"; import whatInput from 'what-input'; import { clusterSetFrameIdHandler } from "../../common/cluster-ipc"; +import { ClusterPageMenuRegistration, clusterPageMenuRegistry } from "../../extensions/registries"; +import { TabLayoutRoute, TabLayout } from "./layout/tab-layout"; +import { Trans } from "@lingui/macro"; @observer export class App extends React.Component { @@ -72,9 +75,48 @@ export class App extends React.Component { return workloadsURL(); } + getTabLayoutRoutes(menuItem: ClusterPageMenuRegistration) { + const routes: TabLayoutRoute[] = []; + if (!menuItem.id) { + return routes; + } + clusterPageMenuRegistry.getSubItems(menuItem).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 + }); + } + }); + return routes; + } + + renderExtensionTabLayoutRoutes() { + return clusterPageMenuRegistry.getRootItems().map((menu, index) => { + const tabRoutes = this.getTabLayoutRoutes(menu); + if (tabRoutes.length > 0) { + const pageComponent = () => ; + return ; + } else { + const page = clusterPageRegistry.getByPageMenuTarget(menu.target); + if (page) { + const pageComponent = () => ; + return ; + } + } + }); + } + renderExtensionRoutes() { - return clusterPageRegistry.getItems().map(({ components: { Page }, exact, routePath }) => { - return ; + return clusterPageRegistry.getItems().map((page, index) => { + const menu = clusterPageMenuRegistry.getByPage(page); + if (!menu) { + return ; + } }); } @@ -96,6 +138,7 @@ export class App extends React.Component { + {this.renderExtensionTabLayoutRoutes()} {this.renderExtensionRoutes()} diff --git a/src/renderer/components/layout/sidebar.tsx b/src/renderer/components/layout/sidebar.tsx index 428794764f..0c56923c28 100644 --- a/src/renderer/components/layout/sidebar.tsx +++ b/src/renderer/components/layout/sidebar.tsx @@ -29,7 +29,7 @@ import { CustomResources } from "../+custom-resources/custom-resources"; import { isActiveRoute, navigation } from "../../navigation"; import { isAllowedResource } from "../../../common/rbac"; import { Spinner } from "../spinner"; -import { clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl } from "../../../extensions/registries"; +import { ClusterPageMenuRegistration, clusterPageMenuRegistry, clusterPageRegistry, getExtensionPageUrl, RegisteredPage } from "../../../extensions/registries"; const SidebarContext = React.createContext({ pinned: false }); type SidebarContextValue = { @@ -76,6 +76,52 @@ export class Sidebar extends React.Component { }); } + getTabLayoutRoutes(menu: ClusterPageMenuRegistration): TabLayoutRoute[] { + if (!menu.id) { + return []; + } + const routes: TabLayoutRoute[] = []; + + clusterPageMenuRegistry.getSubItems(menu).forEach((subItem) => { + const subPage = clusterPageRegistry.getByPageMenuTarget(subItem.target); + if (subPage) { + routes.push({ + routePath: subPage.routePath, + url: getExtensionPageUrl({ extensionId: subPage.extensionId, pageId: subPage.id, params: subItem.target.params }), + title: subItem.title, + component: subPage.components.Page, + exact: subPage.exact + }); + } + }); + return routes; + } + + renderRegisteredMenus() { + return clusterPageMenuRegistry.getRootItems().map((menuItem) => { + const registeredPage = clusterPageRegistry.getByPageMenuTarget(menuItem.target); + let pageUrl: string; + let isActive = false; + if (registeredPage) { + const { extensionId, id: pageId } = registeredPage; + pageUrl = getExtensionPageUrl({ extensionId, pageId, params: menuItem.target.params }); + isActive = pageUrl === navigation.location.pathname; + } + const tabRoutes = this.getTabLayoutRoutes(menuItem); + if (!registeredPage && tabRoutes.length == 0) { + return; + } + return ( + } + isActive={isActive} + subMenus={tabRoutes} + /> + ); + }); + } + render() { const { toggle, isPinned, className } = this.props; const query = namespaceStore.getContextParams(); @@ -191,20 +237,7 @@ export class Sidebar extends React.Component { > {this.renderCustomResources()} - {clusterPageMenuRegistry.getItems().map(({ title, target, components: { Icon } }) => { - const registeredPage = clusterPageRegistry.getByPageMenuTarget(target); - if (!registeredPage) return; - const { extensionId, id: pageId } = registeredPage; - const pageUrl = getExtensionPageUrl({ extensionId, pageId, params: target.params }); - const isActive = pageUrl === navigation.location.pathname; - return ( - } - isActive={isActive} - /> - ); - })} + {this.renderRegisteredMenus()}