diff --git a/src/extensions/extension-loader.ts b/src/extensions/extension-loader.ts index da2669550f..d344e8ebc3 100644 --- a/src/extensions/extension-loader.ts +++ b/src/extensions/extension-loader.ts @@ -251,6 +251,7 @@ export class ExtensionLoader extends Singleton { registries.commandRegistry.add(extension.commands), registries.welcomeMenuRegistry.add(extension.welcomeMenus), registries.catalogEntityDetailRegistry.add(extension.catalogEntityDetailItems), + registries.topBarRegistry.add(extension.topBarItems), ]; this.events.on("remove", (removedExtension: LensRendererExtension) => { diff --git a/src/extensions/lens-renderer-extension.ts b/src/extensions/lens-renderer-extension.ts index fb26d383b0..7119b0f7ac 100644 --- a/src/extensions/lens-renderer-extension.ts +++ b/src/extensions/lens-renderer-extension.ts @@ -28,6 +28,7 @@ import { LensExtension } from "./lens-extension"; import { getExtensionPageUrl } from "./registries/page-registry"; import type { CommandRegistration } from "./registries/command-registry"; import type { EntitySettingRegistration } from "./registries/entity-setting-registry"; +import type { TopBarRegistration } from "./registries/topbar-registry"; export class LensRendererExtension extends LensExtension { globalPages: PageRegistration[] = []; @@ -44,6 +45,7 @@ export class LensRendererExtension extends LensExtension { commands: CommandRegistration[] = []; welcomeMenus: WelcomeMenuRegistration[] = []; catalogEntityDetailItems: CatalogEntityDetailRegistration[] = []; + topBarItems: TopBarRegistration[] = []; async navigate

(pageId?: string, params?: P) { const { navigate } = await import("../renderer/navigation"); diff --git a/src/extensions/registries/index.ts b/src/extensions/registries/index.ts index b9ba986521..70c1c5ecba 100644 --- a/src/extensions/registries/index.ts +++ b/src/extensions/registries/index.ts @@ -35,3 +35,4 @@ export * from "./welcome-menu-registry"; export * from "./protocol-handler-registry"; export * from "./catalog-entity-detail-registry"; export * from "./workloads-overview-detail-registry"; +export * from "./topbar-registry"; diff --git a/src/extensions/registries/topbar-registry.ts b/src/extensions/registries/topbar-registry.ts new file mode 100644 index 0000000000..d0c3f03133 --- /dev/null +++ b/src/extensions/registries/topbar-registry.ts @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import type React from "react"; +import { BaseRegistry } from "./base-registry"; + +interface TopBarComponents { + Item?: React.ComponentType; +} + +export interface TopBarRegistration { + components: TopBarComponents; +} + +export class TopBarRegistry extends BaseRegistry { +} + +export const topBarRegistry = new TopBarRegistry(); diff --git a/src/renderer/components/cluster-manager/topbar.test.tsx b/src/renderer/components/cluster-manager/topbar.test.tsx new file mode 100644 index 0000000000..ce716581db --- /dev/null +++ b/src/renderer/components/cluster-manager/topbar.test.tsx @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2021 OpenLens Authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import React from "react"; +import { render } from "@testing-library/react"; +import "@testing-library/jest-dom/extend-expect"; + +jest.mock("electron", () => ({ + app: { + getPath: () => "/foo", + }, +})); + +jest.mock("../../../extensions/registries"); + +import { topBarRegistry } from "../../../extensions/registries"; +import { TopBar } from "../layout/topbar"; + +describe("", () => { + it("renders w/o errors", () => { + const { container } = render(); + + expect(container).toBeInstanceOf(HTMLElement); + }); + + it("renders title", async () => { + const { getByTestId } = render(); + + expect(await getByTestId("topbarLabel")).toHaveTextContent("topbar"); + }); + + it("renders items", async () => { + const testId = "testId"; + const text = "an item"; + + topBarRegistry.getItems = jest.fn().mockImplementationOnce(() => [ + { + components: { + Item: {text} + } + } + ]); + + const { getByTestId } = render(); + + expect(await getByTestId(testId)).toHaveTextContent(text); + }); +}); diff --git a/src/renderer/components/layout/topbar.module.css b/src/renderer/components/layout/topbar.module.css index 4738d53339..2c3741024b 100644 --- a/src/renderer/components/layout/topbar.module.css +++ b/src/renderer/components/layout/topbar.module.css @@ -43,4 +43,7 @@ align-items: center; display: flex; height: 100%; +} + +.extensionItem { } \ No newline at end of file diff --git a/src/renderer/components/layout/topbar.tsx b/src/renderer/components/layout/topbar.tsx index 2fe302ce55..a29c47a5c3 100644 --- a/src/renderer/components/layout/topbar.tsx +++ b/src/renderer/components/layout/topbar.tsx @@ -22,16 +22,44 @@ import styles from "./topbar.module.css"; import React from "react"; import { observer } from "mobx-react"; +import { topBarRegistry } from "../../../extensions/registries"; interface Props extends React.HTMLAttributes { label: React.ReactNode; } export const TopBar = observer(({ label, children, ...rest }: Props) => { + const renderRegisteredItems = () => { + const items = topBarRegistry.getItems(); + + if (!Array.isArray(items)) { + return null; + } + + return ( +

+ {items.map((registration, index) => { + if (!registration?.components?.Item) { + return null; + } + + return ( +
+ {registration.components.Item} +
+ ); + })} +
+ ); + }; + return (
-
{label}
-
{children}
+
{label}
+
+ {renderRegisteredItems()} + {children} +
); });